Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplified control architecture #101

Merged
merged 7 commits into from
Sep 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions examples/adapt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ const RATE: u32 = 44100;
const BLOCK_SIZE: usize = 512;

fn main() {
let mixer = oddio::Adapt::new(
oddio::Mixer::new(),
let (mut mixer, signal) = oddio::Mixer::new();
let mut signal = oddio::Adapt::new(
signal,
1e-3 / 2.0f32.sqrt(),
oddio::AdaptOptions {
tau: 0.1,
Expand All @@ -13,7 +14,6 @@ fn main() {
high: 0.5 / 2.0f32.sqrt(),
},
);
let (mut mixer, split) = oddio::split(mixer);

let spec = hound::WavSpec {
channels: 1,
Expand All @@ -26,7 +26,7 @@ fn main() {
let mut drive = || {
for _ in 0..(RATE * DURATION_SECS / BLOCK_SIZE as u32) {
let mut block = [0.0; BLOCK_SIZE];
oddio::run(&split, RATE, &mut block);
oddio::run(&mut signal, RATE, &mut block);
for &sample in &block {
writer
.write_sample((sample * i16::MAX as f32) as i16)
Expand All @@ -38,11 +38,11 @@ fn main() {
let quiet = oddio::FixedGain::new(oddio::Sine::new(0.0, 5e2), -60.0);
let loud = oddio::FixedGain::new(oddio::Sine::new(0.0, 4e2), -2.0);

mixer.control::<oddio::Mixer<f32>, _>().play(quiet);
mixer.play(quiet);
drive();
let mut handle = mixer.control::<oddio::Mixer<f32>, _>().play(loud);
let mut handle = mixer.play(loud);
drive();
handle.control::<oddio::Stop<_>, _>().stop();
handle.stop();
drive();

writer.finalize().unwrap();
Expand Down
6 changes: 3 additions & 3 deletions examples/offline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ fn main() {
(t * 500.0 * 2.0 * core::f32::consts::PI).sin() * 80.0
}),
);
let (mut scene_handle, scene) = oddio::split(oddio::SpatialScene::new());
scene_handle.control::<oddio::SpatialScene, _>().play(
let (mut scene_handle, mut scene) = oddio::SpatialScene::new();
scene_handle.play(
oddio::FramesSignal::from(boop),
oddio::SpatialOptions {
position: [-SPEED, 10.0, 0.0].into(),
Expand All @@ -32,7 +32,7 @@ fn main() {

for _ in 0..(RATE * DURATION_SECS / BLOCK_SIZE as u32) {
let mut block = [[0.0; 2]; BLOCK_SIZE];
oddio::run(&scene, RATE, &mut block);
oddio::run(&mut scene, RATE, &mut block);
for &frame in &block {
for &sample in &frame {
writer
Expand Down
44 changes: 15 additions & 29 deletions examples/realtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ fn main() {

// create our oddio handles for a `SpatialScene`. We could also use a `Mixer`,
// which doesn't spatialize signals.
let (mut scene_handle, scene) = oddio::split(oddio::SpatialScene::new());
let (mut scene_handle, mut scene) = oddio::SpatialScene::new();

// We send `scene` into this closure, where changes to `scene_handle` are reflected.
// `scene_handle` is how we add new sounds and modify the scene live.
Expand All @@ -30,7 +30,7 @@ fn main() {
&config,
move |data: &mut [f32], _: &cpal::OutputCallbackInfo| {
let frames = oddio::frame_stereo(data);
oddio::run(&scene, sample_rate.0, frames);
oddio::run(&mut scene, sample_rate.0, frames);
},
move |err| {
eprintln!("{}", err);
Expand Down Expand Up @@ -59,32 +59,24 @@ fn main() {

// We can also add filters around our `FramesSignal` to make our sound more controllable.
// A common one is `Gain`, which lets us modulate the gain of the `Signal` (how loud it is)
let gain = oddio::Gain::new(basic_signal);

// The type given out from `.play_buffered` reflects the controls we placed in it. It will be a
// very complex type, so it can be useful to newtype or typedef. Notice the `Gain`, which is
// there because we wrapped our `FramesSignal` above with `Gain`.
type AudioHandle =
oddio::Handle<oddio::SpatialBuffered<oddio::Stop<oddio::Gain<oddio::FramesSignal<f32>>>>>;
let (mut gain_control, gain) = oddio::Gain::new(basic_signal);

// the speed at which we'll be moving around
const SPEED: f32 = 50.0;
// `_play_buffered` is used because the dynamically adjustable `Gain` filter makes sample values
// `play_buffered` is used because the dynamically adjustable `Gain` filter makes sample values
// non-deterministic. For immutable signals like a bare `FramesSignal`, the regular `play` is
// more efficient.
let mut signal: AudioHandle = scene_handle
.control::<oddio::SpatialScene, _>()
.play_buffered(
gain,
oddio::SpatialOptions {
position: [-SPEED, 10.0, 0.0].into(),
velocity: [SPEED, 0.0, 0.0].into(),
radius: 0.1,
},
1000.0,
sample_rate.0,
0.1,
);
let mut spatial_control = scene_handle.play_buffered(
gain,
oddio::SpatialOptions {
position: [-SPEED, 10.0, 0.0].into(),
velocity: [SPEED, 0.0, 0.0].into(),
radius: 0.1,
},
1000.0,
sample_rate.0,
0.1,
);

let start = Instant::now();

Expand All @@ -95,9 +87,6 @@ fn main() {
break;
}

// Access our Spatial Controls
let mut spatial_control = signal.control::<oddio::SpatialBuffered<_>, _>();

// This has no noticable effect because it matches the initial velocity, but serves to
// demonstrate that `Spatial` can smooth over the inevitable small timing inconsistencies
// between the main thread and the audio thread without glitching.
Expand All @@ -108,9 +97,6 @@ fn main() {
);

// We also could adjust the Gain here in the same way:
let mut gain_control = signal.control::<oddio::Gain<_>, _>();

// Just leave the gain at its natural volume. (sorry this can be a bit loud!)
gain_control.set_gain(1.0);
}
}
8 changes: 3 additions & 5 deletions examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,15 @@ fn main() {
// Create the root mixer, and divide it into two parts: a handle that we can use to add new
// signals to play, and an object we can pass to `oddio::run` in cpal's callback to generate
// output frames.
let (mut mixer_handle, mixer) = oddio::split(oddio::Mixer::new());
let (mut mixer_handle, mut mixer) = oddio::Mixer::new();

// Start cpal, taking care not to drop its stream early
let stream = device
.build_output_stream(
&config,
move |out_flat: &mut [f32], _: &cpal::OutputCallbackInfo| {
let out_stereo: &mut [[f32; 2]] = oddio::frame_stereo(out_flat);
oddio::run(&mixer, sample_rate.0, out_stereo);
oddio::run(&mut mixer, sample_rate.0, out_stereo);
},
move |err| {
eprintln!("{}", err);
Expand All @@ -39,9 +39,7 @@ fn main() {

// Start a 200Hz sine wave. We can do this as many times as we like, whenever we like, with
// different types of signals as needed.
mixer_handle
.control::<oddio::Mixer<_>, _>()
.play(oddio::MonoToStereo::new(oddio::Sine::new(0.0, 400.0)));
mixer_handle.play(oddio::MonoToStereo::new(oddio::Sine::new(0.0, 400.0)));

// Wait a bit before exiting
thread::sleep(Duration::from_secs(3));
Expand Down
8 changes: 3 additions & 5 deletions examples/wav.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ fn main() {
let samples_stereo = oddio::frame_stereo(&mut samples);
let sound_frames = oddio::Frames::from_slice(source_sample_rate, samples_stereo);

let (mut mixer_handle, mixer) = oddio::split(oddio::Mixer::new());
let (mut mixer_handle, mut mixer) = oddio::Mixer::new();

let config = cpal::StreamConfig {
channels: 2,
Expand All @@ -58,7 +58,7 @@ fn main() {
&config,
move |out_flat: &mut [f32], _: &cpal::OutputCallbackInfo| {
let out_stereo = oddio::frame_stereo(out_flat);
oddio::run(&mixer, device_sample_rate, out_stereo);
oddio::run(&mut mixer, device_sample_rate, out_stereo);
},
move |err| {
eprintln!("{}", err);
Expand All @@ -67,9 +67,7 @@ fn main() {
.unwrap();
stream.play().unwrap();

mixer_handle
.control::<oddio::Mixer<_>, _>()
.play(oddio::FramesSignal::from(sound_frames));
mixer_handle.play(oddio::FramesSignal::from(sound_frames));

thread::sleep(Duration::from_secs_f32(length_seconds));
}
22 changes: 6 additions & 16 deletions src/adapt.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use core::cell::Cell;

use crate::{math::Float, Filter, Frame, Signal};
use crate::{math::Float, Frame, Signal};

/// Smoothly adjusts gain over time to keep average (RMS) signal level within a target range
///
Expand All @@ -15,7 +13,7 @@ use crate::{math::Float, Filter, Frame, Signal};
/// perception of loudness is logarithmic.
pub struct Adapt<T: ?Sized> {
options: AdaptOptions,
avg_squared: Cell<f32>,
avg_squared: f32,
inner: T,
}

Expand All @@ -27,7 +25,7 @@ impl<T> Adapt<T> {
pub fn new(signal: T, initial_rms: f32, options: AdaptOptions) -> Self {
Self {
options,
avg_squared: Cell::new(initial_rms * initial_rms),
avg_squared: initial_rms * initial_rms,
inner: signal,
}
}
Expand Down Expand Up @@ -68,14 +66,13 @@ where
{
type Frame = T::Frame;

fn sample(&self, interval: f32, out: &mut [T::Frame]) {
fn sample(&mut self, interval: f32, out: &mut [T::Frame]) {
let alpha = 1.0 - (-interval / self.options.tau).exp();
self.inner.sample(interval, out);
for x in out {
let sample = x.channels().iter().sum::<f32>();
self.avg_squared
.set(sample * sample * alpha + self.avg_squared.get() * (1.0 - alpha));
let avg_peak = self.avg_squared.get().sqrt() * 2.0f32.sqrt();
self.avg_squared = sample * sample * alpha + self.avg_squared * (1.0 - alpha);
let avg_peak = self.avg_squared.sqrt() * 2.0f32.sqrt();
let gain = if avg_peak < self.options.low {
(self.options.low / avg_peak).min(self.options.max_gain)
} else if avg_peak > self.options.high {
Expand All @@ -94,13 +91,6 @@ where
}
}

impl<T> Filter for Adapt<T> {
type Inner = T;
fn inner(&self) -> &T {
&self.inner
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
4 changes: 2 additions & 2 deletions src/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ impl<T> Constant<T> {
impl<T: Clone> Signal for Constant<T> {
type Frame = T;

fn sample(&self, _interval: f32, out: &mut [T]) {
fn sample(&mut self, _interval: f32, out: &mut [T]) {
out.fill(self.0.clone());
}
}

impl<T: Clone> Seek for Constant<T> {
fn seek(&self, _: f32) {}
fn seek(&mut self, _: f32) {}
}
30 changes: 14 additions & 16 deletions src/cycle.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
use alloc::sync::Arc;
use core::cell::Cell;

use crate::{frame, math::Float, Frame, Frames, Seek, Signal};

/// Loops [`Frames`] end-to-end to construct a repeating signal
pub struct Cycle<T> {
/// Current playback time, in samples
cursor: Cell<f64>,
cursor: f64,
frames: Arc<Frames<T>>,
}

Expand All @@ -15,7 +14,7 @@ impl<T> Cycle<T> {
// TODO: Crossfade
pub fn new(frames: Arc<Frames<T>>) -> Self {
Self {
cursor: Cell::new(0.0),
cursor: 0.0,
frames,
}
}
Expand All @@ -24,10 +23,10 @@ impl<T> Cycle<T> {
impl<T: Frame + Copy> Signal for Cycle<T> {
type Frame = T;

fn sample(&self, interval: f32, out: &mut [T]) {
fn sample(&mut self, interval: f32, out: &mut [T]) {
let ds = interval * self.frames.rate() as f32;
let mut base = self.cursor.get() as usize;
let mut offset = (self.cursor.get() - base as f64) as f32;
let mut base = self.cursor as usize;
let mut offset = (self.cursor - base as f64) as f32;
for o in out {
let trunc = unsafe { offset.to_int_unchecked::<usize>() };
let fract = offset - trunc as f32;
Expand All @@ -50,15 +49,14 @@ impl<T: Frame + Copy> Signal for Cycle<T> {
*o = frame::lerp(&a, &b, fract);
offset += ds;
}
self.cursor.set(base as f64 + offset as f64);
self.cursor = base as f64 + offset as f64;
}
}

impl<T: Frame + Copy> Seek for Cycle<T> {
fn seek(&self, seconds: f32) {
let s = (self.cursor.get() + f64::from(seconds) * self.frames.rate() as f64)
fn seek(&mut self, seconds: f32) {
self.cursor = (self.cursor + f64::from(seconds) * self.frames.rate() as f64)
.rem_euclid(self.frames.len() as f64);
self.cursor.set(s);
}
}

Expand All @@ -70,15 +68,15 @@ mod tests {

#[test]
fn wrap_single() {
let s = Cycle::new(Frames::from_slice(1, FRAMES));
let mut s = Cycle::new(Frames::from_slice(1, FRAMES));
let mut buf = [0.0; 5];
s.sample(1.0, &mut buf);
assert_eq!(buf, [1.0, 2.0, 3.0, 1.0, 2.0]);
}

#[test]
fn wrap_multi() {
let s = Cycle::new(Frames::from_slice(1, FRAMES));
let mut s = Cycle::new(Frames::from_slice(1, FRAMES));
let mut buf = [0.0; 5];
s.sample(1.0, &mut buf[..2]);
s.sample(1.0, &mut buf[2..]);
Expand All @@ -87,7 +85,7 @@ mod tests {

#[test]
fn wrap_fract() {
let s = Cycle::new(Frames::from_slice(1, FRAMES));
let mut s = Cycle::new(Frames::from_slice(1, FRAMES));
let mut buf = [0.0; 8];
s.sample(0.5, &mut buf[..2]);
s.sample(0.5, &mut buf[2..]);
Expand All @@ -96,7 +94,7 @@ mod tests {

#[test]
fn wrap_fract_offset() {
let s = Cycle::new(Frames::from_slice(1, FRAMES));
let mut s = Cycle::new(Frames::from_slice(1, FRAMES));
s.seek(0.25);
let mut buf = [0.0; 7];
s.sample(0.5, &mut buf[..2]);
Expand All @@ -106,7 +104,7 @@ mod tests {

#[test]
fn wrap_single_frame() {
let s = Cycle::new(Frames::from_slice(1, &[1.0]));
let mut s = Cycle::new(Frames::from_slice(1, &[1.0]));
s.seek(0.25);
let mut buf = [0.0; 3];
s.sample(1.0, &mut buf[..2]);
Expand All @@ -116,7 +114,7 @@ mod tests {

#[test]
fn wrap_large_interval() {
let s = Cycle::new(Frames::from_slice(1, FRAMES));
let mut s = Cycle::new(Frames::from_slice(1, FRAMES));
let mut buf = [0.0; 3];
s.sample(10.0, &mut buf[..2]);
s.sample(10.0, &mut buf[2..]);
Expand Down
Loading
Loading