Skip to content

Commit

Permalink
feat: working prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
mbillingr committed Jul 19, 2018
1 parent fcf2e4c commit f2e5f63
Show file tree
Hide file tree
Showing 5 changed files with 395 additions and 130 deletions.
122 changes: 122 additions & 0 deletions src/bformat.rs
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.")
}
}
99 changes: 99 additions & 0 deletions src/bmixer.rs
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);
}
}
52 changes: 52 additions & 0 deletions src/bstream.rs
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))
}
}
Loading

0 comments on commit f2e5f63

Please sign in to comment.