diff --git a/Cargo.lock b/Cargo.lock index deeda854..73e94d91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -127,6 +127,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +[[package]] +name = "bytemuck" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b236fc92302c97ed75b38da1f4917b5cdda4984745740f153a5d3059e48d725e" + [[package]] name = "cassowary" version = "0.3.0" @@ -889,6 +895,27 @@ dependencies = [ "ratatui", "toml", "vst", + "wavers", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.60", ] [[package]] @@ -996,6 +1023,18 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wavers" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab501e9e5b13446d3a6e846de0c190c96b85ca3eced3bd00460edc67654500c8" +dependencies = [ + "bytemuck", + "num-traits", + "paste", + "thiserror", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index 7d1df00c..44bbbb1f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,3 +17,4 @@ vst = "0.4.0" #vst3 = "0.1.0" livi = "0.7.4" #atomic_enum = "0.3.0" +wavers = "1.4.3" diff --git a/src/device/chain/sampler.rs b/src/device/chain/sampler.rs index cfc8c463..66ab3705 100644 --- a/src/device/chain/sampler.rs +++ b/src/device/chain/sampler.rs @@ -22,8 +22,8 @@ pub struct Sample { pub channels: Vec>, } impl Sample { - fn new (name: &str) -> Arc { - Arc::new(Self { name: name.to_string(), start: 0, end: 0, channels: vec![] }) + pub fn new (name: &str, start: usize, end: usize, channels: Vec>) -> Arc { + Arc::new(Self { name: name.to_string(), start, end, channels }) } fn play (self: &Arc) -> Voice { Voice { sample: self.clone(), position: self.start } @@ -64,7 +64,27 @@ impl Sampler { } pub fn process (&mut self, _: &Client, scope: &ProcessScope) -> Control { + // Output buffer. This will be copied to the audio outs. + let channel_count = self.audio_outs.len(); + let mut mixed = vec![vec![0.0;scope.n_frames() as usize];channel_count]; + // emit currently playing voices + for voice in self.voices.iter_mut() { + let chunk = voice.chunk(scope.n_frames() as usize); + for (i, channel) in chunk.iter().enumerate() { + let buffer = &mut mixed[i % channel_count]; + for (i, sample) in channel.iter().enumerate() { + buffer[i] += sample; + } + } + } + for (i, port) in self.audio_outs.iter_mut().enumerate() { + let buffer = &mixed[i]; + for (i, value) in port.as_mut_slice(scope).iter_mut().enumerate() { + *value = *buffer.get(i).unwrap_or(&0.0); + } + } + Control::Continue // process midi in // add new voices // emit new voices starting from midi event frames @@ -96,7 +116,6 @@ impl Sampler { //} //} //} - Control::Continue } fn load_sample (&mut self, _path: &str) {} @@ -127,7 +146,7 @@ pub fn render (state: &Sampler, buf: &mut Buffer, Rect { x, y, height, .. }: Rec { let width = 40; let style = Style::default().gray(); - format!(" {} ", state.name).blit(buf, x+1, y, Some(style.white().bold().not_dim())); + format!(" {} ({})", state.name, state.voices.len()).blit(buf, x+1, y, Some(style.white().bold().not_dim())); for (i, (note, sample)) in state.samples.iter().enumerate() { let style = if i == state.cursor.0 { Style::default().green() @@ -215,8 +234,10 @@ fn cursor_down (state: &mut Sampler) -> Usually { Ok(true) } fn select (state: &mut Sampler) -> Usually { - if let Some(sample) = state.samples.get(&u7::from_int_lossy(state.cursor.0 as u8)) { - state.voices.push(sample.play()) + for (i, sample) in state.samples.values().enumerate() { + if i == state.cursor.0 { + state.voices.push(sample.play()) + } } Ok(true) } diff --git a/src/device/launcher/mod.rs b/src/device/launcher/mod.rs index 08f4ef58..d1c386b2 100644 --- a/src/device/launcher/mod.rs +++ b/src/device/launcher/mod.rs @@ -53,14 +53,14 @@ impl Launcher { let ppq = timebase.ppq() as u32; DynamicDevice::new(render, handle, process, Self { name: name.into(), - view: LauncherView::Tracks, + view: LauncherView::Chains, playing: transport.query_state()?, transport, timebase: timebase.clone(), monitoring: true, recording: false, overdub: true, - cursor: (0, 0), + cursor: (1, 1), position: 0, scenes: scenes.unwrap_or_else(||vec![Scene::new(&"Scene 1", &[None])]), tracks: if let Some(tracks) = tracks { tracks } else { vec![ diff --git a/src/main.rs b/src/main.rs index e66a8f8c..32d86b6b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,25 @@ pub mod time; use crate::prelude::*; +macro_rules! sample { + ($note:expr, $name:expr, $src:expr) => { + { + 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); + } + (u7::from_int_lossy($note).into(), Sample::new($name, 0, end, data).into()) + } + }; +} + fn main () -> Result<(), Box> { let _cli = cli::Cli::parse(); let xdg = microxdg::XdgApp::new("tek")?; @@ -35,24 +54,8 @@ fn main () -> Result<(), Box> { Track::new("Kick", &timebase, Some(vec![ //Plugin::lv2("Kick/ChowKick", "file:///home/user/.lv2/ChowKick.lv2", &[1, 1, 0, 2])?.boxed(), Sampler::new("Sampler", Some(BTreeMap::from([ - (u7::from_int_lossy(35), Sample { - name: "Kick 1".into(), start: 0, end: 100000, channels: vec![], - }.into()), - (u7::from_int_lossy(36).into(), Sample { - name: "Kick 2".into(), start: 0, end: 100000, channels: vec![], - }.into()), - (u7::from_int_lossy(38).into(), Sample { - name: "Snare 1".into(), start: 0, end: 100000, channels: vec![], - }.into()), - (u7::from_int_lossy(40).into(), Sample { - name: "Snare 2".into(), start: 50000, end: 100000, channels: vec![], - }.into()), - (u7::from_int_lossy(42).into(), Sample { - name: "HH Closed".into(), start: 0, end: 50000, channels: vec![], - }.into()), - (u7::from_int_lossy(46).into(), Sample { - name: "HH Open".into(), start: 0, end: 25000, channels: vec![] - }.into()), + sample!(36, "Kick", "/home/user/Lab/Music/pak/kik.wav"), + sample!(40, "Snare", "/home/user/Lab/Music/pak/sna.wav"), ])))?.boxed(), ]), Some(vec![ Phrase::new("HelloKick", ppq * 4, Some(BTreeMap::from([