Skip to content

Commit

Permalink
Extract magnetron crate
Browse files Browse the repository at this point in the history
  • Loading branch information
Woyten committed Jun 26, 2022
1 parent 4b3b4e6 commit 28a845b
Show file tree
Hide file tree
Showing 28 changed files with 720 additions and 665 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"antiarcheotonic",
"antidiatonic",
"archeotonic",
"automations",
"backends",
"bedoginning",
"bindgen",
Expand Down
8 changes: 8 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ readme = "README.md"
keywords = ["microtonal", "midi", "scales", "synthesizer", "tuning"]
license = "MIT"
edition = "2021"
rust-version = "1.56"
rust-version = "1.61"

[dependencies]

[dev-dependencies]
assert_approx_eq = "1.1.0"

[workspace]
members = ["fluid-xenth", "microwave", "tune-cli", "tune-web"]
members = ["fluid-xenth", "magnetron", "microwave", "tune-cli", "tune-web"]
2 changes: 1 addition & 1 deletion fluid-xenth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ keywords = ["fluid", "microtonal", "soundfont", "synthesizer", "tuning"]
categories = ["multimedia", "multimedia::audio"]
license = "MIT"
edition = "2021"
rust-version = "1.56"
rust-version = "1.61"

[features]
sf3 = ["fluidlite/with-sf3"]
Expand Down
2 changes: 1 addition & 1 deletion fluid-xenth/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Stop making music with notes. Use pitches.

# Overview

