diff --git a/Cargo.lock b/Cargo.lock index 91f75883..01ffc1a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2539,6 +2539,7 @@ dependencies = [ "clojure-reader", "crossterm", "midly", + "once_cell", "ratatui", "toml", ] @@ -2558,6 +2559,8 @@ dependencies = [ "tek_chain", "tek_core", "tek_jack", + "tek_plugin", + "tek_sampler", ] [[package]] diff --git a/crates/tek/Cargo.toml b/crates/tek/Cargo.toml index c578d97d..da7b78a8 100644 --- a/crates/tek/Cargo.toml +++ b/crates/tek/Cargo.toml @@ -14,7 +14,7 @@ tek_sampler = { path = "../tek_sampler" } tek_sequencer = { path = "../tek_sequencer" } tek_timer = { path = "../tek_timer" } tek_chain = { path = "../tek_chain" } -tek_mixer = { path = "../tek_mixer" } +tek_mixer = { path = "../tek_mixer", features = ["standalone_devices"] } #jack = "0.10" #crossterm = "0.27" #ratatui = { version = "0.26.3", features = [ "unstable-widget-ref", "underline-color" ] } @@ -32,7 +32,6 @@ tek_mixer = { path = "../tek_mixer" } #fraction = "0.15.3" #rlsf = "0.2.1" #r8brain-rs = "0.3.5" -#once_cell = "1.19.0" #symphonia = { version = "0.5.4", features = [ "all" ] } diff --git a/crates/tek/src/control.rs b/crates/tek/src/control.rs index 646cf486..0a5e8aeb 100644 --- a/crates/tek/src/control.rs +++ b/crates/tek/src/control.rs @@ -135,7 +135,7 @@ pub const KEYMAP_GLOBAL: &'static [KeyBinding] = keymap!(App { /// Generic key bindings for views that support focus. pub const KEYMAP_FOCUS: &'static [KeyBinding] = keymap!(App { [Char(';'), NONE, "command", "open command palette", |_: &mut App| { - *MODAL.lock().unwrap() = Some(Box::new(crate::devices::help::HelpModal::new())); + *MODAL.lock().unwrap() = Some(Box::new(HelpModal::new())); Ok(true) }], [Tab, NONE, "focus_next", "focus next area", focus_next], diff --git a/crates/tek/src/edn.rs b/crates/tek/src/edn.rs index 64173758..67fffbca 100644 --- a/crates/tek/src/edn.rs +++ b/crates/tek/src/edn.rs @@ -14,26 +14,15 @@ //! * [LV2Plugin::load_edn] use crate::*; -use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; - -/// EDN parsing helper. -macro_rules! edn { - ($edn:ident { $($pat:pat => $expr:expr),* $(,)? }) => { - match $edn { $($pat => $expr),* } - }; - ($edn:ident in $args:ident { $($pat:pat => $expr:expr),* $(,)? }) => { - for $edn in $args { - edn!($edn { $($pat => $expr),* }) - } - }; -} impl App { + pub fn from_edn (src: &str) -> Usually { let mut app = Self::new()?; app.load_edn(src)?; Ok(app) } + pub fn load_edn (&mut self, mut src: &str) -> Usually<&mut Self> { loop { match read(src) { @@ -55,6 +44,7 @@ impl App { } Ok(self) } + fn load_edn_one <'e> (&mut self, edn: Edn<'e>) -> Usually<()> { match edn { Edn::List(items) => { @@ -69,10 +59,10 @@ impl App { } }, Some(Edn::Symbol("scene")) => { - tek_sequencer::Scene::load_edn(self, &items[1..])?; + tek_sequencer::Scene::from_edn(self, &items[1..])?; }, Some(Edn::Symbol("track")) => { - tek_mixer::Track::load_edn(self, &items[1..])?; + tek_mixer::MixerTrack::from_edn(self, &items[1..])?; }, Some(Edn::Symbol("midi-in")) => { self.midi_ins = items[1..].iter().map(|x|match x { @@ -105,62 +95,5 @@ impl App { } Ok(()) } -} - -impl Phrase { - fn load_edn <'e> (ppq: usize, args: &[Edn<'e>]) -> Usually { - let mut phrase = Self::default(); - let mut name = String::new(); - let mut beats = 0usize; - let mut steps = 0usize; - edn!(edn in args { - Edn::Map(map) => { - if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { - name = String::from(*n); - } - if let Some(Edn::Int(b)) = map.get(&Edn::Key(":beats")) { - beats = *b as usize; - phrase.length = ppq * beats; - for _ in phrase.notes.len()..phrase.length { - phrase.notes.push(Vec::with_capacity(16)) - } - } - if let Some(Edn::Int(s)) = map.get(&Edn::Key(":steps")) { - steps = *s as usize; - } - }, - Edn::List(args) => { - let time = (match args.get(0) { - Some(Edn::Key(text)) => text[1..].parse::()?, - Some(Edn::Int(i)) => *i as f64, - Some(Edn::Double(f)) => f64::from(*f), - _ => panic!("unexpected in phrase '{name}': {:?}", args.get(0)), - } * beats as f64 * ppq as f64 / steps as f64) as usize; - for edn in args[1..].iter() { - match edn { - Edn::List(args) => if let ( - Some(Edn::Int(key)), - Some(Edn::Int(vel)), - ) = ( - args.get(0), - args.get(1), - ) { - let (key, vel) = ( - u7::from((*key as u8).min(127)), - u7::from((*vel as u8).min(127)), - ); - phrase.notes[time].push(MidiMessage::NoteOn { key, vel }) - } else { - panic!("unexpected list in phrase '{name}'") - }, - _ => panic!("unexpected in phrase '{name}': {edn:?}") - } - } - }, - _ => panic!("unexpected in phrase '{name}': {edn:?}"), - }); - phrase.name = name; - Ok(phrase) - } } diff --git a/crates/tek/src/setup.rs b/crates/tek/src/setup.rs index 669a3812..666479c5 100644 --- a/crates/tek/src/setup.rs +++ b/crates/tek/src/setup.rs @@ -34,7 +34,7 @@ render!(SetupModal |self, buf, area| { Ok(area) }); handle!(SetupModal |self, e| { - if let AppEvent::Input(::crossterm::event::Event::Key(KeyEvent { + if let AppEvent::Input(Event::Key(KeyEvent { code: KeyCode::Enter, .. })) = e { diff --git a/crates/tek_core/Cargo.toml b/crates/tek_core/Cargo.toml index 4a49e7b8..57540625 100644 --- a/crates/tek_core/Cargo.toml +++ b/crates/tek_core/Cargo.toml @@ -12,3 +12,4 @@ better-panic = "0.3.0" midly = "0.5" clap = { version = "4.5.4", features = [ "derive" ] } clojure-reader = "0.1.0" +once_cell = "1.19.0" diff --git a/crates/tek_core/src/lib.rs b/crates/tek_core/src/lib.rs index 875ffe19..c156a32e 100644 --- a/crates/tek_core/src/lib.rs +++ b/crates/tek_core/src/lib.rs @@ -8,6 +8,7 @@ pub use crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers}; pub use ratatui::prelude::{Rect, Style, Color, Buffer}; pub use ratatui::style::Stylize; pub use clojure_reader::{edn::{read, Edn}, error::Error as EdnError}; +pub use once_cell::sync::Lazy; pub(crate) use std::error::Error; pub(crate) use std::io::{stdout}; diff --git a/crates/tek_mixer/Cargo.toml b/crates/tek_mixer/Cargo.toml index bee666fd..1c993154 100644 --- a/crates/tek_mixer/Cargo.toml +++ b/crates/tek_mixer/Cargo.toml @@ -7,6 +7,11 @@ version = "0.1.0" tek_core = { path = "../tek_core" } tek_jack = { path = "../tek_jack" } tek_chain = { path = "../tek_chain" } +tek_sampler = { path = "../tek_sampler", optional = true } +tek_plugin = { path = "../tek_plugin", optional = true } + +[features] +standalone_devices = [ "tek_sampler", "tek_plugin" ] [lib] path = "src/lib.rs" diff --git a/crates/tek_mixer/src/mixer.rs b/crates/tek_mixer/src/mixer.rs index d01203c5..a317241b 100644 --- a/crates/tek_mixer/src/mixer.rs +++ b/crates/tek_mixer/src/mixer.rs @@ -23,18 +23,14 @@ impl Mixer { name: name.into(), selected_column: 0, selected_track: 1, - tracks: vec![ - MixerTrack::new(&client, 1, "Mono 1")?, - MixerTrack::new(&client, 1, "Mono 2")?, - MixerTrack::new(&client, 2, "Stereo 1")?, - MixerTrack::new(&client, 2, "Stereo 2")?, - MixerTrack::new(&client, 2, "Stereo 3")?, - MixerTrack::new(&client, 2, "Bus 1")?, - MixerTrack::new(&client, 2, "Bus 2")?, - MixerTrack::new(&client, 2, "Mix")?, - ], + tracks: vec![], }) } + pub fn add_track (&mut self, name: &str, channels: usize) -> Usually<&mut Self> { + let track = MixerTrack::new(name, channels)?; + self.tracks.push(track); + Ok(self) + } } fn process ( diff --git a/crates/tek_mixer/src/mixer_track.rs b/crates/tek_mixer/src/mixer_track.rs index 24f0a5a8..050d151f 100644 --- a/crates/tek_mixer/src/mixer_track.rs +++ b/crates/tek_mixer/src/mixer_track.rs @@ -1,94 +1,124 @@ use crate::*; use tek_core::edn; +#[cfg(feature = "standalone_devices")] +use tek_sampler::*; +#[cfg(feature = "standalone_devices")] +use tek_plugin::*; -/// TODO: A track in the mixer. (Integrate with [crate::model::Track]?) +/// A track in the mixer. pub struct MixerTrack { - pub name: String, - 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, + pub name: String, + pub ports: JackPorts, + pub devices: Vec, + //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 MixerTrack { - pub fn new (jack: &Client, channels: u8, name: &str) -> Usually { - let mut input_ports = vec![]; - let mut insert_ports = vec![]; - let mut return_ports = vec![]; - let mut output_ports = vec![]; - for channel in 1..=channels { - input_ports.push(jack.register_port(&format!("{name} [input {channel}]"), AudioIn::default())?); - output_ports.push(jack.register_port(&format!("{name} [out {channel}]"), AudioOut::default())?); - let insert_port = jack.register_port(&format!("{name} [pre {channel}]"), AudioOut::default())?; - let return_port = jack.register_port(&format!("{name} [insert {channel}]"), AudioIn::default())?; - jack.connect_ports(&insert_port, &return_port)?; - insert_ports.push(insert_port); - return_ports.push(return_port); - } - Ok(Self { - name: name.into(), - channels, - input_ports, - pre_gain_meter: 0.0, - gain: 0.0, - post_gain_meter: 0.0, - insert_ports, - return_ports, - post_insert_meter: 0.0, - level: 0.0, - pan: 0.0, - post_fader_meter: 0.0, - route: "---".into(), - output_ports, - }) - } -} -impl MixerTrack { - fn load_edn <'a, 'e> (app: &'a mut App, args: &[Edn<'e>]) -> Usually<&'a mut Self> { - let ppq = app.transport.ppq(); - let mut name = None; + const SYM_NAME: &'static str = ":name"; + const SYM_GAIN: &'static str = ":gain"; + const SYM_SAMPLER: &'static str = "sampler"; + const SYM_LV2: &'static str = "lv2"; + + pub fn from_edn <'a, 'e> (args: &[Edn<'e>]) -> Usually { let mut _gain = 0.0f64; + let mut track = MixerTrack::new("", 0)?; + #[allow(unused_mut)] let mut devices: Vec = vec![]; edn!(edn in args { Edn::Map(map) => { - if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { - name = Some(*n); + if let Some(Edn::Str(n)) = map.get(&Edn::Key(Self::SYM_NAME)) { + track.name = n.to_string(); } - if let Some(Edn::Double(g)) = map.get(&Edn::Key(":gain")) { - _gain = f64::from(*g) + if let Some(Edn::Double(g)) = map.get(&Edn::Key(Self::SYM_GAIN)) { + _gain = f64::from(*g); } }, Edn::List(args) => match args.get(0) { - Some(Edn::Symbol("sampler")) => { - devices.push(Sampler::load_edn(&args[1..])?) + // Add a sampler device to the track + Some(Edn::Symbol(Self::SYM_SAMPLER)) => { + #[cfg(feature = "standalone_devices")] + devices.push(Sampler::from_edn(&args[1..])?); + #[cfg(not(feature = "standalone_devices"))] + panic!( + "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"sampler\"", + &track.name, + args.get(0).unwrap() + ) }, - Some(Edn::Symbol("lv2")) => { - devices.push(LV2Plugin::load_edn(&args[1..])?) + // Add a LV2 plugin to the track. + Some(Edn::Symbol(Self::SYM_LV2)) => { + #[cfg(feature = "standalone_devices")] + devices.push(LV2Plugin::from_edn(&args[1..])?); + #[cfg(not(feature = "standalone_devices"))] + panic!( + "unsupported in track {}: {:?}; tek_mixer not compiled with feature \"plugin\"", + &track.name, + args.get(0).unwrap() + ) }, - None => panic!("empty list track {}", - name.unwrap_or("") - ), - _ => panic!("unexpected in track {}: {:?}", - name.unwrap_or(""), - args.get(0).unwrap() - ) + None => + panic!("empty list track {}", &track.name), + _ => + panic!("unexpected in track {}: {:?}", &track.name, args.get(0).unwrap()) }, _ => {} }); - let track = app.arranger.track_add(name)?; - for device in devices { track.add_device(device)?; } + for device in devices { + track.add_device(device); + } Ok(track) } + + pub fn new (name: &str, channels: usize) -> Usually { + Ok(Self { + name: name.into(), + ports: JackPorts::default(), + devices: vec![], + //channels, + //input_ports, + //pre_gain_meter: 0.0, + //gain: 0.0, + //post_gain_meter: 0.0, + //insert_ports, + //return_ports, + //post_insert_meter: 0.0, + //level: 0.0, + //pan: 0.0, + //post_fader_meter: 0.0, + //route: "---".into(), + //output_ports, + }) + //let mut input_ports = vec![]; + //let mut insert_ports = vec![]; + //let mut return_ports = vec![]; + //let mut output_ports = vec![]; + //for channel in 1..=channels { + //input_ports.push(jack.register_port(&format!("{name} [input {channel}]"), AudioIn::default())?); + //output_ports.push(jack.register_port(&format!("{name} [out {channel}]"), AudioOut::default())?); + //let insert_port = jack.register_port(&format!("{name} [pre {channel}]"), AudioOut::default())?; + //let return_port = jack.register_port(&format!("{name} [insert {channel}]"), AudioIn::default())?; + //jack.connect_ports(&insert_port, &return_port)?; + //insert_ports.push(insert_port); + //return_ports.push(return_port); + //} + } + + pub fn add_device (&mut self, device: JackDevice) { + self.devices.push(device); + } } //impl Input, bool> for Mixer { diff --git a/crates/tek_plugin/src/lv2.rs b/crates/tek_plugin/src/lv2.rs index 308bf3a2..f149f65e 100644 --- a/crates/tek_plugin/src/lv2.rs +++ b/crates/tek_plugin/src/lv2.rs @@ -1,5 +1,4 @@ use super::*; - use ::livi::{ World, Instance, @@ -9,33 +8,7 @@ use ::livi::{ Port, event::LV2AtomSequence, }; - -use ::winit::{ - application::ApplicationHandler, - event::WindowEvent, - event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, - window::{Window, WindowId}, - platform::x11::EventLoopBuilderExtX11 -}; - -use ::suil_rs::{self}; - -use std::thread::{spawn, JoinHandle}; - -impl Plugin { - pub fn lv2 (name: &str, path: &str) -> Usually { - let plugin = LV2Plugin::new(path)?; - jack_from_lv2(name, &plugin.plugin)? - .run(|ports|Box::new(Self { - name: name.into(), - path: Some(String::from(path)), - plugin: Some(PluginKind::LV2(plugin)), - selected: 0, - mapping: false, - ports - })) - } -} +use std::thread::JoinHandle; /// A LV2 plugin. pub struct LV2Plugin { @@ -49,6 +22,22 @@ pub struct LV2Plugin { } impl LV2Plugin { + pub fn from_edn <'e> (args: &[Edn<'e>]) -> Usually { + let mut name = String::new(); + let mut path = String::new(); + edn!(edn in args { + Edn::Map(map) => { + if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { + name = String::from(*n); + } + if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) { + path = String::from(*p); + } + }, + _ => panic!("unexpected in lv2 '{name}'"), + }); + Plugin::new_lv2(&name, &path) + } pub fn new (uri: &str) -> Usually { // Get 1st plugin at URI let world = World::with_load_bundle(&uri); @@ -84,62 +73,3 @@ impl LV2Plugin { }) } } - -pub fn run_lv2_ui (mut ui: LV2PluginUI) -> Usually> { - Ok(spawn(move||{ - let event_loop = EventLoop::builder().with_x11().with_any_thread(true).build().unwrap(); - event_loop.set_control_flow(ControlFlow::Wait); - event_loop.run_app(&mut ui).unwrap() - })) -} - -/// A LV2 plugin's X11 UI. -pub struct LV2PluginUI { - pub window: Option -} - -impl LV2PluginUI { - pub fn new () -> Usually { - Ok(Self { window: None }) - } -} - -impl ApplicationHandler for LV2PluginUI { - fn resumed (&mut self, event_loop: &ActiveEventLoop) { - self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap()); - } - fn window_event (&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { - match event { - WindowEvent::CloseRequested => { - self.window.as_ref().unwrap().set_visible(false); - event_loop.exit(); - }, - WindowEvent::RedrawRequested => { - self.window.as_ref().unwrap().request_redraw(); - } - _ => (), - } - } -} - -fn lv2_ui_instantiate (kind: &str) { - //let host = Suil -} - -pub fn jack_from_lv2 (name: &str, plugin: &::livi::Plugin) -> Usually { - let counts = plugin.port_counts(); - let mut jack = Jack::new(name)?; - for i in 0..counts.atom_sequence_inputs { - jack = jack.midi_in(&format!("midi-in-{i}")) - } - for i in 0..counts.atom_sequence_outputs { - jack = jack.midi_out(&format!("midi-out-{i}")); - } - for i in 0..counts.audio_inputs { - jack = jack.audio_in(&format!("audio-in-{i}")); - } - for i in 0..counts.audio_outputs { - jack = jack.audio_out(&format!("audio-out-{i}")); - } - Ok(jack) -} diff --git a/crates/tek_plugin/src/lv2_edn.rs b/crates/tek_plugin/src/lv2_edn.rs deleted file mode 100644 index 99eff76c..00000000 --- a/crates/tek_plugin/src/lv2_edn.rs +++ /dev/null @@ -1,20 +0,0 @@ -use crate::*; -impl LV2Plugin { - fn load_edn <'e> (args: &[Edn<'e>]) -> Usually { - let mut name = String::new(); - let mut path = String::new(); - edn!(edn in args { - Edn::Map(map) => { - if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { - name = String::from(*n); - } - if let Some(Edn::Str(p)) = map.get(&Edn::Key(":path")) { - path = String::from(*p); - } - }, - _ => panic!("unexpected in lv2 '{name}'"), - }); - Plugin::lv2(&name, &path) - } -} - diff --git a/crates/tek_plugin/src/lv2_gui.rs b/crates/tek_plugin/src/lv2_gui.rs index 4c70aa29..f096c048 100644 --- a/crates/tek_plugin/src/lv2_gui.rs +++ b/crates/tek_plugin/src/lv2_gui.rs @@ -1,4 +1,12 @@ - +use crate::*; +use std::thread::{spawn, JoinHandle}; +use ::winit::{ + application::ApplicationHandler, + event::WindowEvent, + event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, + window::{Window, WindowId}, + platform::x11::EventLoopBuilderExtX11 +}; //pub struct LV2PluginUI { //write: (), @@ -7,3 +15,62 @@ //features: (), //transfer: (), //} + +pub fn run_lv2_ui (mut ui: LV2PluginUI) -> Usually> { + Ok(spawn(move||{ + let event_loop = EventLoop::builder().with_x11().with_any_thread(true).build().unwrap(); + event_loop.set_control_flow(ControlFlow::Wait); + event_loop.run_app(&mut ui).unwrap() + })) +} + +/// A LV2 plugin's X11 UI. +pub struct LV2PluginUI { + pub window: Option +} + +impl LV2PluginUI { + pub fn new () -> Usually { + Ok(Self { window: None }) + } +} + +impl ApplicationHandler for LV2PluginUI { + fn resumed (&mut self, event_loop: &ActiveEventLoop) { + self.window = Some(event_loop.create_window(Window::default_attributes()).unwrap()); + } + fn window_event (&mut self, event_loop: &ActiveEventLoop, id: WindowId, event: WindowEvent) { + match event { + WindowEvent::CloseRequested => { + self.window.as_ref().unwrap().set_visible(false); + event_loop.exit(); + }, + WindowEvent::RedrawRequested => { + self.window.as_ref().unwrap().request_redraw(); + } + _ => (), + } + } +} + +fn lv2_ui_instantiate (kind: &str) { + //let host = Suil +} + +pub fn jack_from_lv2 (name: &str, plugin: &::livi::Plugin) -> Usually { + let counts = plugin.port_counts(); + let mut jack = Jack::new(name)?; + for i in 0..counts.atom_sequence_inputs { + jack = jack.midi_in(&format!("midi-in-{i}")) + } + for i in 0..counts.atom_sequence_outputs { + jack = jack.midi_out(&format!("midi-out-{i}")); + } + for i in 0..counts.audio_inputs { + jack = jack.audio_in(&format!("audio-in-{i}")); + } + for i in 0..counts.audio_outputs { + jack = jack.audio_out(&format!("audio-out-{i}")); + } + Ok(jack) +} diff --git a/crates/tek_plugin/src/plugin.rs b/crates/tek_plugin/src/plugin.rs index eb950e28..bc3803a2 100644 --- a/crates/tek_plugin/src/plugin.rs +++ b/crates/tek_plugin/src/plugin.rs @@ -23,6 +23,18 @@ pub enum PluginKind { } impl Plugin { + pub fn new_lv2 (name: &str, path: &str) -> Usually { + let plugin = LV2Plugin::new(path)?; + jack_from_lv2(name, &plugin.plugin)? + .run(|ports|Box::new(Self { + name: name.into(), + path: Some(String::from(path)), + plugin: Some(PluginKind::LV2(plugin)), + selected: 0, + mapping: false, + ports + })) + } /// Create a plugin host device. pub fn new (name: &str) -> Usually { Ok(Self { diff --git a/crates/tek_sampler/src/sample.rs b/crates/tek_sampler/src/sample.rs index fa4ca3ab..015f6178 100644 --- a/crates/tek_sampler/src/sample.rs +++ b/crates/tek_sampler/src/sample.rs @@ -11,6 +11,31 @@ pub struct Sample { } impl Sample { + pub fn from_edn <'e> (dir: &str, args: &[Edn<'e>]) -> Usually<(Option, Arc>)> { + let mut name = String::new(); + let mut file = String::new(); + let mut midi = None; + let mut start = 0usize; + edn!(edn in args { + Edn::Map(map) => { + if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { + name = String::from(*n); + } + if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) { + file = String::from(*f); + } + if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) { + start = *i as usize; + } + if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) { + midi = Some(u7::from(*m as u8)); + } + }, + _ => panic!("unexpected in sample {name}"), + }); + let (end, data) = read_sample_data(&format!("{dir}/{file}"))?; + Ok((midi, Arc::new(RwLock::new(Self::new(&name, start, end, data))))) + } pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Self { Self { name: name.to_string(), start, end, channels, rate: None } } diff --git a/crates/tek_sampler/src/sampler.rs b/crates/tek_sampler/src/sampler.rs index 7ed25c30..73a7dd64 100644 --- a/crates/tek_sampler/src/sampler.rs +++ b/crates/tek_sampler/src/sampler.rs @@ -98,8 +98,36 @@ pub const KEYMAP_SAMPLER: &'static [KeyBinding] = keymap!(Sampler { }], }); - impl Sampler { + pub fn from_edn <'e> (args: &[Edn<'e>]) -> Usually { + let mut name = String::new(); + let mut dir = String::new(); + let mut samples = BTreeMap::new(); + edn!(edn in args { + Edn::Map(map) => { + if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { + name = String::from(*n); + } + if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) { + dir = String::from(*n); + } + }, + Edn::List(args) => match args.get(0) { + Some(Edn::Symbol("sample")) => { + let (midi, sample) = Sample::from_edn(&dir, &args[1..])?; + if let Some(midi) = midi { + samples.insert(midi, sample); + } else { + panic!("sample without midi binding: {}", sample.read().unwrap().name); + } + }, + _ => panic!("unexpected in sampler {name}: {args:?}") + }, + _ => panic!("unexpected in sampler {name}: {edn:?}") + }); + Self::new(&name, Some(samples)) + } + pub fn new (name: &str, mapped: Option>>>) -> Usually { Jack::new(name)? .midi_in("midi") diff --git a/crates/tek_sampler/src/sampler_edn.rs b/crates/tek_sampler/src/sampler_edn.rs index d6501292..190062c9 100644 --- a/crates/tek_sampler/src/sampler_edn.rs +++ b/crates/tek_sampler/src/sampler_edn.rs @@ -1,60 +1,7 @@ use crate::*; impl Sampler { - fn load_edn <'e> (args: &[Edn<'e>]) -> Usually { - let mut name = String::new(); - let mut dir = String::new(); - let mut samples = BTreeMap::new(); - edn!(edn in args { - Edn::Map(map) => { - if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { - name = String::from(*n); - } - if let Some(Edn::Str(n)) = map.get(&Edn::Key(":dir")) { - dir = String::from(*n); - } - }, - Edn::List(args) => match args.get(0) { - Some(Edn::Symbol("sample")) => { - let (midi, sample) = Sample::load_edn(&dir, &args[1..])?; - if let Some(midi) = midi { - samples.insert(midi, sample); - } else { - panic!("sample without midi binding: {}", sample.read().unwrap().name); - } - }, - _ => panic!("unexpected in sampler {name}: {args:?}") - }, - _ => panic!("unexpected in sampler {name}: {edn:?}") - }); - Self::new(&name, Some(samples)) - } } impl Sample { - fn load_edn <'e> (dir: &str, args: &[Edn<'e>]) -> Usually<(Option, Arc>)> { - let mut name = String::new(); - let mut file = String::new(); - let mut midi = None; - let mut start = 0usize; - edn!(edn in args { - Edn::Map(map) => { - if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { - name = String::from(*n); - } - if let Some(Edn::Str(f)) = map.get(&Edn::Key(":file")) { - file = String::from(*f); - } - if let Some(Edn::Int(i)) = map.get(&Edn::Key(":start")) { - start = *i as usize; - } - if let Some(Edn::Int(m)) = map.get(&Edn::Key(":midi")) { - midi = Some(u7::from(*m as u8)); - } - }, - _ => panic!("unexpected in sample {name}"), - }); - let (end, data) = read_sample_data(&format!("{dir}/{file}"))?; - Ok((midi, Arc::new(RwLock::new(Self::new(&name, start, end, data))))) - } } diff --git a/crates/tek_sequencer/src/phrase_edn.rs b/crates/tek_sequencer/src/phrase_edn.rs index 1e798e92..111dbeca 100644 --- a/crates/tek_sequencer/src/phrase_edn.rs +++ b/crates/tek_sequencer/src/phrase_edn.rs @@ -56,3 +56,60 @@ impl Phrase { Ok(phrase) } } + +//impl Phrase { + //fn from_edn <'e> (ppq: usize, args: &[Edn<'e>]) -> Usually { + //let mut phrase = Self::default(); + //let mut name = String::new(); + //let mut beats = 0usize; + //let mut steps = 0usize; + //edn!(edn in args { + //Edn::Map(map) => { + //if let Some(Edn::Str(n)) = map.get(&Edn::Key(":name")) { + //name = String::from(*n); + //} + //if let Some(Edn::Int(b)) = map.get(&Edn::Key(":beats")) { + //beats = *b as usize; + //phrase.length = ppq * beats; + //for _ in phrase.notes.len()..phrase.length { + //phrase.notes.push(Vec::with_capacity(16)) + //} + //} + //if let Some(Edn::Int(s)) = map.get(&Edn::Key(":steps")) { + //steps = *s as usize; + //} + //}, + //Edn::List(args) => { + //let time = (match args.get(0) { + //Some(Edn::Key(text)) => text[1..].parse::()?, + //Some(Edn::Int(i)) => *i as f64, + //Some(Edn::Double(f)) => f64::from(*f), + //_ => panic!("unexpected in phrase '{name}': {:?}", args.get(0)), + //} * beats as f64 * ppq as f64 / steps as f64) as usize; + //for edn in args[1..].iter() { + //match edn { + //Edn::List(args) => if let ( + //Some(Edn::Int(key)), + //Some(Edn::Int(vel)), + //) = ( + //args.get(0), + //args.get(1), + //) { + //let (key, vel) = ( + //u7::from((*key as u8).min(127)), + //u7::from((*vel as u8).min(127)), + //); + //phrase.notes[time].push(MidiMessage::NoteOn { key, vel }) + //} else { + //panic!("unexpected list in phrase '{name}'") + //}, + //_ => panic!("unexpected in phrase '{name}': {edn:?}") + //} + //} + //}, + //_ => panic!("unexpected in phrase '{name}': {edn:?}"), + //}); + //phrase.name = name; + //Ok(phrase) + //} +//}