From 41dc5b567169a0d4502325b8acfa8fe7d0f87b20 Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Tue, 18 May 2021 21:20:55 +0200 Subject: [PATCH] implemented a very basic sample loader for future work on a sample player node --- Cargo.toml | 2 +- src/lib.rs | 2 + src/sample_lib.rs | 118 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 src/sample_lib.rs diff --git a/Cargo.toml b/Cargo.toml index 5da9376..bde28e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,11 +17,11 @@ serde_json = "1.0" ringbuf = "0.2.2" triple_buffer = "5.0.6" hexotk = { optional = true, git = "https://github.com/WeirdConstructor/HexoTK.git" } +hound = "3.4.0" [dev-dependencies] microfft = "0.3.1" num-complex = "0.2" -hound = "3.4.0" jack = "0.6.6" [lib] diff --git a/src/lib.rs b/src/lib.rs index 966127f..446b21c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -248,6 +248,7 @@ pub mod matrix; pub mod cell_dir; pub mod monitor; pub mod matrix_repr; +pub mod sample_lib; mod util; pub use nodes::{new_node_engine, NodeConfigurator, NodeExecutor}; @@ -256,6 +257,7 @@ pub use matrix::{Matrix, Cell}; pub use dsp::{NodeId, SAtom}; pub use matrix_repr::load_patch_from_file; pub use matrix_repr::save_patch_to_file; +pub use sample_lib::{SampleLibrary, SampleLoadError}; pub struct Context<'a, 'b, 'c, 'd> { pub nframes: usize, diff --git a/src/sample_lib.rs b/src/sample_lib.rs new file mode 100644 index 0000000..b5267f8 --- /dev/null +++ b/src/sample_lib.rs @@ -0,0 +1,118 @@ +// Copyright (c) 2021 Weird Constructor +// This is a part of HexoDSP. Released under (A)GPLv3 or any later. +// See README.md and COPYING for details. + +use crate::dsp::SAtom; + +use hound; +use std::collections::HashMap; + +#[derive(Debug)] +pub enum SampleLoadError { + LoadError(hound::Error), + UnsupportedFormat, +} + +impl From for SampleLoadError { + fn from(err: hound::Error) -> Self { + SampleLoadError::LoadError(err) + } +} + +/// Loads and stores samples, for use as SAtom parameters for +/// nodes. +pub struct SampleLibrary { + loaded_samples: HashMap, +} + +impl SampleLibrary { + pub fn new() -> Self { + Self { + loaded_samples: HashMap::new(), + } + } + + /// Synchronous blocking loading of a sample from `path`. + /// Returns an SAtom reference that you can clone and send directly + /// to the sampling node of your choice. + /// + /// The maximum length of the sample is `44100 * 10` samples, which + /// is the equivalent of roughly 1.7 MB. + /// + /// Keep in mind that blocking on I/O in the UI might not be desireable. + pub fn load<'a>(&'a mut self, path: &str) -> Result<&'a SAtom, SampleLoadError> { + if self.loaded_samples.get(path).is_some() { + return Ok(self.loaded_samples.get(path).unwrap()); + } + + let mut rd = + match hound::WavReader::open(path) { + Err(e) => return Err(SampleLoadError::LoadError(e)), + Ok(rd) => rd, + }; + + let channels = rd.spec().channels as usize; + + let mut v = vec![]; + v.push(rd.spec().sample_rate as f32); + + match rd.spec().sample_format { + hound::SampleFormat::Float => { + for s in rd.samples::().step_by(channels) { + v.push(s?); + } + }, + hound::SampleFormat::Int => { + for s in rd.samples::().step_by(channels) { + v.push(s? as f32 / (i16::MAX as f32)); + } + }, + }; + + let atom = SAtom::audio(path, std::sync::Arc::new(v)); + + self.loaded_samples.insert(path.to_string(), atom); + Ok(self.loaded_samples.get(path).unwrap()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn save_wav(name: &str, buf: &[f32]) { + let spec = hound::WavSpec { + channels: 1, + sample_rate: 44100, + bits_per_sample: 16, + sample_format: hound::SampleFormat::Int, + }; + + let mut writer = hound::WavWriter::create(name, spec).unwrap(); + for s in buf.iter() { + let amp = i16::MAX as f32; + writer.write_sample((amp * s) as i16).unwrap(); + } + } + + #[test] + fn check_sample_lib() { + let mut sl = SampleLibrary::new(); + + save_wav("check_sample_lib_test.wav", &[0.1, -1.0, 1.0, -0.1]); + + let sat = sl.load("check_sample_lib_test.wav").unwrap(); + + //d// println!("sa: {:?}", sat); + + if let SAtom::AudioSample((_n, Some(v))) = sat { + assert_eq!(v[0], 44100.0); + assert_eq!((v[1] * 1000.0).round() as i32, 100); + assert_eq!((v[2] * 1000.0).round() as i32, -1000); + assert_eq!((v[3] * 1000.0).round() as i32, 1000); + assert_eq!((v[4] * 1000.0).round() as i32, -100); + } else { + assert!(false); + } + } +}