adds support for buffersizes in webaudio and emscripten
This commit is contained in:
parent
cf1a928b84
commit
7c1adce330
|
@ -346,7 +346,6 @@ impl Device {
|
|||
let buffer_size_range = SupportedBufferSize::Range {
|
||||
min: min_buffer_size as u32,
|
||||
max: max_buffer_size as u32,
|
||||
requires_power_of_two: false,
|
||||
};
|
||||
|
||||
let mut output = Vec::with_capacity(
|
||||
|
|
|
@ -135,7 +135,6 @@ impl Device {
|
|||
let buffer_size = SupportedBufferSize::Range {
|
||||
min: min as u32,
|
||||
max: max as u32,
|
||||
requires_power_of_two: false,
|
||||
};
|
||||
// Map th ASIO sample type to a CPAL sample type
|
||||
let data_type = self.driver.input_data_type().map_err(default_config_err)?;
|
||||
|
@ -157,7 +156,6 @@ impl Device {
|
|||
let buffer_size = SupportedBufferSize::Range {
|
||||
min: min as u32,
|
||||
max: max as u32,
|
||||
requires_power_of_two: false,
|
||||
};
|
||||
let data_type = self.driver.output_data_type().map_err(default_config_err)?;
|
||||
let sample_format = convert_data_type(&data_type)
|
||||
|
|
|
@ -925,6 +925,5 @@ fn get_io_buffer_frame_size_range(
|
|||
Ok(SupportedBufferSize::Range {
|
||||
min: buffer_size_range.mMinimum as u32,
|
||||
max: buffer_size_range.mMaximum as u32,
|
||||
requires_power_of_two: false,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ use stdweb::web::TypedArray;
|
|||
use stdweb::Reference;
|
||||
|
||||
use crate::{
|
||||
BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError,
|
||||
BufferSize, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError, DevicesError,
|
||||
InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError, SampleFormat,
|
||||
StreamConfig, StreamError, SupportedStreamConfig, SupportedStreamConfigRange,
|
||||
SupportedStreamConfigsError,
|
||||
SampleRate, StreamConfig, StreamError, SupportedBufferSize, SupportedStreamConfig,
|
||||
SupportedStreamConfigRange, SupportedStreamConfigsError,
|
||||
};
|
||||
use traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
|
||||
|
@ -41,6 +41,16 @@ pub struct StreamId(usize);
|
|||
pub type SupportedInputConfigs = ::std::vec::IntoIter<SupportedStreamConfigRange>;
|
||||
pub type SupportedOutputConfigs = ::std::vec::IntoIter<SupportedStreamConfigRange>;
|
||||
|
||||
const MIN_CHANNELS: u16 = 1;
|
||||
const MAX_CHANNELS: u16 = 32;
|
||||
const MIN_SAMPLE_RATE: SampleRate = SampleRate(8_000);
|
||||
const MAX_SAMPLE_RATE: SampleRate = SampleRate(96_000);
|
||||
const DEFAULT_SAMPLE_RATE: SampleRate = SampleRate(44_100);
|
||||
const MIN_BUFFER_SIZE: u32 = 1;
|
||||
const MAX_BUFFER_SIZE: u32 = std::u32::MAX;
|
||||
const DEFAULT_BUFFER_SIZE: usize = 2048;
|
||||
const SUPPORTED_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
|
||||
|
||||
impl Host {
|
||||
pub fn new() -> Result<Self, crate::HostUnavailable> {
|
||||
stdweb::initialize();
|
||||
|
@ -71,21 +81,20 @@ impl Device {
|
|||
fn supported_output_configs(
|
||||
&self,
|
||||
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
|
||||
// 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
|
||||
// According to https://developer.mozilla.org/en-US/docs/Web/API/BaseAudioContext/createBuffer
|
||||
// browsers must support 1 to 32 channels at leats and 8,000 Hz to 96,000 Hz.
|
||||
//
|
||||
// UPDATE: We can do this now. Might be best to use `crate::COMMON_SAMPLE_RATES` and
|
||||
// filter out those that lay outside the range specified above.
|
||||
Ok(vec![SupportedStreamConfigRange {
|
||||
channels: 2,
|
||||
min_sample_rate: ::SampleRate(44100),
|
||||
max_sample_rate: ::SampleRate(44100),
|
||||
sample_format: ::SampleFormat::F32,
|
||||
}]
|
||||
.into_iter())
|
||||
let buffer_size = SupportedBufferSize::Range {
|
||||
min: MIN_BUFFER_SIZE,
|
||||
max: MAX_BUFFER_SIZE,
|
||||
};
|
||||
let configs: Vec<_> = (MIN_CHANNELS..=MAX_CHANNELS)
|
||||
.map(|channels| SupportedStreamConfigRange {
|
||||
channels,
|
||||
min_sample_rate: MIN_SAMPLE_RATE,
|
||||
max_sample_rate: MAX_SAMPLE_RATE,
|
||||
buffer_size: buffer_size.clone(),
|
||||
sample_format: SUPPORTED_SAMPLE_FORMAT,
|
||||
})
|
||||
.collect();
|
||||
Ok(configs.into_iter())
|
||||
}
|
||||
|
||||
fn default_input_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
||||
|
@ -93,12 +102,15 @@ impl Device {
|
|||
}
|
||||
|
||||
fn default_output_config(&self) -> Result<SupportedStreamConfig, DefaultStreamConfigError> {
|
||||
// TODO: because it is hard coded, see supported_output_configs.
|
||||
Ok(SupportedStreamConfig {
|
||||
channels: 2,
|
||||
sample_rate: ::SampleRate(44100),
|
||||
sample_format: ::SampleFormat::F32,
|
||||
})
|
||||
const EXPECT: &str = "expected at least one valid webaudio stream config";
|
||||
let mut configs: Vec<_> = self.supported_output_configs().expect(EXPECT).collect();
|
||||
configs.sort_by(|a, b| a.cmp_default_heuristics(b));
|
||||
let config = configs
|
||||
.into_iter()
|
||||
.next()
|
||||
.expect(EXPECT)
|
||||
.with_sample_rate(DEFAULT_SAMPLE_RATE);
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,7 +181,7 @@ impl DeviceTrait for Device {
|
|||
|
||||
fn build_output_stream_raw<D, E>(
|
||||
&self,
|
||||
_config: &StreamConfig,
|
||||
config: &StreamConfig,
|
||||
sample_format: SampleFormat,
|
||||
data_callback: D,
|
||||
error_callback: E,
|
||||
|
@ -178,11 +190,20 @@ impl DeviceTrait for Device {
|
|||
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
|
||||
E: FnMut(StreamError) + Send + 'static,
|
||||
{
|
||||
assert_eq!(
|
||||
sample_format,
|
||||
SampleFormat::F32,
|
||||
"emscripten backend currently only supports `f32` data",
|
||||
);
|
||||
if !valid_config(config, sample_format) {
|
||||
return Err(BuildStreamError::StreamConfigNotSupported);
|
||||
}
|
||||
|
||||
let buffer_size_frames = match config.buffer_size {
|
||||
BufferSize::Fixed(v) => {
|
||||
if v == 0 {
|
||||
return Err(BuildStreamError::StreamConfigNotSupported);
|
||||
} else {
|
||||
v as usize
|
||||
}
|
||||
}
|
||||
BufferSize::Default => DEFAULT_BUFFER_SIZE,
|
||||
};
|
||||
|
||||
// Create the stream.
|
||||
let audio_ctxt_ref = js!(return new AudioContext()).into_reference().unwrap();
|
||||
|
@ -199,7 +220,14 @@ impl DeviceTrait for Device {
|
|||
// See also: The call to `set_timeout` at the end of the `audio_callback_fn` which creates
|
||||
// the loop.
|
||||
set_timeout(
|
||||
|| audio_callback_fn::<D, E>(user_data_ptr as *mut c_void),
|
||||
|| {
|
||||
audio_callback_fn::<D, E>(
|
||||
user_data_ptr as *mut c_void,
|
||||
config,
|
||||
sample_format,
|
||||
buffer_size_frames,
|
||||
)
|
||||
},
|
||||
10,
|
||||
);
|
||||
|
||||
|
@ -223,12 +251,18 @@ impl StreamTrait for Stream {
|
|||
|
||||
// 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 audio_callback_fn<D, E>(user_data_ptr: *mut c_void)
|
||||
where
|
||||
fn audio_callback_fn<D, E>(
|
||||
user_data_ptr: *mut c_void,
|
||||
config: &StreamConfig,
|
||||
sample_format: SampleFormat,
|
||||
buffer_size_frames: usize,
|
||||
) where
|
||||
D: FnMut(&mut Data, &OutputCallbackInfo) + Send + 'static,
|
||||
E: FnMut(StreamError) + Send + 'static,
|
||||
{
|
||||
const SAMPLE_RATE: usize = 44100;
|
||||
let num_channels = config.channels as usize;
|
||||
let sample_rate = config.sample_rate.0;
|
||||
let buffer_size_samples = buffer_size_frames * num_channels;
|
||||
|
||||
unsafe {
|
||||
let user_data_ptr2 = user_data_ptr as *mut (&Stream, D, E);
|
||||
|
@ -237,12 +271,11 @@ where
|
|||
let audio_ctxt = &stream.audio_ctxt_ref;
|
||||
|
||||
// TODO: We should be re-using a buffer.
|
||||
let mut temporary_buffer = vec![0.0; SAMPLE_RATE * 2 / 3];
|
||||
let mut temporary_buffer = vec![0f32; buffer_size_samples];
|
||||
|
||||
{
|
||||
let len = temporary_buffer.len();
|
||||
let data = temporary_buffer.as_mut_ptr() as *mut ();
|
||||
let sample_format = SampleFormat::F32;
|
||||
let mut data = Data::from_parts(data, len, sample_format);
|
||||
|
||||
let now_secs: f64 = js!(@{audio_ctxt}.getOutputTimestamp().currentTime)
|
||||
|
@ -253,7 +286,7 @@ where
|
|||
// we estimate based on buffer size instead. Probably should use this, but it's only
|
||||
// supported by firefox (2020-04-28).
|
||||
// let latency_secs: f64 = js!(@{audio_ctxt}.outputLatency).try_into().unwrap();
|
||||
let buffer_duration = frames_to_duration(len, SAMPLE_RATE);
|
||||
let buffer_duration = frames_to_duration(len, sample_rate as usize);
|
||||
let playback = callback
|
||||
.add(buffer_duration)
|
||||
.expect("`playback` occurs beyond representation supported by `StreamInstant`");
|
||||
|
@ -273,19 +306,19 @@ where
|
|||
typed_array
|
||||
};
|
||||
|
||||
let num_channels = 2u32; // TODO: correct value
|
||||
debug_assert_eq!(temporary_buffer.len() % num_channels as usize, 0);
|
||||
|
||||
js!(
|
||||
var src_buffer = new Float32Array(@{typed_array}.buffer);
|
||||
var context = @{audio_ctxt};
|
||||
var buf_len = @{temporary_buffer.len() as u32};
|
||||
var num_channels = @{num_channels};
|
||||
var buffer_size_frames = @{buffer_size_frames as u32};
|
||||
var num_channels = @{num_channels as u32};
|
||||
var sample_rate = sample_rate;
|
||||
|
||||
var buffer = context.createBuffer(num_channels, buf_len / num_channels, 44100);
|
||||
var buffer = context.createBuffer(num_channels, buffer_size_frames, sample_rate);
|
||||
for (var channel = 0; channel < num_channels; ++channel) {
|
||||
var buffer_content = buffer.getChannelData(channel);
|
||||
for (var i = 0; i < buf_len / num_channels; ++i) {
|
||||
for (var i = 0; i < buffer_size_frames; ++i) {
|
||||
buffer_content[i] = src_buffer[i * num_channels + channel];
|
||||
}
|
||||
}
|
||||
|
@ -299,7 +332,10 @@ where
|
|||
// 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
|
||||
set_timeout(|| audio_callback_fn::<D, E>(user_data_ptr), 330);
|
||||
set_timeout(
|
||||
|| audio_callback_fn::<D, E>(user_data_ptr, config, sample_format, buffer_size_frames),
|
||||
330,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -348,6 +384,15 @@ fn is_webaudio_available() -> bool {
|
|||
.unwrap()
|
||||
}
|
||||
|
||||
// Whether or not the given stream configuration is valid for building a stream.
|
||||
fn valid_config(conf: &StreamConfig, sample_format: SampleFormat) -> bool {
|
||||
conf.channels <= MAX_CHANNELS
|
||||
&& conf.channels >= MIN_CHANNELS
|
||||
&& conf.sample_rate <= MAX_SAMPLE_RATE
|
||||
&& conf.sample_rate >= MIN_SAMPLE_RATE
|
||||
&& sample_format == SUPPORTED_SAMPLE_FORMAT
|
||||
}
|
||||
|
||||
// Convert the given duration in frames at the given sample rate to a `std::time::Duration`.
|
||||
fn frames_to_duration(frames: usize, rate: usize) -> std::time::Duration {
|
||||
let secsf = frames as f64 / rate as f64;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceNameError,
|
||||
DevicesError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate,
|
||||
StreamConfig, SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
|
||||
BackendSpecificError, BufferSize, Data, DefaultStreamConfigError, DeviceNameError,
|
||||
DevicesError, InputCallbackInfo, OutputCallbackInfo, SampleFormat, SampleRate, StreamConfig,
|
||||
SupportedBufferSize, SupportedStreamConfig, SupportedStreamConfigRange,
|
||||
SupportedStreamConfigsError, COMMON_SAMPLE_RATES,
|
||||
};
|
||||
use std;
|
||||
|
@ -652,8 +652,8 @@ impl Device {
|
|||
// TO DO: We need IAudioClient3 to get buffersize ranges first
|
||||
// Otherwise the supported ranges are unknown. In the mean time
|
||||
// the smallest buffersize is selected and used.
|
||||
return Err(BuildStreamError::StreamConfigNotSupported)
|
||||
},
|
||||
return Err(BuildStreamError::StreamConfigNotSupported);
|
||||
}
|
||||
BufferSize::Default => (),
|
||||
};
|
||||
|
||||
|
@ -814,8 +814,8 @@ impl Device {
|
|||
// TO DO: We need IAudioClient3 to get buffersize ranges first
|
||||
// Otherwise the supported ranges are unknown. In the mean time
|
||||
// the smallest buffersize is selected and used.
|
||||
return Err(BuildStreamError::StreamConfigNotSupported)
|
||||
},
|
||||
return Err(BuildStreamError::StreamConfigNotSupported);
|
||||
}
|
||||
BufferSize::Default => (),
|
||||
};
|
||||
|
||||
|
|
|
@ -7,10 +7,10 @@ use self::wasm_bindgen::prelude::*;
|
|||
use self::wasm_bindgen::JsCast;
|
||||
use self::web_sys::{AudioContext, AudioContextOptions};
|
||||
use crate::{
|
||||
BackendSpecificError, BuildStreamError, Data, DefaultStreamConfigError, DeviceNameError,
|
||||
DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError, PlayStreamError,
|
||||
SampleFormat, SampleRate, StreamConfig, StreamError, SupportedStreamConfig,
|
||||
SupportedStreamConfigRange, SupportedStreamConfigsError,
|
||||
BackendSpecificError, BufferSize, BuildStreamError, Data, DefaultStreamConfigError,
|
||||
DeviceNameError, DevicesError, InputCallbackInfo, OutputCallbackInfo, PauseStreamError,
|
||||
PlayStreamError, SampleFormat, SampleRate, StreamConfig, StreamError, SupportedBufferSize,
|
||||
SupportedStreamConfig, SupportedStreamConfigRange, SupportedStreamConfigsError,
|
||||
};
|
||||
use std::ops::DerefMut;
|
||||
use std::sync::{Arc, Mutex, RwLock};
|
||||
|
@ -39,6 +39,9 @@ const MAX_CHANNELS: u16 = 32;
|
|||
const MIN_SAMPLE_RATE: SampleRate = SampleRate(8_000);
|
||||
const MAX_SAMPLE_RATE: SampleRate = SampleRate(96_000);
|
||||
const DEFAULT_SAMPLE_RATE: SampleRate = SampleRate(44_100);
|
||||
const MIN_BUFFER_SIZE: u32 = 1;
|
||||
const MAX_BUFFER_SIZE: u32 = std::u32::MAX;
|
||||
const DEFAULT_BUFFER_SIZE: usize = 2048;
|
||||
const SUPPORTED_SAMPLE_FORMAT: SampleFormat = SampleFormat::F32;
|
||||
|
||||
impl Host {
|
||||
|
@ -93,11 +96,16 @@ impl Device {
|
|||
fn supported_output_configs(
|
||||
&self,
|
||||
) -> Result<SupportedOutputConfigs, SupportedStreamConfigsError> {
|
||||
let buffer_size = SupportedBufferSize::Range {
|
||||
min: MIN_BUFFER_SIZE,
|
||||
max: MAX_BUFFER_SIZE,
|
||||
};
|
||||
let configs: Vec<_> = (MIN_CHANNELS..=MAX_CHANNELS)
|
||||
.map(|channels| SupportedStreamConfigRange {
|
||||
channels,
|
||||
min_sample_rate: MIN_SAMPLE_RATE,
|
||||
max_sample_rate: MAX_SAMPLE_RATE,
|
||||
buffer_size: buffer_size.clone(),
|
||||
sample_format: SUPPORTED_SAMPLE_FORMAT,
|
||||
})
|
||||
.collect();
|
||||
|
@ -190,11 +198,20 @@ impl DeviceTrait for Device {
|
|||
}
|
||||
|
||||
let n_channels = config.channels as usize;
|
||||
// Use a buffer period of 1/3s for this early proof of concept.
|
||||
// TODO: Change this to the requested buffer size when updating for the buffer size API.
|
||||
let buffer_size_frames = (config.sample_rate.0 as f64 / 3.0).round() as usize;
|
||||
|
||||
let buffer_size_frames = match config.buffer_size {
|
||||
BufferSize::Fixed(v) => {
|
||||
if v == 0 {
|
||||
return Err(BuildStreamError::StreamConfigNotSupported);
|
||||
} else {
|
||||
v as usize
|
||||
}
|
||||
}
|
||||
BufferSize::Default => DEFAULT_BUFFER_SIZE,
|
||||
};
|
||||
let buffer_size_samples = buffer_size_frames * n_channels;
|
||||
let buffer_time_step_secs = buffer_time_step_secs(buffer_size_frames, config.sample_rate);
|
||||
|
||||
let data_callback = Arc::new(Mutex::new(Box::new(data_callback)));
|
||||
|
||||
// Create the WebAudio stream.
|
||||
|
|
|
@ -200,13 +200,11 @@ pub struct StreamConfig {
|
|||
}
|
||||
|
||||
/// Describes the minimum and maximum supported buffer size for the device
|
||||
/// and if requested buffersize must be a power of 2 value.
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum SupportedBufferSize {
|
||||
Range {
|
||||
min: FrameCount,
|
||||
max: FrameCount,
|
||||
requires_power_of_two: bool,
|
||||
},
|
||||
/// In the case that the platform provides no way of getting the default
|
||||
/// buffersize before starting a stream.
|
||||
|
|
Loading…
Reference in New Issue