diff options
author | Graham Northup <grissess@nexusg.org> | 2017-09-18 23:12:14 -0400 |
---|---|---|
committer | Graham Northup <grissess@nexusg.org> | 2017-09-18 23:12:14 -0400 |
commit | 85925d69e08455bd91d32a27cd3690c9cb634a6b (patch) | |
tree | 1023c044a3350aadb4eb06bf73e209f81f21f8ae |
Initial work on modular synthesis
-rw-r--r-- | .gitignore | 3 | ||||
-rw-r--r-- | Cargo.toml | 6 | ||||
-rw-r--r-- | src/lib.rs | 11 | ||||
-rw-r--r-- | src/main.rs | 23 | ||||
-rw-r--r-- | src/synth/math.rs | 41 | ||||
-rw-r--r-- | src/synth/mod.rs | 155 | ||||
-rw-r--r-- | src/synth/param.rs | 14 | ||||
-rw-r--r-- | src/synth/sine.rs | 24 | ||||
-rw-r--r-- | src/types.rs | 1 |
9 files changed, 278 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4308d82 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +target/ +**/*.rs.bk +Cargo.lock diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..2da6bc8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "synfone" +version = "0.1.0" +authors = ["Graham Northup <grissess@nexusg.org>"] + +[dependencies] diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..9a1eaff --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,11 @@ +pub mod types; +pub use types::*; + +pub mod synth; + +#[cfg(test)] +mod tests { + #[test] + fn it_works() { + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..6db2bcf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,23 @@ +use std::io; +use std::io::*; + +extern crate synfone; +use synfone::synth::*; + +const FRAMES: usize = 44100 * 2; + +fn main() { + let mut params = Default::default(); + + let mut freq: GenBox = Box::new(Param { name: "freq".to_string(), default: 440.0, buf: SampleBuffer::new(1) }); + let mut sg: GenBox = Box::new(Sine { freq: freq, phase: 0.0, buf: SampleBuffer::new(params.env.default_buffer_size) }); + + let mut counter = 0; + let mut out = io::stderr(); + + while counter < FRAMES { + let buf = sg.eval(¶ms); + out.write_all(buf.bytes()); + counter += buf.len(); + } +} diff --git a/src/synth/math.rs b/src/synth/math.rs new file mode 100644 index 0000000..b016103 --- /dev/null +++ b/src/synth/math.rs @@ -0,0 +1,41 @@ +use super::*; + +pub struct Add { + terms: Vec<GenBox>, + buf: SampleBuffer, +} + +impl Generator for Add { + fn eval<'a>(&'a mut self, params: &Parameters) -> &'a SampleBuffer { + if self.terms.is_empty() { + self.buf.zero(); + } else { + let (first, next) = self.terms.split_at_mut(1); + self.buf.update_from(first[0].eval(params)); + for term in next { + self.buf.sum_into(term.eval(params)); + } + } + &self.buf + } +} + +pub struct Mul { + factors: Vec<GenBox>, + buf: SampleBuffer, +} + +impl Generator for Mul { + fn eval<'a>(&'a mut self, params: &Parameters) -> &'a SampleBuffer { + if self.factors.is_empty() { + self.buf.zero(); + } else { + let (first, next) = self.factors.split_at_mut(1); + self.buf.update_from(first[0].eval(params)); + for factor in next { + self.buf.mul_into(factor.eval(params)); + } + } + &self.buf + } +} diff --git a/src/synth/mod.rs b/src/synth/mod.rs new file mode 100644 index 0000000..d0c837a --- /dev/null +++ b/src/synth/mod.rs @@ -0,0 +1,155 @@ +use std::{iter, cmp, slice, mem}; +use std::ops::{Index, IndexMut}; +use std::collections::HashMap; +use super::*; + +#[derive(PartialEq,Eq,Clone,Copy)] +pub enum Rate { + Sample, + Control, +} + +pub struct SampleBuffer { + pub samples: Vec<Sample>, + pub rate: Rate, +} + +pub struct Environment { + pub sample_rate: f32, + pub default_buffer_size: usize, +} + +impl Default for Environment { + fn default() -> Environment { + Environment { + sample_rate: 44100.0, + default_buffer_size: 64, + } + } +} + +pub struct Parameters { + pub env: Environment, + pub vars: HashMap<String, f32>, +} + +impl Default for Parameters { + fn default() -> Parameters { + Parameters { + env: Default::default(), + vars: HashMap::new(), + } + } +} + +impl SampleBuffer { + pub fn new(sz: usize) -> SampleBuffer { + let mut samples = Vec::with_capacity(sz); + samples.extend(iter::repeat(0 as Sample).take(sz)); + SampleBuffer { + samples: samples, + rate: Rate::Sample, + } + } + + pub fn len(&self) -> usize { + self.samples.len() + } + + pub fn first(&self) -> Sample { + *self.samples.first().unwrap() + } + + pub fn set(&mut self, val: Sample) { + self.samples[0] = val; + self.rate = Rate::Control; + } + + pub fn update_from(&mut self, other: &SampleBuffer) { + self.rate = other.rate; + match self.rate { + Rate::Sample => { + for i in 0..cmp::min(self.len(), other.len()) { + self.samples[i] = other.samples[i]; + } + }, + Rate::Control => { + self.samples[0] = other.samples[0]; + }, + } + } + + pub fn sum_into(&mut self, other: &SampleBuffer) { + match self.rate { + Rate::Sample => { + for i in 0..cmp::min(self.len(), other.len()) { + self.samples[i] += match other.rate { + Rate::Sample => other.samples[i], + Rate::Control => other.samples[0], + }; + } + }, + Rate::Control => { + self.samples[0] += other.samples[0]; + }, + } + } + + pub fn mul_into(&mut self, other: &SampleBuffer) { + match self.rate { + Rate::Sample => { + for i in 0..cmp::min(self.len(), other.len()) { + self.samples[i] *= match other.rate { + Rate::Sample => other.samples[i], + Rate::Control => other.samples[0], + }; + } + }, + Rate::Control => { + self.samples[0] *= other.samples[0]; + }, + } + } + + pub fn zero(&mut self) { + for i in 0..self.len() { + self.samples[i] = 0.0; + } + } + + pub fn bytes<'a>(&'a self) -> &'a [u8] { + unsafe { + slice::from_raw_parts( + self.samples.as_ptr() as *const u8, + self.samples.len() * mem::size_of::<Sample>(), + ) + } + } +} + +impl Index<usize> for SampleBuffer { + type Output = Sample; + fn index(&self, idx: usize) -> &Sample { &self.samples[idx] } +} + +impl IndexMut<usize> for SampleBuffer { + fn index_mut(&mut self, idx: usize) -> &mut Sample { &mut self.samples[idx] } +} + +pub trait Generator { + fn eval<'a>(&'a mut self, params: &Parameters) -> &'a SampleBuffer; +} + +pub type GenBox = Box<Generator>; + +pub mod param; +pub use self::param::Param; +pub mod math; +pub use self::math::{Add, Mul}; +pub mod sine; +pub use self::sine::Sine; +//pub mod saw; +//pub use saw::Saw; +//pub mod triangle; +//pub use triangle::Triangle; + diff --git a/src/synth/param.rs b/src/synth/param.rs new file mode 100644 index 0000000..3177ffd --- /dev/null +++ b/src/synth/param.rs @@ -0,0 +1,14 @@ +use super::*; + +pub struct Param { + name: String, + default: Sample, + buf: SampleBuffer, +} + +impl Generator for Param { + fn eval<'a>(&'a mut self, params: &Parameters) -> &'a SampleBuffer { + self.buf.set(*params.vars.get(&self.name).unwrap_or(&self.default)); + &self.buf + } +} diff --git a/src/synth/sine.rs b/src/synth/sine.rs new file mode 100644 index 0000000..3ee4857 --- /dev/null +++ b/src/synth/sine.rs @@ -0,0 +1,24 @@ +use std::f32::consts::PI; +use super::*; + +const TAU: f32 = 2f32 * PI; + +pub struct Sine { + freq: GenBox, + phase: f32, + buf: SampleBuffer, +} + +impl Generator for Sine { + fn eval<'a>(&'a mut self, params: &Parameters) -> &'a SampleBuffer { + self.buf.rate = Rate::Control; + + let pvel = TAU * self.freq.eval(params).first() / params.env.sample_rate; + for i in 0..self.buf.len() { + self.buf[i] = (self.phase + pvel * (i as f32)).sin() + } + + self.phase = (self.phase + pvel * (self.buf.len() as f32)) % TAU; + &self.buf + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..b9ff15f --- /dev/null +++ b/src/types.rs @@ -0,0 +1 @@ +pub type Sample = f32; |