summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGraham Northup <grissess@nexusg.org>2017-09-18 23:12:14 -0400
committerGraham Northup <grissess@nexusg.org>2017-09-18 23:12:14 -0400
commit85925d69e08455bd91d32a27cd3690c9cb634a6b (patch)
tree1023c044a3350aadb4eb06bf73e209f81f21f8ae
Initial work on modular synthesis
-rw-r--r--.gitignore3
-rw-r--r--Cargo.toml6
-rw-r--r--src/lib.rs11
-rw-r--r--src/main.rs23
-rw-r--r--src/synth/math.rs41
-rw-r--r--src/synth/mod.rs155
-rw-r--r--src/synth/param.rs14
-rw-r--r--src/synth/sine.rs24
-rw-r--r--src/types.rs1
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(&params);
+ 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;