Skip to content

Commit da7727d

Browse files
authored
Merge pull request #375 from orottier/feature/hrtf-init-performance
Improve HRTF panner initialization time by caching the HRIR sphere
2 parents 3af813c + 11ada42 commit da7727d

File tree

4 files changed

+64
-18
lines changed

4 files changed

+64
-18
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ cubeb = { version = "0.10", optional = true }
2222
dasp_sample = "0.11"
2323
float_eq = "1.0"
2424
hound = "3.5"
25-
hrtf = "0.8"
25+
hrtf = "0.8.1"
2626
llq = "0.1.1"
2727
log = "0.4"
2828
num-complex = "0.4"

benches/my_benchmark.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ use iai::black_box;
22

33
use web_audio_api::context::BaseAudioContext;
44
use web_audio_api::context::OfflineAudioContext;
5-
use web_audio_api::node::AudioNode;
6-
use web_audio_api::node::AudioScheduledSourceNode;
5+
use web_audio_api::node::{AudioNode, AudioScheduledSourceNode, PanningModelType};
76

87
const SAMPLE_RATE: f32 = 48000.;
98
const DURATION: usize = 10;
@@ -197,6 +196,27 @@ pub fn bench_analyser_node() {
197196
assert_eq!(ctx.start_rendering_sync().length(), SAMPLES);
198197
}
199198

199+
pub fn bench_hrtf_panners() {
200+
let ctx = OfflineAudioContext::new(2, black_box(SAMPLES), SAMPLE_RATE);
201+
202+
let mut panner1 = ctx.create_panner();
203+
panner1.set_panning_model(PanningModelType::HRTF);
204+
panner1.position_x().set_value(10.0);
205+
panner1.connect(&ctx.destination());
206+
207+
let mut panner2 = ctx.create_panner();
208+
panner2.set_panning_model(PanningModelType::HRTF);
209+
panner2.position_x().set_value(-10.0);
210+
panner2.connect(&ctx.destination());
211+
212+
let mut osc = ctx.create_oscillator();
213+
osc.connect(&panner1);
214+
osc.connect(&panner2);
215+
osc.start();
216+
217+
assert_eq!(ctx.start_rendering_sync().length(), SAMPLES);
218+
}
219+
200220
iai::main!(
201221
bench_ctor,
202222
bench_sine,
@@ -209,4 +229,5 @@ iai::main!(
209229
bench_stereo_positional,
210230
bench_stereo_panning_automation,
211231
bench_analyser_node,
232+
bench_hrtf_panners,
212233
);

src/context/concrete_base.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,22 +193,28 @@ impl ConcreteBaseAudioContext {
193193
};
194194

195195
(listener_params, destination_channel_config)
196-
}; // nodes will drop now, so base.inner has no copies anymore
196+
}; // Nodes will drop now, so base.inner has no copies anymore
197197

198198
let mut base = base;
199199
let inner_mut = Arc::get_mut(&mut base.inner).unwrap();
200200
inner_mut.listener_params = Some(listener_params);
201201
inner_mut.destination_channel_config = destination_channel_config;
202202

203-
// validate if the hardcoded node IDs line up
203+
// Validate if the hardcoded node IDs line up
204204
debug_assert_eq!(
205205
base.inner.node_id_inc.load(Ordering::Relaxed),
206206
LISTENER_PARAM_IDS.end,
207207
);
208208

209-
// (?) only for online context
209+
// For an online AudioContext, pre-create the HRTF-database for panner nodes
210+
if !offline {
211+
crate::node::load_hrtf_processor(sample_rate as u32);
212+
}
213+
214+
// Boot the event loop thread that handles the events spawned by the render thread
215+
// (we don't do this for offline rendering because it makes little sense, the graph cannot
216+
// be mutated once rendering has started anyway)
210217
if let Some(event_channel) = event_recv {
211-
// init event loop
212218
event_loop.run(event_channel);
213219
}
214220

src/node/panner.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::any::Any;
2+
use std::collections::HashMap;
23
use std::f32::consts::PI;
4+
use std::sync::{Mutex, OnceLock};
35

46
use float_eq::float_eq;
57
use hrtf::{HrirSphere, HrtfContext, HrtfProcessor, Vec3};
@@ -13,6 +15,31 @@ use super::{
1315
AudioNode, ChannelConfig, ChannelConfigOptions, ChannelCountMode, ChannelInterpretation,
1416
};
1517

18+
/// Load the HRTF processor for the given sample_rate
19+
///
20+
/// The included data contains the impulse responses at 44100 Hertz, so it needs to be resampled
21+
/// for other values (which can easily take 100s of milliseconds). Therefore cache the result (per
22+
/// sample rate) in a global variable and clone it every time a new panner is created.
23+
pub(crate) fn load_hrtf_processor(sample_rate: u32) -> (HrtfProcessor, usize) {
24+
static INSTANCE: OnceLock<Mutex<HashMap<u32, (HrtfProcessor, usize)>>> = OnceLock::new();
25+
let cache = INSTANCE.get_or_init(|| Mutex::new(HashMap::new()));
26+
let mut guard = cache.lock().unwrap();
27+
guard
28+
.entry(sample_rate)
29+
.or_insert_with(|| {
30+
let resource = include_bytes!("../../resources/IRC_1003_C.bin");
31+
let hrir_sphere = HrirSphere::new(&resource[..], sample_rate).unwrap();
32+
let len = hrir_sphere.len();
33+
34+
let interpolation_steps = 1; // TODO?
35+
let samples_per_step = RENDER_QUANTUM_SIZE / interpolation_steps;
36+
let processor = HrtfProcessor::new(hrir_sphere, interpolation_steps, samples_per_step);
37+
38+
(processor, len)
39+
})
40+
.clone()
41+
}
42+
1643
/// Spatialization algorithm used to position the audio in 3D space
1744
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
1845
pub enum PanningModelType {
@@ -167,14 +194,7 @@ struct HrtfState {
167194
}
168195

169196
impl HrtfState {
170-
fn new(hrir_sphere: HrirSphere) -> Self {
171-
let len = hrir_sphere.len();
172-
173-
let interpolation_steps = 1;
174-
let samples_per_step = RENDER_QUANTUM_SIZE / interpolation_steps;
175-
176-
let processor = HrtfProcessor::new(hrir_sphere, interpolation_steps, samples_per_step);
177-
197+
fn new(processor: HrtfProcessor, len: usize) -> Self {
178198
Self {
179199
len,
180200
processor,
@@ -549,10 +569,9 @@ impl PannerNode {
549569
let hrtf_option = match value {
550570
PanningModelType::EqualPower => None,
551571
PanningModelType::HRTF => {
552-
let resource = include_bytes!("../../resources/IRC_1003_C.bin");
553572
let sample_rate = self.context().sample_rate() as u32;
554-
let hrir_sphere = HrirSphere::new(&resource[..], sample_rate).unwrap();
555-
Some(HrtfState::new(hrir_sphere))
573+
let (processor, len) = load_hrtf_processor(sample_rate);
574+
Some(HrtfState::new(processor, len))
556575
}
557576
};
558577

0 commit comments

Comments
 (0)