Skip to content

Commit 471a9ce

Browse files
authored
Merge pull request #354 from uklotzde/audiobuffersourcenode-position
Expose current playhead position
2 parents 480baf5 + 2a7ebda commit 471a9ce

File tree

1 file changed

+52
-36
lines changed

1 file changed

+52
-36
lines changed

src/node/audio_buffer_source.rs

Lines changed: 52 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
use std::any::Any;
2-
use std::sync::{Mutex, OnceLock};
2+
use std::sync::atomic::Ordering;
3+
use std::sync::{Arc, Mutex, OnceLock};
34

45
use crate::buffer::AudioBuffer;
56
use crate::context::{AudioContextRegistration, AudioParamId, BaseAudioContext};
67
use crate::param::{AudioParam, AudioParamDescriptor, AutomationRate};
78
use crate::render::{AudioParamValues, AudioProcessor, AudioRenderQuantum, RenderScope};
8-
use crate::RENDER_QUANTUM_SIZE;
9+
use crate::{AtomicF64, RENDER_QUANTUM_SIZE};
910

1011
use super::{AudioNode, AudioScheduledSourceNode, ChannelConfig};
1112

@@ -106,6 +107,7 @@ pub struct AudioBufferSourceNode {
106107
channel_config: ChannelConfig,
107108
detune: AudioParam, // has constraints, no a-rate
108109
playback_rate: AudioParam, // has constraints, no a-rate
110+
buffer_time: Arc<AtomicF64>,
109111
buffer: OnceLock<AudioBuffer>,
110112
inner_state: Mutex<InnerState>,
111113
}
@@ -224,6 +226,7 @@ impl AudioBufferSourceNode {
224226
channel_config: ChannelConfig::default(),
225227
detune: d_param,
226228
playback_rate: pr_param,
229+
buffer_time: Arc::clone(&renderer.render_state.buffer_time),
227230
buffer: OnceLock::new(),
228231
inner_state: Mutex::new(inner_state),
229232
};
@@ -293,6 +296,16 @@ impl AudioBufferSourceNode {
293296
&self.playback_rate
294297
}
295298

299+
/// Current playhead position in seconds within the [`AudioBuffer`].
300+
///
301+
/// This value is updated at the end of each render quantum.
302+
///
303+
/// Unofficial v2 API extension, not part of the spec yet.
304+
/// See also: <https://github.com/WebAudio/web-audio-api/issues/2397#issuecomment-709478405>
305+
pub fn position(&self) -> f64 {
306+
self.buffer_time.load(Ordering::Relaxed)
307+
}
308+
296309
/// K-rate [`AudioParam`] that defines a pitch transposition of the file,
297310
/// expressed in cents
298311
///
@@ -341,7 +354,7 @@ impl AudioBufferSourceNode {
341354
}
342355

