-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
395 additions
and
130 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
use cpal::{Sample as CpalSample, SampleFormat}; | ||
use rodio::{Sample, Source}; | ||
|
||
#[derive(Copy, Clone)] | ||
pub struct Bweights { | ||
w: f32, | ||
x: f32, | ||
y: f32, | ||
z: f32, | ||
} | ||
|
||
impl Bweights { | ||
pub fn from_position(pos: [f32; 3]) -> Self { | ||
let dist = (pos[0] * pos[0] + pos[1] * pos[1] + pos[1] * pos[2]).sqrt(); | ||
let falloff = 1.0 / dist.max(1.0); // todo: proper falloff and distance model(s) | ||
Bweights { | ||
w: falloff / 2f32.sqrt(), | ||
x: falloff * pos[0] / dist, | ||
y: falloff * pos[1] / dist, | ||
z: falloff * pos[2] / dist, | ||
} | ||
} | ||
|
||
pub fn dot(&self, b: Bformat) -> f32 { | ||
self.w * b.w + self.x * b.x + self.y * b.y + self.z * b.z | ||
} | ||
|
||
pub fn scale(&self, s: f32) -> Bformat { | ||
Bformat { | ||
w: self.w * s, | ||
x: self.x * s, | ||
y: self.y * s, | ||
z: self.z * s, | ||
} | ||
} | ||
|
||
pub fn virtual_microphone(direction: [f32; 3], p: f32) -> Self { | ||
let l = (direction[0] * direction[0] | ||
+ direction[1] * direction[1] | ||
+ direction[1] * direction[2]) | ||
.sqrt(); | ||
Bweights { | ||
w: p * 2f32.sqrt(), | ||
x: direction[0] * (1.0 - p) / l, | ||
y: direction[1] * (1.0 - p) / l, | ||
z: direction[2] * (1.0 - p) / l, | ||
} | ||
} | ||
} | ||
|
||
#[derive(Copy, Clone)] | ||
pub struct Bformat { | ||
w: f32, | ||
x: f32, | ||
y: f32, | ||
z: f32, | ||
} | ||
|
||
impl Sample for Bformat { | ||
fn lerp(first: Self, second: Self, numerator: u32, denominator: u32) -> Self { | ||
let alpha = numerator as f32 / denominator as f32; | ||
Bformat { | ||
w: first.w * alpha + second.w * (1.0 - alpha), | ||
x: first.x * alpha + second.x * (1.0 - alpha), | ||
y: first.y * alpha + second.y * (1.0 - alpha), | ||
z: first.z * alpha + second.z * (1.0 - alpha), | ||
} | ||
} | ||
|
||
fn amplify(self, alpha: f32) -> Self { | ||
Bformat { | ||
w: self.w * alpha, | ||
x: self.x * alpha, | ||
y: self.y * alpha, | ||
z: self.z * alpha, | ||
} | ||
} | ||
|
||
fn saturating_add(self, other: Self) -> Self { | ||
Bformat { | ||
w: self.w + other.w, | ||
x: self.x + other.x, | ||
y: self.y + other.y, | ||
z: self.z + other.z, | ||
} | ||
} | ||
|
||
fn zero_value() -> Self { | ||
Bformat { | ||
w: 0.0, | ||
x: 0.0, | ||
y: 0.0, | ||
z: 0.0, | ||
} | ||
} | ||
} | ||
|
||
// Why? Oh, why!? | ||
unsafe impl CpalSample for Bformat { | ||
fn get_format() -> SampleFormat { | ||
panic!("The B-format is not intended to be used as a CPAL sample directly. Use a renderer instead.") | ||
} | ||
|
||
fn to_f32(&self) -> f32 { | ||
panic!("The B-format is not intended to be used as a CPAL sample directly. Use a renderer instead.") | ||
} | ||
|
||
fn to_i16(&self) -> i16 { | ||
panic!("The B-format is not intended to be used as a CPAL sample directly. Use a renderer instead.") | ||
} | ||
|
||
fn to_u16(&self) -> u16 { | ||
panic!("The B-format is not intended to be used as a CPAL sample directly. Use a renderer instead.") | ||
} | ||
|
||
fn from<S>(_s: &S) -> Self | ||
where | ||
S: CpalSample, | ||
{ | ||
panic!("The B-format is not intended to be used as a CPAL sample directly. Use a renderer instead.") | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
use std::sync::{Arc, Mutex}; | ||
use std::sync::atomic::{AtomicBool, Ordering}; | ||
use std::time::Duration; | ||
|
||
use rodio::{Sample, Source}; | ||
|
||
use bformat::{Bformat, Bweights}; | ||
use bstream::Bstream; | ||
|
||
pub fn bmixer(sample_rate: u32) -> (BstreamMixer, Arc<BmixerController>) { | ||
let controller = Arc::new(BmixerController { | ||
sample_rate, | ||
pending_streams: Mutex::new(Vec::new()), | ||
has_pending: AtomicBool::new(false), | ||
}); | ||
|
||
let mixer = BstreamMixer { | ||
controller: controller.clone(), | ||
active_streams: Vec::with_capacity(8), | ||
}; | ||
|
||
(mixer, controller) | ||
} | ||
|
||
pub struct BstreamMixer { | ||
controller: Arc<BmixerController>, | ||
active_streams: Vec<Bstream>, | ||
} | ||
|
||
impl Source for BstreamMixer { | ||
#[inline(always)] | ||
fn current_frame_len(&self) -> Option<usize> { | ||
None | ||
} | ||
|
||
#[inline(always)] | ||
fn channels(&self) -> u16 { | ||
1 // actually 4, but they are packed into one struct | ||
} | ||
|
||
#[inline(always)] | ||
fn sample_rate(&self) -> u32 { | ||
self.controller.sample_rate | ||
} | ||
|
||
#[inline(always)] | ||
fn total_duration(&self) -> Option<Duration> { | ||
None | ||
} | ||
} | ||
|
||
impl Iterator for BstreamMixer { | ||
type Item = Bformat; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
if self.controller.has_pending.load(Ordering::SeqCst) { | ||
let mut pending = self.controller.pending_streams.lock().expect("Cannot lock pending streams"); | ||
self.active_streams.extend(pending.drain(..)); | ||
self.controller.has_pending.store(false, Ordering::SeqCst); | ||
} | ||
|
||
let mut mix = Bformat::zero_value(); | ||
|
||
let mut done = Vec::new(); | ||
|
||
for (i, stream) in self.active_streams.iter_mut().enumerate() { | ||
match stream.next() { | ||
Some(x) => mix = mix.saturating_add(x), | ||
None => done.push(i), | ||
} | ||
} | ||
|
||
for i in done.into_iter().rev() { | ||
self.active_streams.remove(i); | ||
} | ||
|
||
Some(mix) | ||
} | ||
} | ||
|
||
pub struct BmixerController { | ||
has_pending: AtomicBool, | ||
pending_streams: Mutex<Vec<Bstream>>, | ||
sample_rate: u32, | ||
} | ||
|
||
impl BmixerController { | ||
pub fn play<I>(&self, input: I, pos: [f32; 3]) | ||
where | ||
I: Source<Item = f32> + Send + 'static | ||
{ | ||
assert_eq!(input.channels(), 1); | ||
|
||
let bstream = Bstream::new(input, pos); | ||
|
||
self.pending_streams.lock().expect("Cannot lock pending streams").push(bstream); | ||
self.has_pending.store(true, Ordering::SeqCst); | ||
} | ||
} |
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
use std::sync::Arc; | ||
use std::time::Duration; | ||
|
||
use rodio::Source; | ||
|
||
use bformat::{Bformat, Bweights}; | ||
|
||
pub struct Bstream { | ||
input: Box<Source<Item = f32> + Send>, | ||
bweights: Bweights, | ||
} | ||
|
||
impl Bstream { | ||
pub fn new<I: Source<Item = f32> + Send + 'static>(source: I, pos: [f32; 3]) -> Self { | ||
Bstream { | ||
input: Box::new(source), | ||
bweights: Bweights::from_position(pos), | ||
} | ||
} | ||
} | ||
|
||
impl Source for Bstream { | ||
#[inline(always)] | ||
fn current_frame_len(&self) -> Option<usize> { | ||
self.input.current_frame_len() | ||
} | ||
|
||
#[inline(always)] | ||
fn channels(&self) -> u16 { | ||
assert_eq!(self.input.channels(), 1); | ||
1 | ||
} | ||
|
||
#[inline(always)] | ||
fn sample_rate(&self) -> u32 { | ||
self.input.sample_rate() | ||
} | ||
|
||
#[inline(always)] | ||
fn total_duration(&self) -> Option<Duration> { | ||
self.input.total_duration() | ||
} | ||
} | ||
|
||
impl Iterator for Bstream { | ||
type Item = Bformat; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
let x = self.input.next()?; | ||
Some(self.bweights.scale(x)) | ||
} | ||
} |
Oops, something went wrong.