2017-10-22 12:17:25 +00:00
|
|
|
use std::mem;
|
2017-10-18 18:24:05 +00:00
|
|
|
use std::os::raw::c_void;
|
2017-10-22 12:17:25 +00:00
|
|
|
use std::slice::from_raw_parts;
|
|
|
|
use std::sync::Mutex;
|
|
|
|
use stdweb;
|
|
|
|
use stdweb::Reference;
|
|
|
|
use stdweb::unstable::TryInto;
|
|
|
|
use stdweb::web::TypedArray;
|
2017-10-23 14:41:38 +00:00
|
|
|
use stdweb::web::set_timeout;
|
2017-10-18 18:24:05 +00:00
|
|
|
|
|
|
|
use CreationError;
|
|
|
|
use Format;
|
|
|
|
use FormatsEnumerationError;
|
|
|
|
use Sample;
|
2017-10-20 19:18:40 +00:00
|
|
|
use SupportedFormat;
|
2017-10-18 18:24:05 +00:00
|
|
|
use UnknownTypeBuffer;
|
|
|
|
|
|
|
|
// The emscripten backend works by having a global variable named `_cpal_audio_contexts`, which
|
|
|
|
// is an array of `AudioContext` objects. A voice ID corresponds to an entry in this array.
|
|
|
|
//
|
|
|
|
// Creating a voice creates a new `AudioContext`. Destroying a voice destroys it.
|
|
|
|
|
|
|
|
// TODO: handle latency better ; right now we just use setInterval with the amount of sound data
|
|
|
|
// that is in each buffer ; this is obviously bad, and also the schedule is too tight and there may
|
|
|
|
// be underflows
|
|
|
|
|
2017-10-22 12:17:25 +00:00
|
|
|
pub struct EventLoop {
|
|
|
|
voices: Mutex<Vec<Option<Reference>>>,
|
|
|
|
}
|
|
|
|
|
2017-10-18 18:24:05 +00:00
|
|
|
impl EventLoop {
|
|
|
|
#[inline]
|
|
|
|
pub fn new() -> EventLoop {
|
2017-10-22 12:17:25 +00:00
|
|
|
stdweb::initialize();
|
|
|
|
|
2017-10-23 14:41:38 +00:00
|
|
|
EventLoop { voices: Mutex::new(Vec::new()) }
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2017-10-22 12:17:25 +00:00
|
|
|
pub fn run<F>(&self, callback: F) -> !
|
2017-10-18 18:24:05 +00:00
|
|
|
where F: FnMut(VoiceId, UnknownTypeBuffer)
|
|
|
|
{
|
2017-10-22 12:17:25 +00:00
|
|
|
// The `run` function uses `set_timeout` to invoke a Rust callback repeatidely. The job
|
|
|
|
// of this callback is to fill the content of the audio buffers.
|
|
|
|
|
|
|
|
// The first argument of the callback function (a `void*`) is a casted pointer to `self`
|
|
|
|
// and to the `callback` parameter that was passed to `run`.
|
|
|
|
|
|
|
|
fn callback_fn<F>(user_data_ptr: *mut c_void)
|
|
|
|
where F: FnMut(VoiceId, UnknownTypeBuffer)
|
|
|
|
{
|
|
|
|
unsafe {
|
|
|
|
let user_data_ptr2 = user_data_ptr as *mut (&EventLoop, F);
|
|
|
|
let user_data = &mut *user_data_ptr2;
|
|
|
|
let user_cb = &mut user_data.1;
|
|
|
|
|
|
|
|
let voices = user_data.0.voices.lock().unwrap().clone();
|
|
|
|
for (voice_id, voice) in voices.iter().enumerate() {
|
|
|
|
let voice = match voice.as_ref() {
|
|
|
|
Some(v) => v,
|
|
|
|
None => continue,
|
|
|
|
};
|
|
|
|
|
|
|
|
let buffer = Buffer {
|
|
|
|
temporary_buffer: vec![0.0; 44100 * 2 / 3],
|
|
|
|
voice: &voice,
|
|
|
|
};
|
|
|
|
|
2017-10-23 14:41:38 +00:00
|
|
|
user_cb(VoiceId(voice_id),
|
|
|
|
::UnknownTypeBuffer::F32(::Buffer { target: Some(buffer) }));
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
|
2017-10-22 12:17:25 +00:00
|
|
|
set_timeout(|| callback_fn::<F>(user_data_ptr), 330);
|
|
|
|
}
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
2017-10-22 12:17:25 +00:00
|
|
|
|
|
|
|
let mut user_data = (self, callback);
|
|
|
|
let user_data_ptr = &mut user_data as *mut (_, _);
|
|
|
|
|
|
|
|
set_timeout(|| callback_fn::<F>(user_data_ptr as *mut _), 10);
|
|
|
|
|
|
|
|
stdweb::event_loop();
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
2017-10-23 14:41:38 +00:00
|
|
|
pub fn build_voice(&self, _: &Endpoint, _format: &Format) -> Result<VoiceId, CreationError> {
|
2017-10-22 12:17:25 +00:00
|
|
|
let voice = js!(return new AudioContext()).into_reference().unwrap();
|
|
|
|
|
|
|
|
let mut voices = self.voices.lock().unwrap();
|
|
|
|
let voice_id = if let Some(pos) = voices.iter().position(|v| v.is_none()) {
|
|
|
|
voices[pos] = Some(voice);
|
|
|
|
pos
|
|
|
|
} else {
|
|
|
|
let l = voices.len();
|
|
|
|
voices.push(Some(voice));
|
|
|
|
l
|
2017-10-18 18:24:05 +00:00
|
|
|
};
|
|
|
|
|
2017-10-22 12:17:25 +00:00
|
|
|
Ok(VoiceId(voice_id))
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn destroy_voice(&self, voice_id: VoiceId) {
|
2017-10-22 12:17:25 +00:00
|
|
|
self.voices.lock().unwrap()[voice_id.0] = None;
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn play(&self, voice_id: VoiceId) {
|
2017-10-22 12:17:25 +00:00
|
|
|
let voices = self.voices.lock().unwrap();
|
2017-10-23 14:41:38 +00:00
|
|
|
let voice = voices
|
|
|
|
.get(voice_id.0)
|
|
|
|
.and_then(|v| v.as_ref())
|
|
|
|
.expect("invalid voice ID");
|
2017-10-22 12:17:25 +00:00
|
|
|
js!(@{voice}.resume());
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn pause(&self, voice_id: VoiceId) {
|
2017-10-22 12:17:25 +00:00
|
|
|
let voices = self.voices.lock().unwrap();
|
2017-10-23 14:41:38 +00:00
|
|
|
let voice = voices
|
|
|
|
.get(voice_id.0)
|
|
|
|
.and_then(|v| v.as_ref())
|
|
|
|
.expect("invalid voice ID");
|
2017-10-22 12:17:25 +00:00
|
|
|
js!(@{voice}.suspend());
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-22 12:17:25 +00:00
|
|
|
// Index within the `voices` array of the events loop.
|
2017-10-18 18:24:05 +00:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
2017-10-22 12:17:25 +00:00
|
|
|
pub struct VoiceId(usize);
|
2017-10-18 18:24:05 +00:00
|
|
|
|
|
|
|
// Detects whether the `AudioContext` global variable is available.
|
|
|
|
fn is_webaudio_available() -> bool {
|
2017-10-22 12:17:25 +00:00
|
|
|
stdweb::initialize();
|
|
|
|
|
2017-10-23 14:41:38 +00:00
|
|
|
js!(if (!AudioContext) {
|
|
|
|
return false;
|
|
|
|
} else {
|
|
|
|
return true;
|
|
|
|
}).try_into()
|
|
|
|
.unwrap()
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Content is false if the iterator is empty.
|
|
|
|
pub struct EndpointsIterator(bool);
|
|
|
|
impl Default for EndpointsIterator {
|
|
|
|
fn default() -> EndpointsIterator {
|
|
|
|
// We produce an empty iterator if the WebAudio API isn't available.
|
|
|
|
EndpointsIterator(is_webaudio_available())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
impl Iterator for EndpointsIterator {
|
|
|
|
type Item = Endpoint;
|
|
|
|
#[inline]
|
|
|
|
fn next(&mut self) -> Option<Endpoint> {
|
|
|
|
if self.0 {
|
|
|
|
self.0 = false;
|
|
|
|
Some(Endpoint)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn default_endpoint() -> Option<Endpoint> {
|
|
|
|
if is_webaudio_available() {
|
|
|
|
Some(Endpoint)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
|
|
pub struct Endpoint;
|
|
|
|
|
|
|
|
impl Endpoint {
|
|
|
|
#[inline]
|
2017-10-23 14:41:38 +00:00
|
|
|
pub fn supported_formats(&self) -> Result<SupportedFormatsIterator, FormatsEnumerationError> {
|
2017-10-18 18:24:05 +00:00
|
|
|
// TODO: right now cpal's API doesn't allow flexibility here
|
|
|
|
// "44100" and "2" (channels) have also been hard-coded in the rest of the code ; if
|
|
|
|
// this ever becomes more flexible, don't forget to change that
|
2017-10-23 14:41:38 +00:00
|
|
|
Ok(
|
|
|
|
vec![
|
|
|
|
SupportedFormat {
|
2018-02-04 09:38:06 +00:00
|
|
|
channels: 2,
|
2017-10-23 14:41:38 +00:00
|
|
|
min_samples_rate: ::SamplesRate(44100),
|
|
|
|
max_samples_rate: ::SamplesRate(44100),
|
|
|
|
data_type: ::SampleFormat::F32,
|
|
|
|
},
|
|
|
|
].into_iter(),
|
|
|
|
)
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn name(&self) -> String {
|
|
|
|
"Default endpoint".to_owned()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-20 19:18:40 +00:00
|
|
|
pub type SupportedFormatsIterator = ::std::vec::IntoIter<SupportedFormat>;
|
2017-10-18 18:24:05 +00:00
|
|
|
|
2017-10-23 14:41:38 +00:00
|
|
|
pub struct Buffer<'a, T: 'a>
|
|
|
|
where T: Sample
|
|
|
|
{
|
2017-10-18 18:24:05 +00:00
|
|
|
temporary_buffer: Vec<T>,
|
2017-10-22 12:17:25 +00:00
|
|
|
voice: &'a Reference,
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
|
2017-10-23 14:41:38 +00:00
|
|
|
impl<'a, T> Buffer<'a, T>
|
|
|
|
where T: Sample
|
|
|
|
{
|
2017-10-18 18:24:05 +00:00
|
|
|
#[inline]
|
|
|
|
pub fn buffer(&mut self) -> &mut [T] {
|
|
|
|
&mut self.temporary_buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
self.temporary_buffer.len()
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn finish(self) {
|
2017-10-22 12:17:25 +00:00
|
|
|
// TODO: directly use a TypedArray<f32> once this is supported by stdweb
|
|
|
|
|
|
|
|
let typed_array = {
|
|
|
|
let t_slice: &[T] = self.temporary_buffer.as_slice();
|
2017-10-23 14:41:38 +00:00
|
|
|
let u8_slice: &[u8] = unsafe {
|
|
|
|
from_raw_parts(t_slice.as_ptr() as *const _,
|
|
|
|
t_slice.len() * mem::size_of::<T>())
|
|
|
|
};
|
2017-10-22 12:17:25 +00:00
|
|
|
let typed_array: TypedArray<u8> = u8_slice.into();
|
|
|
|
typed_array
|
|
|
|
};
|
|
|
|
|
2017-10-23 14:41:38 +00:00
|
|
|
let num_channels = 2u32; // TODO: correct value
|
2017-10-22 12:17:25 +00:00
|
|
|
debug_assert_eq!(self.temporary_buffer.len() % num_channels as usize, 0);
|
|
|
|
|
|
|
|
js!(
|
|
|
|
var src_buffer = new Float32Array(@{typed_array}.buffer);
|
|
|
|
var context = @{self.voice};
|
|
|
|
var buf_len = @{self.temporary_buffer.len() as u32};
|
|
|
|
var num_channels = @{num_channels};
|
|
|
|
|
|
|
|
var buffer = context.createBuffer(num_channels, buf_len / num_channels, 44100);
|
|
|
|
for (var channel = 0; channel < num_channels; ++channel) {
|
|
|
|
var buffer_content = buffer.getChannelData(channel);
|
|
|
|
for (var i = 0; i < buf_len / num_channels; ++i) {
|
|
|
|
buffer_content[i] = src_buffer[i * num_channels + channel];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var node = context.createBufferSource();
|
|
|
|
node.buffer = buffer;
|
|
|
|
node.connect(context.destination);
|
|
|
|
node.start();
|
|
|
|
);
|
2017-10-18 18:24:05 +00:00
|
|
|
}
|
|
|
|
}
|