343356
struct AudioBufferRendererState {
344-
buffer_time: f64,
357+
buffer_time: Arc<AtomicF64>,
345358
started: bool,
346359
entered_loop: bool,
347360
buffer_time_elapsed: f64,
@@ -351,7 +364,7 @@ struct AudioBufferRendererState {
351364
impl Default for AudioBufferRendererState {
352365
fn default() -> Self {
353366
Self {
354-
buffer_time: 0.,
367+
buffer_time: Arc::new(AtomicF64::new(0.)),
355368
started: false,
356369
entered_loop: false,
357370
buffer_time_elapsed: 0.,
@@ -463,9 +476,13 @@ impl AudioProcessor for AudioBufferSourceRenderer {
463476
return false;
464477
}
465478

479+
// Load the buffer time from the render state.
480+
// The render state has to be updated before leaving this method!
481+
let mut buffer_time = self.render_state.buffer_time.load(Ordering::Relaxed);
482+
466483
// 3. the end of the buffer has been reached.
467484
if !is_looping {
468-
if computed_playback_rate > 0. && self.render_state.buffer_time >= buffer_duration {
485+
if computed_playback_rate > 0. && buffer_time >= buffer_duration {
469486
output.make_silent(); // also converts to mono
470487
if !self.ended_triggered {
471488
scope.send_ended_event();
@@ -474,7 +491,7 @@ impl AudioProcessor for AudioBufferSourceRenderer {
474491
return false;
475492
}
476493

477-
if computed_playback_rate < 0. && self.render_state.buffer_time < 0. {
494+
if computed_playback_rate < 0. && buffer_time < 0. {
478495
output.make_silent(); // also converts to mono
479496
if !self.ended_triggered {
480497
scope.send_ended_event();
@@ -533,23 +550,21 @@ impl AudioProcessor for AudioBufferSourceRenderer {
533550
}
534551

535552
// check if buffer ends within this block
536-
if self.render_state.buffer_time + block_duration > buffer_duration
537-
|| self.render_state.buffer_time + block_duration > self.duration
553+
if buffer_time + block_duration > buffer_duration
554+
|| buffer_time + block_duration > self.duration
538555
|| current_time + block_duration > self.stop_time
539556
{
540-
let buffer_time = self.render_state.buffer_time;
541557
let end_index = if current_time + block_duration > self.stop_time
542-
|| self.render_state.buffer_time + block_duration > self.duration
558+
|| buffer_time + block_duration > self.duration
543559
{
544-
let dt = (self.stop_time - current_time)
545-
.min(self.duration - self.render_state.buffer_time);
546-
let end_buffer_time = self.render_state.buffer_time + dt;
560+
let dt = (self.stop_time - current_time).min(self.duration - buffer_time);
561+
let end_buffer_time = buffer_time + dt;
547562
(end_buffer_time * sample_rate).round() as usize
548563
} else {
549564
buffer.length()
550565
};
551566
// in case of a loop point in the middle of the block, this value
552-
// will be used to recompute `self.render_state.buffer_time` according
567+
// will be used to recompute `buffer_time` according
553568
// to the actual loop point.
554569
let mut loop_point_index: Option<usize> = None;
555570

@@ -587,14 +602,13 @@ impl AudioProcessor for AudioBufferSourceRenderer {
587602
});
588603

589604
if let Some(loop_point_index) = loop_point_index {
590-
self.render_state.buffer_time =
591-
((RENDER_QUANTUM_SIZE - loop_point_index) as f64 / sample_rate)
592-
% buffer_duration;
605+
buffer_time = ((RENDER_QUANTUM_SIZE - loop_point_index) as f64 / sample_rate)
606+
% buffer_duration;
593607
} else {
594-
self.render_state.buffer_time += block_duration;
608+
buffer_time += block_duration;
595609
}
596610
} else {
597-
let start_index = (self.render_state.buffer_time * sample_rate).round() as usize;
611+
let start_index = (buffer_time * sample_rate).round() as usize;
598612
let end_index = start_index + RENDER_QUANTUM_SIZE;
599613
// we can do memcopy
600614
buffer
@@ -606,10 +620,13 @@ impl AudioProcessor for AudioBufferSourceRenderer {
606620
output_channel.copy_from_slice(&buffer_channel[start_index..end_index]);
607621
});
608622

609-
self.render_state.buffer_time += block_duration;
623+
buffer_time += block_duration;
610624
}
611625

612626
// update render state
627+
self.render_state
628+
.buffer_time
629+
.store(buffer_time, Ordering::Relaxed);
613630
self.render_state.buffer_time_elapsed += block_duration;
614631

615632
return true;
@@ -658,44 +675,38 @@ impl AudioProcessor for AudioBufferSourceRenderer {
658675
self.offset = actual_loop_start;
659676
}
660677

661-
self.render_state.buffer_time = self.offset;
678+
buffer_time = self.offset;
662679
self.render_state.started = true;
663680
}
664681

665682
if is_looping {
666683
if !self.render_state.entered_loop {
667684
// playback began before or within loop, and playhead is now past loop start
668-
if self.offset < actual_loop_end
669-
&& self.render_state.buffer_time >= actual_loop_start
670-
{
685+
if self.offset < actual_loop_end && buffer_time >= actual_loop_start {
671686
self.render_state.entered_loop = true;
672687
}
673688

674689
// playback began after loop, and playhead is now prior to the loop end
675690
// @note - only possible when playback_rate < 0 (?)
676-
if self.offset >= actual_loop_end
677-
&& self.render_state.buffer_time < actual_loop_end
678-
{
691+
if self.offset >= actual_loop_end && buffer_time < actual_loop_end {
679692
self.render_state.entered_loop = true;
680693
}
681694
}
682695

683696
// check loop boundaries
684697
if self.render_state.entered_loop {
685-
while self.render_state.buffer_time >= actual_loop_end {
686-
self.render_state.buffer_time -= actual_loop_end - actual_loop_start;
698+
while buffer_time >= actual_loop_end {
699+
buffer_time -= actual_loop_end - actual_loop_start;
687700
}
688701

689-
while self.render_state.buffer_time < actual_loop_start {
690-
self.render_state.buffer_time += actual_loop_end - actual_loop_start;
702+
while buffer_time < actual_loop_start {
703+
buffer_time += actual_loop_end - actual_loop_start;
691704
}
692705
}
693706
}
694707

695-
if self.render_state.buffer_time >= 0.
696-
&& self.render_state.buffer_time < buffer_duration
697-
{
698-
let position = self.render_state.buffer_time * sampling_ratio;
708+
if buffer_time >= 0. && buffer_time < buffer_duration {
709+
let position = buffer_time * sampling_ratio;
699710
let playhead = position * sample_rate;
700711
let playhead_floored = playhead.floor();
701712
let prev_frame_index = playhead_floored as usize; // can't be < 0.
@@ -710,7 +721,7 @@ impl AudioProcessor for AudioBufferSourceRenderer {
710721
}
711722

712723
let time_incr = dt * computed_playback_rate;
713-
self.render_state.buffer_time += time_incr;
724+
buffer_time += time_incr;
714725
self.render_state.buffer_time_elapsed += time_incr;
715726
current_time += dt;
716727
}
@@ -746,6 +757,11 @@ impl AudioProcessor for AudioBufferSourceRenderer {
746757
});
747758
});
748759

760+
// update render state
761+
self.render_state
762+
.buffer_time
763+
.store(buffer_time, Ordering::Relaxed);
764+
749765
true
750766
}
751767

0 commit comments

Comments
 (0)