`fluid-xenth` is a microtonal wrapper around [FluidLite](https://crates.io/crates/fluidlite). It uses the JIT live-retuning concept implemented in [tune](https://github.com/Woyten/tune) to enable arbitrary-pitch playback.
`fluid-xenth` is a microtonal wrapper around [FluidLite](https://crates.io/crates/fluidlite). It uses the AOT / JIT live-retuning concepts implemented in [tune](https://github.com/Woyten/tune) to enable arbitrary-pitch playback.

# Getting Started

Expand Down
17 changes: 17 additions & 0 deletions magnetron/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "magnetron"
version = "0.1.0"
authors = ["Woyten <woyten.tielesch@online.de>"]
description = "Create your own modular microtonal synthesizer from reusable building blocks."
repository = "https://github.com/Woyten/tune/tree/master/magnetron"
readme = "README.md"
keywords = ["effects", "microtonal", "modular", "sound", "synthesizer"]
categories = ["multimedia", "multimedia::audio"]
license = "MIT"
edition = "2021"
rust-version = "1.61"

[dependencies]

[dev-dependencies]
assert_approx_eq = "1.1.0"
12 changes: 12 additions & 0 deletions magnetron/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Create your own modular microtonal synthesizer from reusable building blocks.

# Resources

- [Changelog](https://github.com/Woyten/tune/releases)
- [API documentation](https://docs.rs/magnetron)

# Overview

`magnetron` will contain reusable and tested core components of the microtonal synthesizer [`microwave`](https://github.com/Woyten/tune/tree/master/microwave).

So far, `magnetron` provides a very basic architecture for creating custom audio processors and automations. Over time, the design will solidify and predefined audio processors and automations will be added.
73 changes: 73 additions & 0 deletions magnetron/src/automation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::marker::PhantomData;

use crate::{spec::Spec, waveform::WaveformState};

pub struct Automation<S> {
pub(crate) automation_fn: Box<dyn FnMut(&AutomationContext<S>) -> f64 + Send>,
}

pub struct AutomationContext<'a, S> {
pub render_window_secs: f64,
pub state: &'a WaveformState,
pub storage: &'a S,
}

impl<'a, S> AutomationContext<'a, S> {
pub fn read<V: AutomatedValue<Storage = S>>(&self, value: &mut V) -> V::Value {
value.use_context(self)
}
}

impl<S> AutomatedValue for Automation<S> {
type Storage = S;
type Value = f64;

fn use_context(&mut self, context: &AutomationContext<Self::Storage>) -> Self::Value {
(self.automation_fn)(context)
}
}

pub trait AutomatedValue {
type Storage;
type Value;

fn use_context(&mut self, context: &AutomationContext<Self::Storage>) -> Self::Value;
}

impl<A: AutomatedValue<Value = f64>> AutomatedValue for PhantomData<A> {
type Storage = A::Storage;
type Value = ();

fn use_context(&mut self, _context: &AutomationContext<Self::Storage>) -> Self::Value {}
}

impl<A1: AutomatedValue, A2: AutomatedValue<Storage = A1::Storage>> AutomatedValue for (A1, A2) {
type Storage = A1::Storage;
type Value = (A1::Value, A2::Value);

fn use_context(&mut self, context: &AutomationContext<Self::Storage>) -> Self::Value {
(context.read(&mut self.0), context.read(&mut self.1))
}
}

impl<
A1: AutomatedValue,
A2: AutomatedValue<Storage = A1::Storage>,
A3: AutomatedValue<Storage = A1::Storage>,
> AutomatedValue for (A1, A2, A3)
{
type Storage = A1::Storage;
type Value = (A1::Value, A2::Value, A3::Value);

fn use_context(&mut self, context: &AutomationContext<Self::Storage>) -> Self::Value {
(
context.read(&mut self.0),
context.read(&mut self.1),
context.read(&mut self.2),
)
}
}

pub trait AutomationSpec: Spec<Created = Automation<Self::Storage>> {
type Storage: 'static;
}
158 changes: 158 additions & 0 deletions magnetron/src/buffer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
use std::{iter, mem, sync::Arc};

pub struct BufferWriter {
pub(crate) sample_width_secs: f64,
pub(crate) readable: ReadableBuffers,
pub(crate) writeable: WaveformBuffer,
}

impl BufferWriter {
pub fn sample_width_secs(&self) -> f64 {
self.sample_width_secs
}

pub fn read_0_and_write(
&mut self,
out_buffer: OutBuffer,
out_level: f64,
mut f: impl FnMut() -> f64,
) {
self.read_n_and_write(out_buffer, |_, write_access| {
write_access.write(iter::repeat_with(|| f() * out_level))
});
}

pub fn read_1_and_write(
&mut self,
in_buffer: InBuffer,
out_buffer: OutBuffer,
out_level: f64,
mut f: impl FnMut(f64) -> f64,
) {
self.read_n_and_write(out_buffer, |read_access, write_access| {
write_access.write(
read_access
.read(in_buffer)
.iter()
.map(|&src| f(src) * out_level),
)
});
}

pub fn read_2_and_write(
&mut self,
in_buffers: (InBuffer, InBuffer),
out_buffer: OutBuffer,
out_level: f64,
mut f: impl FnMut(f64, f64) -> f64,
) {
self.read_n_and_write(out_buffer, |read_access, write_access| {
write_access.write(
read_access
.read(in_buffers.0)
.iter()
.zip(read_access.read(in_buffers.1))
.map(|(&src_0, &src_1)| f(src_0, src_1) * out_level),
)
});
}

fn read_n_and_write(
&mut self,
out_buffer: OutBuffer,
mut rw_access_fn: impl FnMut(&ReadableBuffers, &mut WaveformBuffer),
) {
self.readable.swap(out_buffer, &mut self.writeable);
rw_access_fn(&self.readable, &mut self.writeable);
self.readable.swap(out_buffer, &mut self.writeable);
}
}

#[derive(Copy, Clone, Debug)]
pub enum InBuffer {
Buffer(usize),
AudioIn,
}

#[derive(Copy, Clone, Debug)]
pub enum OutBuffer {
Buffer(usize),
AudioOut,
}

pub(crate) struct ReadableBuffers {
pub audio_in: WaveformBuffer,
pub intermediate: Vec<WaveformBuffer>,
pub audio_out: WaveformBuffer,
pub mix: WaveformBuffer,
}

impl ReadableBuffers {
fn swap(&mut self, buffer_a: OutBuffer, buffer_b: &mut WaveformBuffer) {
let buffer_a = match buffer_a {
OutBuffer::Buffer(index) => self.intermediate.get_mut(index).unwrap_or_else(|| {
panic!(
"Index {} out of range. Please allocate more waveform buffers.",
index
)
}),
OutBuffer::AudioOut => &mut self.audio_out,
};
mem::swap(buffer_a, buffer_b);
}

fn read(&self, in_buffer: InBuffer) -> &[f64] {
match in_buffer {
InBuffer::Buffer(index) => &self.intermediate[index],
InBuffer::AudioIn => &self.audio_in,
}
.read()
}
}

#[derive(Clone)]
pub(crate) struct WaveformBuffer {
pub storage: Vec<f64>,
pub len: usize,
pub dirty: bool,
pub zeros: Arc<[f64]>,
}

impl WaveformBuffer {
pub fn new(zeros: Arc<[f64]>) -> Self {
Self {
storage: vec![0.0; zeros.len()],
len: 0,
dirty: false,
zeros,
}
}

pub fn clear(&mut self, len: usize) {
self.len = len;
self.dirty = true;
}

pub fn read(&self) -> &[f64] {
match self.dirty {
true => &self.zeros[..self.len],
false => &self.storage[..self.len],
}
}

pub fn write(&mut self, items: impl Iterator<Item = f64>) {
match self.dirty {
true => {
for (dest, src) in self.storage[..self.len].iter_mut().zip(items) {
*dest = src
}
self.dirty = false;
}
false => {
for (dest, src) in self.storage[..self.len].iter_mut().zip(items) {
*dest += src
}
}
}
}
}
Loading

0 comments on commit 28a845b

Please sign in to comment.