mirror of
https://codeberg.org/unspeaker/tek.git
synced 2025-12-06 11:46:41 +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");
|
||||
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)]
|
||||
|
|
|
|||
|
|
@ -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())?;
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<RwLock<JackClient>>,
|
||||
|
|
@ -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};
|
||||
|
|
|
|||
|
|
@ -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 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};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,30 @@
|
|||
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).
|
||||
pub jack: Arc<RwLock<JackClient>>,
|
||||
pub name: String,
|
||||
pub tracks: Vec<Track<E>>,
|
||||
pub tracks: Vec<MixerTrack>,
|
||||
pub selected_track: 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> {
|
||||
Ok(Self {
|
||||
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> {
|
||||
let track = Track::new(name)?;
|
||||
let track = MixerTrack::new(name)?;
|
||||
self.tracks.push(track);
|
||||
Ok(self)
|
||||
}
|
||||
pub fn track (&self) -> Option<&Track<E>> {
|
||||
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<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> {
|
||||
impl MixerTrack {
|
||||
pub fn new (name: &str) -> Usually<Self> {
|
||||
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<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
||||
self.devices.get(i).map(|d|d.state.write().unwrap())
|
||||
}
|
||||
pub fn device_mut (&self) -> Option<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
||||
self.get_device_mut(self.device)
|
||||
}
|
||||
/// Add a device to the end of the chain.
|
||||
pub fn append_device (&mut self, device: JackDevice<E>) -> Usually<&mut JackDevice<E>> {
|
||||
self.devices.push(device);
|
||||
let index = self.devices.len() - 1;
|
||||
Ok(&mut self.devices[index])
|
||||
}
|
||||
pub fn add_device (&mut self, device: JackDevice<E>) {
|
||||
self.devices.push(device);
|
||||
}
|
||||
//fn get_device_mut (&self, i: usize) -> Option<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
||||
//self.devices.get(i).map(|d|d.state.write().unwrap())
|
||||
//}
|
||||
//pub fn device_mut (&self) -> Option<RwLockWriteGuard<Box<dyn AudioComponent<E>>>> {
|
||||
//self.get_device_mut(self.device)
|
||||
//}
|
||||
///// Add a device to the end of the chain.
|
||||
//pub fn append_device (&mut self, device: JackDevice<E>) -> Usually<&mut JackDevice<E>> {
|
||||
//self.devices.push(device);
|
||||
//let index = self.devices.len() - 1;
|
||||
//Ok(&mut self.devices[index])
|
||||
//}
|
||||
//pub fn add_device (&mut self, device: JackDevice<E>) {
|
||||
//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<E: Engine> Track<E> {
|
|||
//}
|
||||
}
|
||||
|
||||
pub struct TrackView<'a, E: Engine> {
|
||||
pub chain: Option<&'a Track<E>>,
|
||||
pub struct TrackView<'a> {
|
||||
pub chain: Option<&'a MixerTrack>,
|
||||
pub direction: Direction,
|
||||
pub focused: 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]> {
|
||||
todo!()
|
||||
}
|
||||
|
|
@ -129,40 +130,40 @@ impl<'a> Render<Tui> for TrackView<'a, Tui> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Content<Tui> for Mixer<Tui> {
|
||||
fn content (&self) -> impl Render<Tui> {
|
||||
Stack::right(|add| {
|
||||
for channel in self.tracks.iter() {
|
||||
add(channel)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
//impl Content<Tui> for Mixer<Tui> {
|
||||
//fn content (&self) -> impl Render<Tui> {
|
||||
//Stack::right(|add| {
|
||||
//for channel in self.tracks.iter() {
|
||||
//add(channel)?;
|
||||
//}
|
||||
//Ok(())
|
||||
//})
|
||||
//}
|
||||
//}
|
||||
|
||||
impl Content<Tui> for Track<Tui> {
|
||||
fn content (&self) -> impl Render<Tui> {
|
||||
TrackView {
|
||||
chain: Some(&self),
|
||||
direction: tek_core::Direction::Right,
|
||||
focused: true,
|
||||
entered: true,
|
||||
//pub channels: u8,
|
||||
//pub input_ports: Vec<Port<AudioIn>>,
|
||||
//pub pre_gain_meter: f64,
|
||||
//pub gain: f64,
|
||||
//pub insert_ports: Vec<Port<AudioOut>>,
|
||||
//pub return_ports: Vec<Port<AudioIn>>,
|
||||
//pub post_gain_meter: f64,
|
||||
//pub post_insert_meter: f64,
|
||||
//pub level: f64,
|
||||
//pub pan: f64,
|
||||
//pub output_ports: Vec<Port<AudioOut>>,
|
||||
//pub post_fader_meter: f64,
|
||||
//pub route: String,
|
||||
}
|
||||
}
|
||||
}
|
||||
//impl Content<Tui> for Track<Tui> {
|
||||
//fn content (&self) -> impl Render<Tui> {
|
||||
//TrackView {
|
||||
//chain: Some(&self),
|
||||
//direction: tek_core::Direction::Right,
|
||||
//focused: true,
|
||||
//entered: true,
|
||||
////pub channels: u8,
|
||||
////pub input_ports: Vec<Port<AudioIn>>,
|
||||
////pub pre_gain_meter: f64,
|
||||
////pub gain: f64,
|
||||
////pub insert_ports: Vec<Port<AudioOut>>,
|
||||
////pub return_ports: Vec<Port<AudioIn>>,
|
||||
////pub post_gain_meter: f64,
|
||||
////pub post_insert_meter: f64,
|
||||
////pub level: f64,
|
||||
////pub pan: f64,
|
||||
////pub output_ports: Vec<Port<AudioOut>>,
|
||||
////pub post_fader_meter: f64,
|
||||
////pub route: String,
|
||||
//}
|
||||
//}
|
||||
//}
|
||||
|
||||
handle!(<Tui>|self:Mixer,engine|{
|
||||
if let TuiEvent::Input(crossterm::event::Event::Key(event)) = engine.event() {
|
||||
|
|
@ -212,18 +213,18 @@ handle!(<Tui>|self:Mixer,engine|{
|
|||
Ok(None)
|
||||
});
|
||||
|
||||
handle!(<Tui>|self:Track<Tui>,from|{
|
||||
handle!(<Tui>|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!(<Tui>|self:Track<Tui>,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!(<Tui>|self:Track<Tui>,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<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)
|
||||
//}
|
||||
|
||||
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 super::{*, piano_h::PianoHorizontalKeys};
|
||||
use crate::{*, tui::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 std::fs::File;
|
||||
|
|
@ -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::*;
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
|
|
|
|||
|
|
@ -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