Skip to content

Commit

Permalink
Merge pull request #377 from orottier/feature/no-denormals
Browse files Browse the repository at this point in the history
Flush denormal float values in audio graph processing
  • Loading branch information
orottier authored Oct 20, 2023
2 parents 2796013 + d00e9a9 commit 8f17ce9
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ hound = "3.5"
hrtf = "0.8.1"
llq = "0.1.1"
log = "0.4"
no_denormals = "0.1.2"
num-complex = "0.4"
realfft = "3.3"
rubato = "0.14"
Expand Down
1 change: 1 addition & 0 deletions src/render/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ impl Graph {
// This may lead to logic bugs later on, but it is the best that we can do.
// The alternative is to crash and reboot the render thread.
let catch_me = AssertUnwindSafe(|| node.process(params, scope));

match panic::catch_unwind(catch_me) {
Ok(tail_time) => (true, tail_time),
Err(e) => {
Expand Down
23 changes: 17 additions & 6 deletions src/render/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ impl RenderThread {
let num_frames = (length + RENDER_QUANTUM_SIZE - 1) / RENDER_QUANTUM_SIZE;

for _ in 0..num_frames {
// handle addition/removal of nodes/edges
// Handle addition/removal of nodes/edges
self.handle_control_messages();

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

// render audio graph
let rendered = self.graph.as_mut().unwrap().render(&scope);
// Render audio graph
let graph = self.graph.as_mut().unwrap();

// For x64 and aarch, process with denormal floats disabled (for performance, #194)
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
let rendered = no_denormals::no_denormals(|| graph.render(&scope));
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
let rendered = graph.render(&scope);

rendered.channels().iter().enumerate().for_each(
|(channel_number, rendered_channel)| {
Expand All @@ -186,10 +192,15 @@ impl RenderThread {
}

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

// perform actual rendering
// Perform actual rendering

// For x64 and aarch, process with denormal floats disabled (for performance, #194)
#[cfg(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64"))]
no_denormals::no_denormals(|| self.render_inner(output_buffer));
#[cfg(not(any(target_arch = "x86", target_arch = "x86_64", target_arch = "aarch64")))]
self.render_inner(output_buffer);

// calculate load value and ship to control thread
Expand Down
30 changes: 30 additions & 0 deletions tests/denormals.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use web_audio_api::context::{BaseAudioContext, OfflineAudioContext};
use web_audio_api::node::{AudioNode, AudioScheduledSourceNode};

#[test]
fn test_flush_denormals() {
let context = OfflineAudioContext::new(1, 128, 48000.);

let mut signal = context.create_constant_source();
signal.start();

let gain1 = context.create_gain();
gain1.gain().set_value(0.001);
signal.connect(&gain1);

let gain2 = context.create_gain();
gain2.gain().set_value(f32::MIN_POSITIVE);
gain1.connect(&gain2);

let gain3 = context.create_gain();
gain3.gain().set_value(f32::MAX);
gain2.connect(&gain3);

gain3.connect(&context.destination());

let output = context.start_rendering_sync();

// When denormals are flushed, we expect the output to be exactly 0.0
// If not, the output will be ~0.004
assert_eq!(output.get_channel_data(0), &[0.; 128][..]);
}

0 comments on commit 8f17ce9

Please sign in to comment.