Created
July 19, 2018 16:20
-
-
Save mbillingr/25d1020bf8c0e56e6f1a3d8e1bc458c1 to your computer and use it in GitHub Desktop.
Simple Ambisonics on top of CPAL (and inspired by Rodio)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
extern crate cpal; | |
extern crate hound; | |
use std::f32; | |
use std::sync::{Arc, Mutex}; | |
use std::sync::atomic::{AtomicBool, Ordering}; | |
use std::thread; | |
use std::ops; | |
use cpal::{StreamData, UnknownTypeOutputBuffer}; | |
trait Sample { | |
fn as_u16(&self) -> u16; | |
fn as_i16(&self) -> i16; | |
fn as_f32(&self) -> f32; | |
fn zero() -> Self; | |
} | |
/* | |
/// Blanket impl for references | |
impl<'a, T: Sample> Sample for &'a T { | |
fn as_u16(&self) -> u16 { | |
T::as_u16(self) | |
} | |
fn as_i16(&self) -> i16 { | |
T::as_i16(self) | |
} | |
fn as_f32(&self) -> f32 { | |
T::as_f32(self) | |
} | |
fn zero() -> Self { | |
T::zero() | |
} | |
}*/ | |
impl Sample for i16 { | |
fn as_u16(&self) -> u16 { | |
((u16::max_value() / 2) as i32 + *self as i32) as u16 | |
} | |
fn as_i16(&self) -> i16 { | |
*self | |
} | |
fn as_f32(&self) -> f32 { | |
*self as f32 / i16::max_value() as f32 | |
} | |
fn zero() -> Self { | |
0 | |
} | |
} | |
impl Sample for f32 { | |
fn as_u16(&self) -> u16 { | |
((u16::max_value() / 2) as i32 + self.as_i16() as i32) as u16 | |
} | |
fn as_i16(&self) -> i16 { | |
(*self * i16::max_value() as f32) as i16 | |
} | |
fn as_f32(&self) -> f32 { | |
*self | |
} | |
fn zero() -> Self { | |
0.0 | |
} | |
} | |
trait Stream: Iterator { | |
fn n_channels(&self) -> usize; | |
fn broadcast(self, n: usize) -> Broadcast<Self::Item, Self> | |
where | |
Self: Sized, | |
Self::Item: Sample, | |
{ | |
Broadcast { | |
inner: self, | |
n_channels: n, | |
current_sample: Self::Item::zero(), | |
reps_to_go: 0, | |
} | |
} | |
fn floatify(self) -> Floatify<Self> | |
where | |
Self: Sized, | |
{ | |
Floatify { | |
inner: self, | |
} | |
} | |
fn repeat(self) -> Repeated<Self::Item> | |
where | |
Self: Sized, | |
{ | |
Repeated { | |
n_channels: self.n_channels(), | |
buffer: self.collect(), | |
index: 0, | |
} | |
} | |
} | |
impl<'a, S> Stream for std::iter::Cloned<std::slice::Iter<'a, S>> | |
where S: Sample + Clone | |
{ | |
fn n_channels(&self) -> usize { | |
1 | |
} | |
} | |
impl<'a, S> Stream for std::vec::IntoIter<S> | |
where S: Sample + Clone | |
{ | |
fn n_channels(&self) -> usize { | |
1 | |
} | |
} | |
struct Floatify<T> { | |
inner: T, | |
} | |
impl<S, T> Stream for Floatify<T> | |
where | |
T: Stream<Item=S>, | |
S: Sample, | |
{ | |
fn n_channels(&self) -> usize { | |
self.inner.n_channels() | |
} | |
} | |
impl<S, T> Iterator for Floatify<T> | |
where | |
T: Stream<Item=S>, | |
S: Sample, | |
{ | |
type Item = f32; | |
fn next(&mut self) -> Option<f32> { | |
self.inner.next().map(|s| s.as_f32()) | |
} | |
} | |
struct Repeated<S> { | |
n_channels: usize, | |
buffer: Vec<S>, | |
index: usize, | |
} | |
impl<S> Stream for Repeated<S> | |
where | |
S: Clone, | |
{ | |
fn n_channels(&self) -> usize { | |
self.n_channels | |
} | |
} | |
impl<S> Iterator for Repeated<S> | |
where | |
S: Clone, | |
{ | |
type Item = S; | |
fn next(&mut self) -> Option<S> { | |
if self.index >= self.buffer.len() { | |
if self.buffer.len() == 0 { | |
return None | |
} | |
self.index = 0; | |
} | |
let x = self.buffer[self.index].clone(); | |
self.index += 1; | |
Some(x) | |
} | |
} | |
#[derive(Clone)] | |
struct SharedBuffer<S: 'static> { | |
n_channels: usize, | |
buffer: Arc<Vec<S>>, | |
index: usize, | |
} | |
impl<S> Stream for SharedBuffer<S> | |
where | |
S: Clone + 'static, | |
{ | |
fn n_channels(&self) -> usize { | |
self.n_channels | |
} | |
} | |
impl<S> Iterator for SharedBuffer<S> | |
where | |
S: Clone + 'static, | |
{ | |
type Item = S; | |
fn next(&mut self) -> Option<S> { | |
if self.index >= self.buffer.len() { | |
None | |
} else { | |
let x = self.buffer[self.index].clone(); | |
self.index += 1; | |
Some(x) | |
} | |
} | |
} | |
struct Broadcast<S, T> | |
{ | |
inner: T, | |
n_channels: usize, | |
current_sample: S, | |
reps_to_go: usize, | |
} | |
impl<S, T> Stream for Broadcast<S, T> | |
where | |
T: Stream<Item=S>, | |
S: Sample + Clone, | |
{ | |
fn n_channels(&self) -> usize { | |
self.n_channels | |
} | |
} | |
impl<S, T> Iterator for Broadcast<S, T> | |
where | |
T: Stream<Item=S>, | |
S: Sample + Clone, | |
{ | |
type Item = S; | |
fn next(&mut self) -> Option<Self::Item> { | |
if self.reps_to_go == 0 { | |
self.current_sample = self.inner.next()?; | |
self.reps_to_go = self.n_channels; | |
} | |
self.reps_to_go -= 1; | |
Some(self.current_sample.clone()) | |
} | |
} | |
struct BFormat { | |
w: f32, | |
x: f32, | |
y: f32, | |
z: f32, | |
} | |
impl BFormat { | |
fn virtual_microphone(&self, dir: [f32; 3], p: f32) -> f32 { | |
p * 2f32.sqrt() * self.w + (1.0 - p) * (dir[0] * self.x + dir[1] * self.y + dir[2] * self.z) | |
} | |
} | |
impl Default for BFormat { | |
fn default() -> Self { | |
BFormat {w: 0.0, x: 0.0, y: 0.0, z: 0.0} | |
} | |
} | |
impl ops::AddAssign for BFormat { | |
fn add_assign(&mut self, other: Self) { | |
self.w += other.w; | |
self.x += other.x; | |
self.y += other.y; | |
self.z += other.z; | |
} | |
} | |
impl<'a> ops::Mul<f32> for &'a BFormat { | |
type Output = BFormat; | |
fn mul(self, s: f32) -> BFormat { | |
BFormat { | |
w: self.w * s, | |
x: self.x * s, | |
y: self.y * s, | |
z: self.z * s, | |
} | |
} | |
} | |
impl From<[f32; 3]> for BFormat { | |
fn from(dir: [f32; 3]) -> Self { | |
let l = (dir[0] * dir[0] + dir[1] * dir[1] + dir[1] * dir[2]).sqrt(); | |
BFormat { | |
w: 1.0 / 2f32.sqrt(), | |
x: dir[0] / l, | |
y: dir[1] / l, | |
z: dir[2] / l, | |
} | |
} | |
} | |
struct BStreamStereoDecoder { | |
input: BStreamMixer, | |
left_mic: [f32; 3], | |
right_mic: [f32; 3], | |
buffered_sample: Option<f32>, | |
} | |
impl BStreamStereoDecoder { | |
fn new(input: BStreamMixer) -> Self { | |
BStreamStereoDecoder { | |
input, | |
left_mic: [-1.0 / 2f32.sqrt(), 1.0 / 2f32.sqrt(), 0.0], | |
right_mic: [1.0 / 2f32.sqrt(), 1.0 / 2f32.sqrt(), 0.0], | |
buffered_sample: None, | |
} | |
} | |
} | |
impl Stream for BStreamStereoDecoder { | |
fn n_channels(&self) -> usize { | |
2 | |
} | |
} | |
impl Iterator for BStreamStereoDecoder { | |
type Item = f32; | |
fn next(&mut self) -> Option<f32> { | |
match self.buffered_sample.take() { | |
Some(s) => Some(s), | |
None => { | |
let sample = self.input.next()?; | |
let left = sample.virtual_microphone(self.left_mic, 0.5); | |
let right = sample.virtual_microphone(self.right_mic, 0.5); | |
// emit left channel now, and right channel on next call | |
self.buffered_sample = Some(right); | |
Some(left) | |
}, | |
} | |
} | |
} | |
fn bstream() -> (BStreamMixer, Arc<BStreamController>) { | |
let controller = Arc::new(BStreamController { | |
pending_streams: Mutex::new(Vec::new()), | |
has_pending: AtomicBool::new(false), | |
}); | |
let mixer = BStreamMixer { | |
input: controller.clone(), | |
active_streams: Vec::with_capacity(8), | |
}; | |
(mixer, controller) | |
} | |
struct BStreamMixer { | |
input: Arc<BStreamController>, | |
active_streams: Vec<SpatialStream>, | |
} | |
impl Iterator for BStreamMixer { | |
type Item = BFormat; | |
fn next(&mut self) -> Option<Self::Item> { | |
if self.input.has_pending.load(Ordering::SeqCst) { | |
let mut pending = self.input.pending_streams.lock().expect("Cannot lock pending streams"); | |
self.active_streams.extend(pending.drain(..)); | |
self.input.has_pending.store(false, Ordering::SeqCst); | |
} | |
let mut mix = BFormat::default(); | |
let mut done = Vec::new(); | |
for (i, stream) in self.active_streams.iter_mut().enumerate() { | |
match stream.next() { | |
Some(x) => mix += x, | |
None => done.push(i), | |
} | |
} | |
for i in done { | |
self.active_streams.remove(i); | |
} | |
Some(mix) | |
} | |
} | |
struct BStreamController { | |
pending_streams: Mutex<Vec<SpatialStream>>, | |
has_pending: AtomicBool, | |
} | |
impl BStreamController { | |
fn attach<T: 'static + Stream<Item = f32> + Send>(&self, stream: T, direction: [f32; 3]) -> Arc<SpatialStreamController> { | |
let controller = Arc::new(SpatialStreamController{ | |
direction: Mutex::new(direction), | |
update: AtomicBool::new(false), | |
}); | |
let sstream = SpatialStream { | |
inner: Box::new(stream), | |
bfactors: direction.into(), | |
controller: controller.clone(), | |
}; | |
self.pending_streams.lock().expect("Cannot lock pending streams").push(sstream); | |
self.has_pending.store(true, Ordering::SeqCst); | |
controller | |
} | |
} | |
struct SpatialStream { | |
inner: Box<Stream<Item = f32> + Send>, | |
bfactors: BFormat, | |
controller: Arc<SpatialStreamController>, | |
} | |
impl Iterator for SpatialStream { | |
type Item = BFormat; | |
fn next(&mut self) -> Option<Self::Item> { | |
if self.controller.update.load(Ordering::SeqCst) { | |
self.bfactors = (*self.controller.direction.lock().unwrap()).into(); | |
self.controller.update.store(false, Ordering::SeqCst); | |
} | |
let x = self.inner.next()?; | |
Some(&self.bfactors * x) | |
} | |
} | |
struct SpatialStreamController { | |
direction: Mutex<[f32; 3]>, | |
update: AtomicBool, | |
} | |
impl SpatialStreamController { | |
fn set_direction(&self, d: [f32; 3]) { | |
*self.direction.lock().unwrap() = d; | |
self.update.store(true, Ordering::SeqCst); | |
} | |
} | |
fn stream_player<T>(device: &cpal::Device, format: &cpal::Format, mut stream: T) -> thread::JoinHandle<()> | |
where | |
T: Stream + Send + 'static, | |
T::Item: Sample, | |
{ | |
let event_loop = cpal::EventLoop::new(); | |
let stream_id = event_loop.build_output_stream(&device, &format).unwrap(); | |
event_loop.play_stream(stream_id); | |
thread::spawn(move || { | |
event_loop.run(move |_stream_id, stream_data| { | |
match stream_data { | |
StreamData::Output { buffer: UnknownTypeOutputBuffer::U16(mut buffer) } => { | |
for elem in buffer.iter_mut() { | |
*elem = stream.next().unwrap().as_u16(); | |
} | |
}, | |
StreamData::Output { buffer: UnknownTypeOutputBuffer::I16(mut buffer) } => { | |
for elem in buffer.iter_mut() { | |
*elem = stream.next().unwrap().as_i16(); | |
} | |
}, | |
StreamData::Output { buffer: UnknownTypeOutputBuffer::F32(mut buffer) } => { | |
for elem in buffer.iter_mut() { | |
*elem = stream.next().unwrap().as_f32(); | |
} | |
}, | |
_ => (), | |
} | |
}); | |
}) | |
} | |
fn main() { | |
println!("Hello, world!"); | |
let mut reader = hound::WavReader::open(r"U:\Ling\Audio wav files\short\A.wav").unwrap(); | |
println!("{:?}", reader.spec()); | |
let sound: Vec<_> = reader | |
.samples::<i16>() | |
.map(|s| s.unwrap()) | |
.collect(); | |
let sound = Arc::new(sound); | |
let sound = SharedBuffer { | |
n_channels: 1, | |
buffer: sound, | |
index: 0, | |
}; | |
println!("Audio Devices:"); | |
for device in cpal::devices() { | |
println!(" -> {}", device.name()); | |
} | |
let device = cpal::default_output_device().expect("no output device available"); | |
println!("Default Device:\n {}", device.name()); | |
let supported_formats_range = device.supported_output_formats() | |
.expect("error while querying formats"); | |
println!("Supported formats:"); | |
for format in supported_formats_range { | |
println!("{:?}", format); | |
} | |
let format = device.supported_output_formats().unwrap().next() | |
.expect("no supported format?!") | |
.with_max_sample_rate(); | |
let (mixer, controller) = bstream(); | |
let decoder = BStreamStereoDecoder::new(mixer); | |
let player = stream_player(&device, &format, decoder); | |
/*for a in 0..=16 { | |
let a = a as f32 / 16.0 * 2.0 * f32::consts::PI; | |
controller.attach(sound.clone().into_iter().floatify(), [a.sin(), a.cos(), 0.0]); | |
thread::sleep_ms(1000); | |
}*/ | |
//let player = stream_player(&device, &format, sound.into_iter().broadcast(2)); | |
//let sc = controller.attach(sound.clone().into_iter().floatify().repeat(), [0.0, 1.0, 0.0]); | |
//controller.attach(sound.clone().floatify(), [-1.0, 0.0, 0.0]); | |
//thread::sleep_ms(500); | |
let sc = controller.attach(sound.floatify().repeat(), [0.0, 1.0, 0.0]); | |
for a in 0..1000 { | |
let a = a as f32 / 100.0; | |
sc.set_direction([a.sin(), a.cos(), 0.0]); | |
thread::sleep_ms(10); | |
} | |
//thread::sleep_ms(5000); | |
//player.join(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment