A simple drawbar organ in Rust
A Rust application that roughly models a drawbar organ.
Largely an experiment in audio coding, multithreading and FFI.
Language features looked interesting for audio
Concurrency is becoming more important
An organ seemed a good size for a spare time project
Rust is a systems programming langauge from Mozilla, aimed at:
use std::string::String;
fn main() {
for num_bottles in (1u32..100).rev() {
println!("{}", bottles_line(num_bottles, true));
println!("{}", bottles_line(num_bottles, false));
println!("Take one down, pass it around...");
println!("{}", bottles_line(num_bottles - 1, true));
println!("-----------------------------------");
}
}
fn bottles_line(num_bottles: u32, on_the_wall: bool) -> String {
let tail = match on_the_wall {
true => "of beer on the wall!\n",
false => "of beer\n"
};
match num_bottles {
0 => format!("No bottles {}", tail),
1 => format!("One bottle {}", tail),
n => format!("{} bottles {}", n, tail)
}
}
Basic facilities are there
Audio IO libraries: CoreAudio, PortAudio, OpenAL etc
Utilities: pitch_calc, panning etc
Midi on most platforms
No CoreMidi library yet
Had to write my own FFI wrapper
A number of sine-ish oscillators per note (usually 9)
Drawbars control the mix of different harmonics
Notes are on or off, no envelope, no velocity
Image by Wikipedia user D135-1r43
Sound generation is split into voice threads
Each thread runs a number of voices
Per-thread mixing of voices
Final mixing of audio from all threads in audio output thread
Midi input callback forwards midi to voices
9 Oscillators
Mixer
Amp/envelope
Audio buffers
Connections
Audio objects
Just short arrays
pub const BUFFER_SIZE: usize = 16;
pub type AudioBuffer = [f32; BUFFER_SIZE];
pub const BLANK_BUFFER: AudioBuffer = [0.0; BUFFER_SIZE];
Traits for output and input ends
pub trait Output {
/// Write audio into the buffer
fn supply_audio(&self, buffer: AudioBuffer);
}
pub trait Input {
/// Get audio out of the buffer
fn get_audio(&self) -> AudioBuffer;
}
2 implementations:
Both are thin wrappers around standard libray objects.
Individual audio processing stages are objects.
Use generics to accept different connection types
pub struct Mixer<T, U> where T: Input, U: Output {
levels: Vec<f32>,
inputs: Vec<T>,
output: U
}
Each object has a run method for performing processing
pub fn run(&mut self) {
let mut samples: AudioBuffer = BLANK_BUFFER;
for (input, level) in self.inputs.iter().zip(self.levels.iter()) {
let in_samples = input.get_audio();
for (sample, in_sample) in samples.iter_mut().zip(in_samples.iter()) {
*sample += *in_sample * level;
}
}
self.output.supply_audio(samples);
}
Contains a phase iterator object which produces phase values for each sample
32 bit counter incremented (with wrapping) every sample by an amount based on the frequency
Has selectable scaling of output values
Uses a phase iterator scaled to 2π
Applies sine funtion to it
Contains a vector of inputs and a vector of level settings
Multiplies each input by its level setting and adds them up
Simple short attack-release envelope for avoiding pops and clicks
Has a ramp time in samples and a current position that is updated every sample during attack and release
Input signal is multiplied by position/ramp time
Built by joining objects with connections
Code corresponds to structure diagram
for _ in (0..9) {
let (output, input) = unthreaded_connection::new();
let osc = Oscillator::new(sample_rate, output);
oscillators.push(osc);
osc_connections.push(input);
}
let num_oscs = osc_connections.len();
let (mix_output, env_input) = unthreaded_connection::new();
let mut mixer = Mixer::new(osc_connections, vec![0.0; num_oscs], mix_output);
// mixer setup omitted
let env = Env::new(env_input, voice_output, 20, sample_rate);
Audio processing is done by calling the run method on each object in the signal chain.
for osc in self.oscillators.iter_mut() {
osc.run();
}
self.mixer.run();
self.env.run();
This allows for processing to be moved to a different thread if desired
It works!
32 voices on 4 threads
Threading adds latency (16 samples)
Add chorus or rotary speaker simultation
Switch from midi to an internal message format and add a midi mapper
Code - https://github.com/monsieursquirrel/organn
Rust language - https://www.rust-lang.org/
Article on oscillators - http://0au.de/2015/07/numerically-controlled-oscillators/