From ca874305e829b224d84a412b17bc308f5e36ee6c Mon Sep 17 00:00:00 2001 From: Otto Date: Sun, 26 May 2024 14:59:53 +0200 Subject: [PATCH 1/7] AudioWorkletNode: add processor that yields to V8 once per quantum However - load only goes up - process does not terminate anymore --- examples/audio-worklet.mjs | 5 ++++- src/audio_context.rs | 3 ++- src/audio_worklet_node.rs | 42 ++++++++++++++++++++++++++++++++++-- src/offline_audio_context.rs | 3 ++- 4 files changed, 48 insertions(+), 5 deletions(-) diff --git a/examples/audio-worklet.mjs b/examples/audio-worklet.mjs index 309bd29..7ec9bb1 100644 --- a/examples/audio-worklet.mjs +++ b/examples/audio-worklet.mjs @@ -5,7 +5,7 @@ import { sleep } from '@ircam/sc-utils'; const latencyHint = process.env.WEB_AUDIO_LATENCY === 'playback' ? 'playback' : 'interactive'; -const TEST_ONLINE = false; +const TEST_ONLINE = true; const audioContext = TEST_ONLINE ? new AudioContext({ latencyHint }) @@ -42,13 +42,16 @@ const whiteNoise = new AudioWorkletNode(audioContext, 'white-noise'); whiteNoise.connect(audioContext.destination); if (TEST_ONLINE) { + var maxPeakLoad = 0.; audioContext.renderCapacity.addEventListener('update', e => { const { timestamp, averageLoad, peakLoad, underrunRatio } = e; console.log('AudioRenderCapacityEvent:', { timestamp, averageLoad, peakLoad, underrunRatio }); + maxPeakLoad = Math.max(maxPeakLoad, peakLoad); }); audioContext.renderCapacity.start({ updateInterval: 1. }); await sleep(8); + console.log('maxPeakLoad', maxPeakLoad); await audioContext.close(); } else { const buffer = await audioContext.startRendering(); diff --git a/src/audio_context.rs b/src/audio_context.rs index 94d7f7f..6c3af1d 100644 --- a/src/audio_context.rs +++ b/src/audio_context.rs @@ -100,7 +100,8 @@ fn constructor(ctx: CallContext) -> Result { }; let audio_context = AudioContext::new(audio_context_options); - let worklet_id = crate::audio_worklet_node::allocate_process_call_channel(); + let base = audio_context.base(); + let worklet_id = crate::audio_worklet_node::allocate_process_call_channel(base); // ------------------------------------------------- // Wrap context diff --git a/src/audio_worklet_node.rs b/src/audio_worklet_node.rs index 8caa400..2735f23 100644 --- a/src/audio_worklet_node.rs +++ b/src/audio_worklet_node.rs @@ -5,6 +5,7 @@ use crossbeam_channel::{self, Receiver, Sender}; use napi::*; use napi_derive::js_function; +use web_audio_api::context::ConcreteBaseAudioContext; use web_audio_api::node::{AudioNode, AudioNodeOptions, ChannelCountMode, ChannelInterpretation}; use web_audio_api::worklet::{ AudioParamValues, AudioWorkletGlobalScope, AudioWorkletNode, AudioWorkletNodeOptions, @@ -25,6 +26,7 @@ static INCREMENTING_ID: AtomicU32 = AtomicU32::new(0); enum WorkletCommand { Drop(u32), Process(ProcessorArguments), + Tick, } /// Render thread to Worker processor arguments @@ -61,7 +63,7 @@ struct ProcessCallChannel { static GLOBAL_PROCESS_CALL_CHANNEL_MAP: RwLock> = RwLock::new(vec![]); /// Request a new channel + ID for a newly created (Offline)AudioContext -pub(crate) fn allocate_process_call_channel() -> usize { +pub(crate) fn allocate_process_call_channel(ctx: &ConcreteBaseAudioContext) -> usize { // Only one process message can be sent at same time from a given context, // but Drop messages could be send too, so let's take some room let (send, recv) = crossbeam_channel::bounded(32); @@ -71,6 +73,16 @@ pub(crate) fn allocate_process_call_channel() -> usize { exited: Arc::new(AtomicBool::new(false)), }; + let options = AudioWorkletNodeOptions { + number_of_inputs: 1, + number_of_outputs: 1, // should be zero, but bug in base lib + output_channel_count: Default::default(), + parameter_data: Default::default(), + audio_node_options: AudioNodeOptions::default(), + processor_options: channel.send.clone(), + }; + AudioWorkletNode::new::(ctx, options); + // We need a write-lock to initialize the channel let mut write_lock = GLOBAL_PROCESS_CALL_CHANNEL_MAP.write().unwrap(); let id = write_lock.len(); @@ -394,7 +406,7 @@ pub(crate) fn run_audio_worklet_global_scope(ctx: CallContext) -> Result { let mut processors = ctx.get::(1)?; @@ -403,6 +415,9 @@ pub(crate) fn run_audio_worklet_global_scope(ctx: CallContext) -> Result { process_audio_worklet(ctx.env, &processors, args)?; } + WorkletCommand::Tick => { + break; + } } } @@ -786,3 +801,26 @@ impl Drop for NapiAudioWorkletProcessor { } } } + +struct RenderTickProcessor { + send: Sender, +} + +impl AudioWorkletProcessor for RenderTickProcessor { + type ProcessorOptions = Sender; + + fn constructor(send: Self::ProcessorOptions) -> Self { + Self { send } + } + + fn process<'a, 'b>( + &mut self, + _inputs: &'b [&'a [&'a [f32]]], + _outputs: &'b mut [&'a mut [&'a mut [f32]]], + _params: AudioParamValues<'b>, + _scope: &'b AudioWorkletGlobalScope, + ) -> bool { + self.send.send(WorkletCommand::Tick).unwrap(); + true + } +} diff --git a/src/offline_audio_context.rs b/src/offline_audio_context.rs index ba658dd..b107f4c 100644 --- a/src/offline_audio_context.rs +++ b/src/offline_audio_context.rs @@ -53,7 +53,8 @@ fn constructor(ctx: CallContext) -> Result { let sample_rate = ctx.get::(2)?.get_double()? as f32; let audio_context = OfflineAudioContext::new(number_of_channels, length, sample_rate); - let worklet_id = crate::audio_worklet_node::allocate_process_call_channel(); + let base = audio_context.base(); + let worklet_id = crate::audio_worklet_node::allocate_process_call_channel(base); // ------------------------------------------------- // Wrap context From 24d3f5bcdd6d16f615f549ba56f4289adf2832de Mon Sep 17 00:00:00 2001 From: Otto Date: Sun, 26 May 2024 15:32:35 +0200 Subject: [PATCH 2/7] AudioWorkletNode: rewrite crossbeam channel to Mutex and Condvar The theory being that two high-prio thread sharing the Mutex will run delay-free. In practise I get frame drops.. --- src/audio_worklet_node.rs | 116 +++++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 51 deletions(-) diff --git a/src/audio_worklet_node.rs b/src/audio_worklet_node.rs index 2735f23..f337652 100644 --- a/src/audio_worklet_node.rs +++ b/src/audio_worklet_node.rs @@ -5,7 +5,7 @@ use crossbeam_channel::{self, Receiver, Sender}; use napi::*; use napi_derive::js_function; -use web_audio_api::context::ConcreteBaseAudioContext; +use web_audio_api::context::{BaseAudioContext, ConcreteBaseAudioContext}; use web_audio_api::node::{AudioNode, AudioNodeOptions, ChannelCountMode, ChannelInterpretation}; use web_audio_api::worklet::{ AudioParamValues, AudioWorkletGlobalScope, AudioWorkletNode, AudioWorkletNodeOptions, @@ -17,12 +17,13 @@ use std::cell::Cell; use std::collections::HashMap; use std::option::Option; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; -use std::sync::{Arc, Mutex, OnceLock, RwLock}; +use std::sync::{Arc, Condvar, Mutex, OnceLock, RwLock}; /// Unique ID generator for AudioWorkletProcessors static INCREMENTING_ID: AtomicU32 = AtomicU32::new(0); /// Command issued from render thread to the Worker +#[derive(Debug)] enum WorkletCommand { Drop(u32), Process(ProcessorArguments), @@ -30,6 +31,7 @@ enum WorkletCommand { } /// Render thread to Worker processor arguments +#[derive(Debug)] struct ProcessorArguments { // processor unique ID id: u32, @@ -49,10 +51,38 @@ struct ProcessorArguments { /// Message channel from render thread to Worker struct ProcessCallChannel { - send: Sender, - recv: Receiver, + // queue of worklet commands + command_buffer: Mutex>, + // Condition Variable to wait/notify on new worklet commands + cond_var: Condvar, // mark that the worklet has been exited to prevent any further `process` call - exited: Arc, + exited: AtomicBool, +} + +impl ProcessCallChannel { + fn push(&self, command: WorkletCommand) { + let mut buffer = self.command_buffer.lock().unwrap(); + buffer.push(command); + self.cond_var.notify_one(); + } + + fn pop(&self) -> WorkletCommand { + let mut buffer = self.command_buffer.lock().unwrap(); + while buffer.is_empty() { + buffer = self.cond_var.wait(buffer).unwrap(); + } + buffer.remove(0) + } + + fn try_pop(&self) -> Option { + let mut buffer = self.command_buffer.lock().unwrap(); + + if buffer.is_empty() { + return None; + } + + Some(buffer.remove(0)) + } } /// Global map of ID -> ProcessCallChannel @@ -60,18 +90,20 @@ struct ProcessCallChannel { /// Every (Offline)AudioContext is assigned a new channel + ID. The ID is passed to the /// AudioWorklet Worker and to every AudioNode in the context so they can grab the channel and use /// message passing. -static GLOBAL_PROCESS_CALL_CHANNEL_MAP: RwLock> = RwLock::new(vec![]); +static GLOBAL_PROCESS_CALL_CHANNEL_MAP: RwLock>> = RwLock::new(vec![]); /// Request a new channel + ID for a newly created (Offline)AudioContext pub(crate) fn allocate_process_call_channel(ctx: &ConcreteBaseAudioContext) -> usize { // Only one process message can be sent at same time from a given context, // but Drop messages could be send too, so let's take some room - let (send, recv) = crossbeam_channel::bounded(32); + let command_buffer = Mutex::new(Vec::with_capacity(32)); + let channel = ProcessCallChannel { - send, - recv, - exited: Arc::new(AtomicBool::new(false)), + command_buffer, + cond_var: Condvar::new(), + exited: AtomicBool::new(false), }; + let channel = Arc::new(channel); let options = AudioWorkletNodeOptions { number_of_inputs: 1, @@ -79,9 +111,10 @@ pub(crate) fn allocate_process_call_channel(ctx: &ConcreteBaseAudioContext) -> u output_channel_count: Default::default(), parameter_data: Default::default(), audio_node_options: AudioNodeOptions::default(), - processor_options: channel.send.clone(), + processor_options: channel.clone(), }; - AudioWorkletNode::new::(ctx, options); + let node = AudioWorkletNode::new::(ctx, options); + ctx.destination().connect(&node); // We need a write-lock to initialize the channel let mut write_lock = GLOBAL_PROCESS_CALL_CHANNEL_MAP.write().unwrap(); @@ -92,27 +125,9 @@ pub(crate) fn allocate_process_call_channel(ctx: &ConcreteBaseAudioContext) -> u } /// Obtain the WorkletCommand sender for this context ID -fn process_call_sender(id: usize) -> Sender { - // optimistically assume the channel exists and we can use a shared read-lock - GLOBAL_PROCESS_CALL_CHANNEL_MAP.read().unwrap()[id] - .send - .clone() -} - -/// Obtain the WorkletCommand receiver for this context ID -fn process_call_receiver(id: usize) -> Receiver { +fn process_call_channel(id: usize) -> Arc { // optimistically assume the channel exists and we can use a shared read-lock - GLOBAL_PROCESS_CALL_CHANNEL_MAP.read().unwrap()[id] - .recv - .clone() -} - -/// Obtain the WorkletCommand exited flag for this context ID -fn process_call_exited(id: usize) -> Arc { - // optimistically assume the channel exists and we can use a shared read-lock - GLOBAL_PROCESS_CALL_CHANNEL_MAP.read().unwrap()[id] - .exited - .clone() + Arc::clone(&GLOBAL_PROCESS_CALL_CHANNEL_MAP.read().unwrap()[id]) } /// Message channel inside the control thread to pass param descriptors of a given AudioWorkletNode @@ -406,8 +421,8 @@ pub(crate) fn run_audio_worklet_global_scope(ctx: CallContext) -> Result { let mut processors = ctx.get::(1)?; processors.delete_named_property(&id.to_string()).unwrap(); @@ -429,9 +444,11 @@ pub(crate) fn exit_audio_worklet_global_scope(ctx: CallContext) -> Result(0)?.get_uint32()? as usize; // Flag message channel as exited to prevent any other render call - process_call_exited(worklet_id).store(true, Ordering::SeqCst); + process_call_channel(worklet_id) + .exited + .store(true, Ordering::SeqCst); // Handle any pending message from audio thread - if let Ok(WorkletCommand::Process(args)) = process_call_receiver(worklet_id).try_recv() { + if let Some(WorkletCommand::Process(args)) = process_call_channel(worklet_id).try_pop() { let _ = args.tail_time_sender.send(false); } @@ -629,8 +646,7 @@ fn constructor(ctx: CallContext) -> Result { let id = INCREMENTING_ID.fetch_add(1, Ordering::Relaxed); let processor_options = NapiAudioWorkletProcessor { id, - send: process_call_sender(worklet_id), - exited: process_call_exited(worklet_id), + command_channel: process_call_channel(worklet_id), tail_time_channel: crossbeam_channel::bounded(1), param_values: Vec::with_capacity(32), }; @@ -721,10 +737,8 @@ audio_node_impl!(NapiAudioWorkletNode); struct NapiAudioWorkletProcessor { /// Unique id to pair Napi Worklet and JS processor id: u32, - /// Sender to the JS Worklet - send: Sender, - /// Flag that marks the JS worklet as exited - exited: Arc, + /// Command channel to the JS Worklet + command_channel: Arc, /// tail_time result channel tail_time_channel: (Sender, Receiver), /// Reusable Vec for AudioParam values @@ -754,7 +768,7 @@ impl AudioWorkletProcessor for NapiAudioWorkletProcessor { scope: &'b AudioWorkletGlobalScope, ) -> bool { // Early return if audio thread is still closing while worklet has been exited - if self.exited.load(Ordering::SeqCst) { + if self.command_channel.exited.load(Ordering::SeqCst) { return false; } @@ -788,7 +802,7 @@ impl AudioWorkletProcessor for NapiAudioWorkletProcessor { }; // send command to Worker - self.send.send(WorkletCommand::Process(item)).unwrap(); + self.command_channel.push(WorkletCommand::Process(item)); // await result self.tail_time_channel.1.recv().unwrap() } @@ -796,21 +810,21 @@ impl AudioWorkletProcessor for NapiAudioWorkletProcessor { impl Drop for NapiAudioWorkletProcessor { fn drop(&mut self) { - if !self.exited.load(Ordering::SeqCst) { - self.send.send(WorkletCommand::Drop(self.id)).unwrap(); + if !self.command_channel.exited.load(Ordering::SeqCst) { + self.command_channel.push(WorkletCommand::Drop(self.id)); } } } struct RenderTickProcessor { - send: Sender, + channel: Arc, } impl AudioWorkletProcessor for RenderTickProcessor { - type ProcessorOptions = Sender; + type ProcessorOptions = Arc; - fn constructor(send: Self::ProcessorOptions) -> Self { - Self { send } + fn constructor(channel: Self::ProcessorOptions) -> Self { + Self { channel } } fn process<'a, 'b>( @@ -820,7 +834,7 @@ impl AudioWorkletProcessor for RenderTickProcessor { _params: AudioParamValues<'b>, _scope: &'b AudioWorkletGlobalScope, ) -> bool { - self.send.send(WorkletCommand::Tick).unwrap(); + self.channel.push(WorkletCommand::Tick); true } } From dafa7aac5d66d0c4f6cd2aaa31757638c233c86c Mon Sep 17 00:00:00 2001 From: Otto Date: Mon, 27 May 2024 16:12:31 +0200 Subject: [PATCH 3/7] audio worklet.rs: add timing logs, change thread prio strategy --- src/audio_worklet_node.rs | 40 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/src/audio_worklet_node.rs b/src/audio_worklet_node.rs index f337652..df9fe6f 100644 --- a/src/audio_worklet_node.rs +++ b/src/audio_worklet_node.rs @@ -19,6 +19,8 @@ use std::option::Option; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Arc, Condvar, Mutex, OnceLock, RwLock}; +use std::time::{Duration, Instant}; + /// Unique ID generator for AudioWorkletProcessors static INCREMENTING_ID: AtomicU32 = AtomicU32::new(0); @@ -404,13 +406,30 @@ fn process_audio_worklet(env: &Env, processors: &JsObject, args: ProcessorArgume Ok(()) } +static PREV_START: RwLock> = RwLock::new(None); + /// The entry point into Rust from the Worker #[js_function(2)] pub(crate) fn run_audio_worklet_global_scope(ctx: CallContext) -> Result { + let enter_start = Instant::now(); + let mut lock = PREV_START.write().unwrap(); + if let Some(prev) = *lock { + let micros = enter_start.duration_since(prev).as_micros(); + if micros > 200 { + println!("return to Rust after {} micros", micros); + } + } + // Set thread priority to highest, if not done already if !HAS_THREAD_PRIO.replace(true) { // allowed to fail - let _ = thread_priority::set_current_thread_priority(thread_priority::ThreadPriority::Max); + let prio = thread_priority::ThreadPriority::Deadline { + runtime: Duration::from_millis(2), + deadline: Duration::from_millis(2), + period: Duration::from_millis(2), + flags: Default::default(), + }; + let _ = thread_priority::set_current_thread_priority(prio); } // Obtain the unique worker ID @@ -421,8 +440,16 @@ pub(crate) fn run_audio_worklet_global_scope(ctx: CallContext) -> Result 3000 { + println!("got command after {} micros", micros); + } + + match cmd { WorkletCommand::Drop(id) => { let mut processors = ctx.get::(1)?; processors.delete_named_property(&id.to_string()).unwrap(); @@ -434,8 +461,17 @@ pub(crate) fn run_audio_worklet_global_scope(ctx: CallContext) -> Result 200 { + println!("handled command after {} micros", micros); + } + + prev = now; } + *lock = Some(Instant::now()); ctx.env.get_undefined() } From 120d06ddd20da658da67784757812705eedb30e0 Mon Sep 17 00:00:00 2001 From: Otto Date: Mon, 27 May 2024 16:20:46 +0200 Subject: [PATCH 4/7] Switch thread-priority crate to audio_thread_priority --- Cargo.toml | 2 +- src/audio_worklet_node.rs | 13 +++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 88639b0..e0523e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,10 +10,10 @@ version = "0.21.0" crate-type = ["cdylib"] [dependencies] +audio_thread_priority = "0.32.0" crossbeam-channel = "0.5.12" napi = { version="2.15", features=["napi9", "tokio_rt"] } napi-derive = { version="2.15" } -thread-priority = "1.1.0" web-audio-api = "=0.45.0" # web-audio-api = { path = "../web-audio-api-rs" } diff --git a/src/audio_worklet_node.rs b/src/audio_worklet_node.rs index df9fe6f..9da2885 100644 --- a/src/audio_worklet_node.rs +++ b/src/audio_worklet_node.rs @@ -422,14 +422,11 @@ pub(crate) fn run_audio_worklet_global_scope(ctx: CallContext) -> Result Date: Mon, 27 May 2024 16:22:15 +0200 Subject: [PATCH 5/7] Cleanup --- src/audio_worklet_node.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/audio_worklet_node.rs b/src/audio_worklet_node.rs index 9da2885..895c1b1 100644 --- a/src/audio_worklet_node.rs +++ b/src/audio_worklet_node.rs @@ -19,7 +19,7 @@ use std::option::Option; use std::sync::atomic::{AtomicBool, AtomicU32, Ordering}; use std::sync::{Arc, Condvar, Mutex, OnceLock, RwLock}; -use std::time::{Duration, Instant}; +use std::time::Instant; /// Unique ID generator for AudioWorkletProcessors static INCREMENTING_ID: AtomicU32 = AtomicU32::new(0); From 14829cba45e79fcf3a285b2f794c0485a9efc2f1 Mon Sep 17 00:00:00 2001 From: Otto Date: Tue, 4 Jun 2024 09:33:10 +0200 Subject: [PATCH 6/7] Audio Worklet: remove tick processor, yield always to VM --- src/audio_context.rs | 3 +-- src/audio_worklet_node.rs | 52 ++---------------------------------- src/offline_audio_context.rs | 3 +-- 3 files changed, 4 insertions(+), 54 deletions(-) diff --git a/src/audio_context.rs b/src/audio_context.rs index 6c3af1d..94d7f7f 100644 --- a/src/audio_context.rs +++ b/src/audio_context.rs @@ -100,8 +100,7 @@ fn constructor(ctx: CallContext) -> Result { }; let audio_context = AudioContext::new(audio_context_options); - let base = audio_context.base(); - let worklet_id = crate::audio_worklet_node::allocate_process_call_channel(base); + let worklet_id = crate::audio_worklet_node::allocate_process_call_channel(); // ------------------------------------------------- // Wrap context diff --git a/src/audio_worklet_node.rs b/src/audio_worklet_node.rs index 895c1b1..6b9c216 100644 --- a/src/audio_worklet_node.rs +++ b/src/audio_worklet_node.rs @@ -5,7 +5,6 @@ use crossbeam_channel::{self, Receiver, Sender}; use napi::*; use napi_derive::js_function; -use web_audio_api::context::{BaseAudioContext, ConcreteBaseAudioContext}; use web_audio_api::node::{AudioNode, AudioNodeOptions, ChannelCountMode, ChannelInterpretation}; use web_audio_api::worklet::{ AudioParamValues, AudioWorkletGlobalScope, AudioWorkletNode, AudioWorkletNodeOptions, @@ -29,7 +28,6 @@ static INCREMENTING_ID: AtomicU32 = AtomicU32::new(0); enum WorkletCommand { Drop(u32), Process(ProcessorArguments), - Tick, } /// Render thread to Worker processor arguments @@ -68,14 +66,6 @@ impl ProcessCallChannel { self.cond_var.notify_one(); } - fn pop(&self) -> WorkletCommand { - let mut buffer = self.command_buffer.lock().unwrap(); - while buffer.is_empty() { - buffer = self.cond_var.wait(buffer).unwrap(); - } - buffer.remove(0) - } - fn try_pop(&self) -> Option { let mut buffer = self.command_buffer.lock().unwrap(); @@ -95,7 +85,7 @@ impl ProcessCallChannel { static GLOBAL_PROCESS_CALL_CHANNEL_MAP: RwLock>> = RwLock::new(vec![]); /// Request a new channel + ID for a newly created (Offline)AudioContext -pub(crate) fn allocate_process_call_channel(ctx: &ConcreteBaseAudioContext) -> usize { +pub(crate) fn allocate_process_call_channel() -> usize { // Only one process message can be sent at same time from a given context, // but Drop messages could be send too, so let's take some room let command_buffer = Mutex::new(Vec::with_capacity(32)); @@ -107,17 +97,6 @@ pub(crate) fn allocate_process_call_channel(ctx: &ConcreteBaseAudioContext) -> u }; let channel = Arc::new(channel); - let options = AudioWorkletNodeOptions { - number_of_inputs: 1, - number_of_outputs: 1, // should be zero, but bug in base lib - output_channel_count: Default::default(), - parameter_data: Default::default(), - audio_node_options: AudioNodeOptions::default(), - processor_options: channel.clone(), - }; - let node = AudioWorkletNode::new::(ctx, options); - ctx.destination().connect(&node); - // We need a write-lock to initialize the channel let mut write_lock = GLOBAL_PROCESS_CALL_CHANNEL_MAP.write().unwrap(); let id = write_lock.len(); @@ -438,8 +417,7 @@ pub(crate) fn run_audio_worklet_global_scope(ctx: CallContext) -> Result 3000 { @@ -454,9 +432,6 @@ pub(crate) fn run_audio_worklet_global_scope(ctx: CallContext) -> Result { process_audio_worklet(ctx.env, &processors, args)?; } - WorkletCommand::Tick => { - break; - } } let end = Instant::now(); @@ -848,26 +823,3 @@ impl Drop for NapiAudioWorkletProcessor { } } } - -struct RenderTickProcessor { - channel: Arc, -} - -impl AudioWorkletProcessor for RenderTickProcessor { - type ProcessorOptions = Arc; - - fn constructor(channel: Self::ProcessorOptions) -> Self { - Self { channel } - } - - fn process<'a, 'b>( - &mut self, - _inputs: &'b [&'a [&'a [f32]]], - _outputs: &'b mut [&'a mut [&'a mut [f32]]], - _params: AudioParamValues<'b>, - _scope: &'b AudioWorkletGlobalScope, - ) -> bool { - self.channel.push(WorkletCommand::Tick); - true - } -} diff --git a/src/offline_audio_context.rs b/src/offline_audio_context.rs index b107f4c..ba658dd 100644 --- a/src/offline_audio_context.rs +++ b/src/offline_audio_context.rs @@ -53,8 +53,7 @@ fn constructor(ctx: CallContext) -> Result { let sample_rate = ctx.get::(2)?.get_double()? as f32; let audio_context = OfflineAudioContext::new(number_of_channels, length, sample_rate); - let base = audio_context.base(); - let worklet_id = crate::audio_worklet_node::allocate_process_call_channel(base); + let worklet_id = crate::audio_worklet_node::allocate_process_call_channel(); // ------------------------------------------------- // Wrap context From a813250af7b36af30cd839813d10995dbb726ce2 Mon Sep 17 00:00:00 2001 From: Otto Date: Tue, 4 Jun 2024 19:15:27 +0200 Subject: [PATCH 7/7] audio_thread_priority requires libdbus-1-dev installed --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dca454d..b8b8555 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: - name: Install ALSA and Jack dependencies run: | - sudo apt-get update && sudo apt-get install -y libasound2-dev libjack-jackd2-dev + sudo apt-get update && sudo apt-get install -y libasound2-dev libjack-jackd2-dev libdbus-1-dev - name: Check out repository uses: actions/checkout@v4