Skip to content

Commit 8f17ce9

Browse files
authored
Merge pull request #377 from orottier/feature/no-denormals
Flush denormal float values in audio graph processing
2 parents 2796013 + d00e9a9 commit 8f17ce9

File tree

4 files changed

+49
-6
lines changed

4 files changed

+49
-6
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ hound = "3.5"
2525
hrtf = "0.8.1"
2626
llq = "0.1.1"
2727
log = "0.4"
28+
no_denormals = "0.1.2"
2829
num-complex = "0.4"
2930
realfft = "3.3"
3031
rubato = "0.14"

src/render/graph.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@ impl Graph {
403403
// This may lead to logic bugs later on, but it is the best that we can do.
404404
// The alternative is to crash and reboot the render thread.
405405
let catch_me = AssertUnwindSafe(|| node.process(params, scope));
406+
406407
match panic::catch_unwind(catch_me) {
407408
Ok(tail_time) => (true, tail_time),
408409
Err(e) => {

src/render/thread.rs

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,10 @@ impl RenderThread {
151151
let num_frames = (length + RENDER_QUANTUM_SIZE - 1) / RENDER_QUANTUM_SIZE;
152152

153153
for _ in 0..num_frames {
154-
// handle addition/removal of nodes/edges
154+
// Handle addition/removal of nodes/edges
155155
self.handle_control_messages();
156156

157-
// update time
157+
// Update time
158158
let current_frame = self
159159
.frames_played
160160
.fetch_add(RENDER_QUANTUM_SIZE as u64, Ordering::SeqCst);
@@ -168,8 +168,14 @@ impl RenderThread {
168168
node_id: Cell::new(AudioNodeId(0)), // placeholder value
169169
};
170170

171-
// render audio graph
172-
let rendered = self.graph.as_mut().unwrap().render(&scope);
171+
// Render audio graph
172+
let graph = self.graph.as_mut().unwrap();
173+
174+
// For x64 and aarch, process with denormal floats disabled (for performance, #194)
175+
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
176+
let rendered = no_denormals::no_denormals(|| graph.render(&scope));
177+
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
178+
let rendered = graph.render(&scope);
173179

174180
rendered.channels().iter().enumerate().for_each(
175181
|(channel_number, rendered_channel)| {
@@ -186,10 +192,15 @@ impl RenderThread {
186192
}
187193

188194
pub fn render<S: FromSample<f32> + Clone>(&mut self, output_buffer: &mut [S]) {
189-
// collect timing information
195+
// Collect timing information
190196
let render_start = Instant::now();
191197

192-
// perform actual rendering
198+
// Perform actual rendering
199+
200+
// For x64 and aarch, process with denormal floats disabled (for performance, #194)
201+
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
202+
no_denormals::no_denormals(|| self.render_inner(output_buffer));
203+
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
193204
self.render_inner(output_buffer);
194205

195206
// calculate load value and ship to control thread

tests/denormals.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
use web_audio_api::context::{BaseAudioContext, OfflineAudioContext};
2+
use web_audio_api::node::{AudioNode, AudioScheduledSourceNode};
3+
4+
#[test]
5+
fn test_flush_denormals() {
6+
let context = OfflineAudioContext::new(1, 128, 48000.);
7+
8+
let mut signal = context.create_constant_source();
9+
signal.start();
10+
11+
let gain1 = context.create_gain();
12+
gain1.gain().set_value(0.001);
13+
signal.connect(&gain1);
14+
15+
let gain2 = context.create_gain();
16+
gain2.gain().set_value(f32::MIN_POSITIVE);
17+
gain1.connect(&gain2);
18+
19+
let gain3 = context.create_gain();
20+
gain3.gain().set_value(f32::MAX);
21+
gain2.connect(&gain3);
22+
23+
gain3.connect(&context.destination());
24+
25+
let output = context.start_rendering_sync();
26+
27+
// When denormals are flushed, we expect the output to be exactly 0.0
28+
// If not, the output will be ~0.004
29+
assert_eq!(output.get_channel_data(0), &[0.; 128][..]);
30+
}

0 commit comments

Comments
 (0)