diff --git a/crates/cli/src/cli_arranger.rs b/crates/cli/src/cli_arranger.rs index 6c218c8e..5cf780db 100644 --- a/crates/cli/src/cli_arranger.rs +++ b/crates/cli/src/cli_arranger.rs @@ -1,9 +1,6 @@ include!("./lib.rs"); -use tek::tui::ArrangerTui; - -pub fn main () -> Usually<()> { - ArrangerCli::parse().run() -} +use tek::ArrangerTui; +pub fn main () -> Usually<()> { ArrangerCli::parse().run() } /// Launches an interactive MIDI arranger. #[derive(Debug, Parser)] diff --git a/crates/cli/src/cli_groovebox.rs b/crates/cli/src/cli_groovebox.rs index d72778a6..002777c8 100644 --- a/crates/cli/src/cli_groovebox.rs +++ b/crates/cli/src/cli_groovebox.rs @@ -30,7 +30,7 @@ pub struct GrooveboxCli { impl GrooveboxCli { fn run (&self) -> Usually<()> { 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 midi_in = jack.register_port("i", MidiIn::default())?; let midi_out = jack.register_port("o", MidiOut::default())?; diff --git a/crates/cli/src/cli_sampler.rs b/crates/cli/src/cli_sampler.rs index 55601c18..ae7f7bd1 100644 --- a/crates/cli/src/cli_sampler.rs +++ b/crates/cli/src/cli_sampler.rs @@ -9,7 +9,7 @@ pub fn main () -> Usually<()> { SamplerCli::parse().run() } impl SamplerCli { fn run (&self) -> Usually<()> { 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(()) diff --git a/crates/edn/src/lib.rs b/crates/edn/src/lib.rs index a6b51214..347f98a0 100644 --- a/crates/edn/src/lib.rs +++ b/crates/edn/src/lib.rs @@ -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::collections::BTreeMap; diff --git a/crates/tek/src/tui/app_arranger.rs b/crates/tek/src/arranger.rs similarity index 93% rename from crates/tek/src/tui/app_arranger.rs rename to crates/tek/src/arranger.rs index 3a09d730..71478dc1 100644 --- a/crates/tek/src/tui/app_arranger.rs +++ b/crates/tek/src/arranger.rs @@ -1,4 +1,13 @@ 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` pub struct ArrangerTui { jack: Arc>, diff --git a/crates/tek/src/tui/arranger_command.rs b/crates/tek/src/arranger/arranger_command.rs similarity index 100% rename from crates/tek/src/tui/arranger_command.rs rename to crates/tek/src/arranger/arranger_command.rs diff --git a/crates/tek/src/tui/arranger_h.rs b/crates/tek/src/arranger/arranger_h.rs similarity index 100% rename from crates/tek/src/tui/arranger_h.rs rename to crates/tek/src/arranger/arranger_h.rs diff --git a/crates/tek/src/tui/arranger_mode.rs b/crates/tek/src/arranger/arranger_mode.rs similarity index 100% rename from crates/tek/src/tui/arranger_mode.rs rename to crates/tek/src/arranger/arranger_mode.rs diff --git a/crates/tek/src/tui/arranger_scene.rs b/crates/tek/src/arranger/arranger_scene.rs similarity index 100% rename from crates/tek/src/tui/arranger_scene.rs rename to crates/tek/src/arranger/arranger_scene.rs diff --git a/crates/tek/src/tui/arranger_select.rs b/crates/tek/src/arranger/arranger_select.rs similarity index 100% rename from crates/tek/src/tui/arranger_select.rs rename to crates/tek/src/arranger/arranger_select.rs diff --git a/crates/tek/src/tui/arranger_track.rs b/crates/tek/src/arranger/arranger_track.rs similarity index 100% rename from crates/tek/src/tui/arranger_track.rs rename to crates/tek/src/arranger/arranger_track.rs diff --git a/crates/tek/src/tui/arranger_v.rs b/crates/tek/src/arranger/arranger_v.rs similarity index 100% rename from crates/tek/src/tui/arranger_v.rs rename to crates/tek/src/arranger/arranger_v.rs diff --git a/crates/tek/src/tui/arranger_v/v_clips.rs b/crates/tek/src/arranger/arranger_v/v_clips.rs similarity index 100% rename from crates/tek/src/tui/arranger_v/v_clips.rs rename to crates/tek/src/arranger/arranger_v/v_clips.rs diff --git a/crates/tek/src/tui/arranger_v/v_cursor.rs b/crates/tek/src/arranger/arranger_v/v_cursor.rs similarity index 100% rename from crates/tek/src/tui/arranger_v/v_cursor.rs rename to crates/tek/src/arranger/arranger_v/v_cursor.rs diff --git a/crates/tek/src/tui/arranger_v/v_head.rs b/crates/tek/src/arranger/arranger_v/v_head.rs similarity index 100% rename from crates/tek/src/tui/arranger_v/v_head.rs rename to crates/tek/src/arranger/arranger_v/v_head.rs diff --git a/crates/tek/src/tui/arranger_v/v_io.rs b/crates/tek/src/arranger/arranger_v/v_io.rs similarity index 100% rename from crates/tek/src/tui/arranger_v/v_io.rs rename to crates/tek/src/arranger/arranger_v/v_io.rs diff --git a/crates/tek/src/tui/arranger_v/v_sep.rs b/crates/tek/src/arranger/arranger_v/v_sep.rs similarity index 100% rename from crates/tek/src/tui/arranger_v/v_sep.rs rename to crates/tek/src/arranger/arranger_v/v_sep.rs diff --git a/crates/tek/src/audio.rs b/crates/tek/src/audio.rs index 3d287f09..c7b7e813 100644 --- a/crates/tek/src/audio.rs +++ b/crates/tek/src/audio.rs @@ -1,9 +1 @@ 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}; diff --git a/crates/tek/src/audio/mixer.rs b/crates/tek/src/audio/mixer.rs deleted file mode 100644 index 0ec986c9..00000000 --- a/crates/tek/src/audio/mixer.rs +++ /dev/null @@ -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>, - pub name: String, - pub tracks: Vec, - 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>, - /// Outputs of last device - pub audio_outs: Vec>, - /// Device chain - pub devices: Vec>, -} - -//impl MixerTrackDevice for LV2Plugin {} - -pub trait MixerTrackDevice: Debug + Send + Sync { - fn boxed (self) -> Box where Self: Sized + 'static { - Box::new(self) - } -} - -impl MixerTrackDevice for Sampler {} - -impl MixerTrackDevice for Plugin {} diff --git a/crates/tek/src/audio/sampler.rs b/crates/tek/src/audio/sampler.rs deleted file mode 100644 index c9052520..00000000 --- a/crates/tek/src/audio/sampler.rs +++ /dev/null @@ -1,152 +0,0 @@ -use crate::*; - -/// The sampler plugin plays sounds. -#[derive(Debug)] -pub struct Sampler { - pub jack: Arc>, - pub name: String, - pub mapped: BTreeMap>>, - pub unmapped: Vec>>, - pub voices: Arc>>, - pub midi_in: Port, - pub audio_outs: Vec>, - pub buffer: Vec>, - 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>, - pub rate: Option, -} - -/// A currently playing instance of a sample. -#[derive(Default, Debug, Clone)] -pub struct Voice { - pub sample: Arc>, - 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>) -> Self { - Self { name: name.to_string(), start, end, channels, rate: None } - } - pub fn play (sample: &Arc>, 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>)> { - let mut channels: Vec> = vec![]; - for channel in wavers::Wav::from_path(src)?.channels() { - channels.push(channel); - } - let mut end = 0; - let mut data: Vec> = 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 { - 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 - } -} diff --git a/crates/tek/src/tui/app_groovebox.rs b/crates/tek/src/groovebox.rs similarity index 100% rename from crates/tek/src/tui/app_groovebox.rs rename to crates/tek/src/groovebox.rs diff --git a/crates/tek/src/lib.rs b/crates/tek/src/lib.rs index 67cf5166..2518e788 100644 --- a/crates/tek/src/lib.rs +++ b/crates/tek/src/lib.rs @@ -7,15 +7,44 @@ pub mod time; pub(crate) use self::time::*; 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}; diff --git a/crates/tek/src/tui/app_mixer.rs b/crates/tek/src/mixer.rs similarity index 63% rename from crates/tek/src/tui/app_mixer.rs rename to crates/tek/src/mixer.rs index 111d5cb2..71aba47f 100644 --- a/crates/tek/src/tui/app_mixer.rs +++ b/crates/tek/src/mixer.rs @@ -1,14 +1,30 @@ use crate::*; -pub struct Mixer { +#[derive(Debug)] +pub struct Mixer { /// JACK client handle (needs to not be dropped for standalone mode to work). pub jack: Arc>, pub name: String, - pub tracks: Vec>, + pub tracks: Vec, pub selected_track: usize, pub selected_column: usize, } -impl Mixer { + +/// A mixer track. +#[derive(Debug)] +pub struct MixerTrack { + pub name: String, + /// Inputs of 1st device + pub audio_ins: Vec>, + /// Outputs of last device + pub audio_outs: Vec>, + /// Device chain + pub devices: Vec>, +} + +audio!(|self: Mixer, _client, _scope|Control::Continue); + +impl Mixer { pub fn new (jack: &Arc>, name: &str) -> Usually { Ok(Self { jack: jack.clone(), @@ -19,57 +35,42 @@ impl Mixer { }) } 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); Ok(self) } - pub fn track (&self) -> Option<&Track> { + pub fn track (&self) -> Option<&MixerTrack> { self.tracks.get(self.selected_track) } } -//pub const ACTIONS: [(&'static str, &'static str);2] = [ - //("+/-", "Adjust"), - //("Ins/Del", "Add/remove track"), -//]; - - -/// A sequencer track. -#[derive(Debug)] -pub struct Track { - pub name: String, - /// Inputs and outputs of 1st and last device - pub ports: JackPorts, - /// Device chain - pub devices: Vec>, - /// Device selector - pub device: usize, -} - -impl Track { +impl MixerTrack { pub fn new (name: &str) -> Usually { Ok(Self { - name: name.to_string(), - ports: JackPorts::default(), - devices: vec![], - device: 0, + name: name.to_string(), + audio_ins: vec![], + audio_outs: vec![], + devices: vec![], + //ports: JackPorts::default(), + //devices: vec![], + //device: 0, }) } - fn get_device_mut (&self, i: usize) -> Option>>> { - self.devices.get(i).map(|d|d.state.write().unwrap()) - } - pub fn device_mut (&self) -> Option>>> { - self.get_device_mut(self.device) - } - /// Add a device to the end of the chain. - pub fn append_device (&mut self, device: JackDevice) -> Usually<&mut JackDevice> { - self.devices.push(device); - let index = self.devices.len() - 1; - Ok(&mut self.devices[index]) - } - pub fn add_device (&mut self, device: JackDevice) { - self.devices.push(device); - } + //fn get_device_mut (&self, i: usize) -> Option>>> { + //self.devices.get(i).map(|d|d.state.write().unwrap()) + //} + //pub fn device_mut (&self) -> Option>>> { + //self.get_device_mut(self.device) + //} + ///// Add a device to the end of the chain. + //pub fn append_device (&mut self, device: JackDevice) -> Usually<&mut JackDevice> { + //self.devices.push(device); + //let index = self.devices.len() - 1; + //Ok(&mut self.devices[index]) + //} + //pub fn add_device (&mut self, device: JackDevice) { + //self.devices.push(device); + //} //pub fn connect_first_device (&self) -> Usually<()> { //if let (Some(port), Some(device)) = (&self.midi_out, self.devices.get(0)) { //device.client.as_client().connect_ports(&port, &device.midi_ins()?[0])?; @@ -88,14 +89,14 @@ impl Track { //} } -pub struct TrackView<'a, E: Engine> { - pub chain: Option<&'a Track>, +pub struct TrackView<'a> { + pub chain: Option<&'a MixerTrack>, pub direction: Direction, pub focused: bool, pub entered: bool, } -impl<'a> Render for TrackView<'a, Tui> { +impl<'a> Render for TrackView<'a> { fn min_size (&self, area: [u16;2]) -> Perhaps<[u16;2]> { todo!() } @@ -129,40 +130,40 @@ impl<'a> Render for TrackView<'a, Tui> { } } -impl Content for Mixer { - fn content (&self) -> impl Render { - Stack::right(|add| { - for channel in self.tracks.iter() { - add(channel)?; - } - Ok(()) - }) - } -} +//impl Content for Mixer { + //fn content (&self) -> impl Render { + //Stack::right(|add| { + //for channel in self.tracks.iter() { + //add(channel)?; + //} + //Ok(()) + //}) + //} +//} -impl Content for Track { - fn content (&self) -> impl Render { - TrackView { - chain: Some(&self), - direction: tek_core::Direction::Right, - focused: true, - entered: true, - //pub channels: u8, - //pub input_ports: Vec>, - //pub pre_gain_meter: f64, - //pub gain: f64, - //pub insert_ports: Vec>, - //pub return_ports: Vec>, - //pub post_gain_meter: f64, - //pub post_insert_meter: f64, - //pub level: f64, - //pub pan: f64, - //pub output_ports: Vec>, - //pub post_fader_meter: f64, - //pub route: String, - } - } -} +//impl Content for Track { + //fn content (&self) -> impl Render { + //TrackView { + //chain: Some(&self), + //direction: tek_core::Direction::Right, + //focused: true, + //entered: true, + ////pub channels: u8, + ////pub input_ports: Vec>, + ////pub pre_gain_meter: f64, + ////pub gain: f64, + ////pub insert_ports: Vec>, + ////pub return_ports: Vec>, + ////pub post_gain_meter: f64, + ////pub post_insert_meter: f64, + ////pub level: f64, + ////pub pan: f64, + ////pub output_ports: Vec>, + ////pub post_fader_meter: f64, + ////pub route: String, + //} + //} +//} handle!(|self:Mixer,engine|{ if let TuiEvent::Input(crossterm::event::Event::Key(event)) = engine.event() { @@ -212,18 +213,18 @@ handle!(|self:Mixer,engine|{ Ok(None) }); -handle!(|self:Track,from|{ +handle!(|self:MixerTrack,from|{ match from.event() { //, NONE, "chain_cursor_up", "move cursor up", || { - key!(KeyCode::Up) => { + key_pat!(KeyCode::Up) => { Ok(Some(true)) }, // , NONE, "chain_cursor_down", "move cursor down", || { - key!(KeyCode::Down) => { + key_pat!(KeyCode::Down) => { Ok(Some(true)) }, // Left, NONE, "chain_cursor_left", "move cursor left", || { - key!(KeyCode::Left) => { + key_pat!(KeyCode::Left) => { //if let Some(track) = app.arranger.track_mut() { //track.device = track.device.saturating_sub(1); //return Ok(true) @@ -231,7 +232,7 @@ handle!(|self:Track,from|{ Ok(Some(true)) }, // , NONE, "chain_cursor_right", "move cursor right", || { - key!(KeyCode::Right) => { + key_pat!(KeyCode::Right) => { //if let Some(track) = app.arranger.track_mut() { //track.device = (track.device + 1).min(track.devices.len().saturating_sub(1)); //return Ok(true) @@ -239,10 +240,24 @@ handle!(|self:Track,from|{ Ok(Some(true)) }, // , NONE, "chain_mode_switch", "switch the display mode", || { - key!(KeyCode::Char('`')) => { + key_pat!(KeyCode::Char('`')) => { //app.chain_mode = !app.chain_mode; Ok(Some(true)) }, _ => Ok(None) } }); + +pub enum MixerTrackCommand {} + +//impl MixerTrackDevice for LV2Plugin {} + +pub trait MixerTrackDevice: Debug + Send + Sync { + fn boxed (self) -> Box where Self: Sized + 'static { + Box::new(self) + } +} + +impl MixerTrackDevice for Sampler {} + +impl MixerTrackDevice for Plugin {} diff --git a/crates/tek/src/plugin.rs b/crates/tek/src/plugin.rs index b728bfd1..63595c17 100644 --- a/crates/tek/src/plugin.rs +++ b/crates/tek/src/plugin.rs @@ -127,3 +127,140 @@ audio!(|self: PluginAudio, client, scope|{ //} //Ok(jack) //} + +impl Plugin { + /// Create a plugin host device. + pub fn new ( + jack: &Arc>, + name: &str, + ) -> Usually { + 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 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!(|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) + } +}); diff --git a/crates/tek/src/tui/app_sampler.rs b/crates/tek/src/sampler.rs similarity index 77% rename from crates/tek/src/tui/app_sampler.rs rename to crates/tek/src/sampler.rs index 60ca1ee9..ba68e5db 100644 --- a/crates/tek/src/tui/app_sampler.rs +++ b/crates/tek/src/sampler.rs @@ -1,5 +1,155 @@ -use crate::*; -use super::{*, piano_h::PianoHorizontalKeys}; +use crate::{*, tui::piano_h::PianoHorizontalKeys}; + +/// The sampler plugin plays sounds. +#[derive(Debug)] +pub struct Sampler { + pub jack: Arc>, + pub name: String, + pub mapped: BTreeMap>>, + pub unmapped: Vec>>, + pub voices: Arc>>, + pub midi_in: Port, + pub audio_outs: Vec>, + pub buffer: Vec>, + 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>, + pub rate: Option, +} + +/// A currently playing instance of a sample. +#[derive(Default, Debug, Clone)] +pub struct Voice { + pub sample: Arc>, + 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>) -> Self { + Self { name: name.to_string(), start, end, channels, rate: None } + } + pub fn play (sample: &Arc>, 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>)> { + let mut channels: Vec> = vec![]; + for channel in wavers::Wav::from_path(src)?.channels() { + channels.push(channel); + } + let mut end = 0; + let mut data: Vec> = 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 { + 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 std::fs::File; diff --git a/crates/tek/src/tui/app_sequencer.rs b/crates/tek/src/sequencer.rs similarity index 100% rename from crates/tek/src/tui/app_sequencer.rs rename to crates/tek/src/sequencer.rs diff --git a/crates/tek/src/tui/app_transport.rs b/crates/tek/src/transport.rs similarity index 100% rename from crates/tek/src/tui/app_transport.rs rename to crates/tek/src/transport.rs diff --git a/crates/tek/src/tui.rs b/crates/tek/src/tui.rs index 35c167ad..4b703916 100644 --- a/crates/tek/src/tui.rs +++ b/crates/tek/src/tui.rs @@ -12,40 +12,15 @@ mod tui_style; mod tui_theme; pub(crate) use self::tui_theme::*; 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::*; -mod phrase_editor; pub(crate) use self::phrase_editor::*; -mod status; pub(crate) use self::status::*; -mod file_browser; pub(crate) use self::file_browser::*; -mod piano_h; pub(crate) use self::piano_h::*; +pub mod pool; pub(crate) use self::pool::*; +pub mod phrase_editor; pub(crate) use self::phrase_editor::*; +pub mod status; pub(crate) use self::status::*; +pub mod file_browser; pub(crate) use self::file_browser::*; +pub mod piano_h; pub(crate) use self::piano_h::*; //////////////////////////////////////////////////////// diff --git a/crates/tek/src/tui/app_plugin.rs b/crates/tek/src/tui/app_plugin.rs deleted file mode 100644 index 8f4eddfd..00000000 --- a/crates/tek/src/tui/app_plugin.rs +++ /dev/null @@ -1,147 +0,0 @@ -use crate::*; - -/// A plugin device. -pub struct Plugin { - _engine: PhantomData, - /// JACK client handle (needs to not be dropped for standalone mode to work). - pub jack: Arc>, - pub name: String, - pub path: Option, - pub plugin: Option, - pub selected: usize, - pub mapping: bool, - pub ports: JackPorts, -} - -impl Plugin { - /// Create a plugin host device. - pub fn new ( - jack: &Arc>, - name: &str, - ) -> Usually { - Ok(Self { - _engine: Default::default(), - jack: jack.clone(), - name: name.into(), - path: None, - plugin: None, - selected: 0, - mapping: false, - ports: JackPorts::default() - }) - } -} -impl Render 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(Rect { x, y, width: w, height: 1 }) -} - -handle!(|self:Plugin,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) - } -}); -}