Modified: trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp (278286 => 278287)
--- trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp 2021-06-01 00:04:51 UTC (rev 278286)
+++ trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.cpp 2021-06-01 00:07:14 UTC (rev 278287)
@@ -66,11 +66,11 @@
{
auto node = adoptRef(*new AudioBufferSourceNode(context));
- node->setBuffer(WTFMove(options.buffer));
+ node->setBufferForBindings(WTFMove(options.buffer));
node->detune().setValue(options.detune);
- node->setLoop(options.loop);
- node->setLoopEnd(options.loopEnd);
- node->setLoopStart(options.loopStart);
+ node->setLoopForBindings(options.loop);
+ node->setLoopEndForBindings(options.loopEnd);
+ node->setLoopStartForBindings(options.loopStart);
node->playbackRate().setValue(options.playbackRate);
return node;
@@ -110,7 +110,7 @@
}
Locker locker { AdoptLock, m_processLock };
- if (!buffer()) {
+ if (!m_buffer) {
outputBus.zero();
return;
}
@@ -118,7 +118,7 @@
// After calling setBuffer() with a buffer having a different number of channels, there can in rare cases be a slight delay
// before the output bus is updated to the new number of channels because of use of tryLocks() in the context's updating system.
// In this case, if the buffer has just been changed and we're not quite ready yet, then just output silence.
- if (numberOfChannels() != buffer()->numberOfChannels()) {
+ if (numberOfChannels() != m_buffer->numberOfChannels()) {
outputBus.zero();
return;
}
@@ -148,7 +148,7 @@
// Returns true if we're finished.
bool AudioBufferSourceNode::renderSilenceAndFinishIfNotLooping(AudioBus*, unsigned index, size_t framesToProcess)
{
- if (!loop()) {
+ if (!m_isLooping) {
// If we're not looping, then stop playing when we get to the end.
if (framesToProcess > 0) {
@@ -171,8 +171,8 @@
// Basic sanity checking
ASSERT(bus);
- ASSERT(buffer());
- if (!bus || !buffer())
+ ASSERT(m_buffer);
+ if (!bus || !m_buffer)
return false;
unsigned numberOfChannels = this->numberOfChannels();
@@ -205,8 +205,8 @@
// Offset the pointers to the correct offset frame.
unsigned writeIndex = destinationFrameOffset;
- size_t bufferLength = buffer()->length();
- double bufferSampleRate = buffer()->sampleRate();
+ size_t bufferLength = m_buffer->length();
+ double bufferSampleRate = m_buffer->sampleRate();
double pitchRate = totalPitchRate();
bool reverse = pitchRate < 0;
@@ -228,10 +228,10 @@
double virtualMinFrame = 0;
double virtualDeltaFrames = maxFrame;
- if (loop() && (m_loopStart || m_loopEnd) && m_loopStart >= 0 && m_loopEnd > 0 && m_loopStart < m_loopEnd) {
+ if (m_isLooping && (m_loopStart || m_loopEnd) && m_loopStart >= 0 && m_loopEnd > 0 && m_loopStart < m_loopEnd) {
// Convert from seconds to sample-frames.
- double loopMinFrame = m_loopStart * buffer()->sampleRate();
- double loopMaxFrame = m_loopEnd * buffer()->sampleRate();
+ double loopMinFrame = m_loopStart * m_buffer->sampleRate();
+ double loopMaxFrame = m_loopEnd * m_buffer->sampleRate();
virtualMaxFrame = std::min(loopMaxFrame, virtualMaxFrame);
virtualMinFrame = std::max(loopMinFrame, virtualMinFrame);
@@ -240,8 +240,8 @@
// If we're looping and the offset (virtualReadIndex) is past the end of the loop, wrap back to the
// beginning of the loop. For other cases, nothing needs to be done.
- if (loop() && m_virtualReadIndex >= virtualMaxFrame) {
- m_virtualReadIndex = (m_loopStart < 0) ? 0 : (m_loopStart * buffer()->sampleRate());
+ if (m_isLooping && m_virtualReadIndex >= virtualMaxFrame) {
+ m_virtualReadIndex = (m_loopStart < 0) ? 0 : (m_loopStart * m_buffer->sampleRate());
m_virtualReadIndex = std::min(m_virtualReadIndex, static_cast<double>(bufferLength - 1));
}
@@ -339,7 +339,7 @@
unsigned readIndex2 = readIndex + 1;
if (readIndex2 >= maxFrame)
- readIndex2 = loop() ? minFrame : readIndex;
+ readIndex2 = m_isLooping ? minFrame : readIndex;
// Linear interpolation.
for (unsigned i = 0; i < numberOfChannels; ++i) {
@@ -368,7 +368,7 @@
// For linear interpolation we need the next sample-frame too.
unsigned readIndex2 = readIndex + 1;
if (readIndex2 >= bufferLength) {
- if (loop()) {
+ if (m_isLooping) {
// Make sure to wrap around at the end of the buffer.
readIndex2 = static_cast<unsigned>(virtualReadIndex + 1 - virtualDeltaFrames);
} else
@@ -407,19 +407,19 @@
return true;
}
-ExceptionOr<void> AudioBufferSourceNode::setBuffer(RefPtr<AudioBuffer>&& buffer)
+ExceptionOr<void> AudioBufferSourceNode::setBufferForBindings(RefPtr<AudioBuffer>&& buffer)
{
ASSERT(isMainThread());
DEBUG_LOG(LOGIDENTIFIER);
- if (buffer && m_wasBufferSet)
- return Exception { InvalidStateError, "The buffer was already set"_s };
-
// The context must be locked since changing the buffer can re-configure the number of channels that are output.
Locker contextLocker { context().graphLock() };
// This synchronizes with process().
Locker locker { m_processLock };
+
+ if (buffer && m_wasBufferSet)
+ return Exception { InvalidStateError, "The buffer was already set"_s };
if (buffer) {
m_wasBufferSet = true;
@@ -457,6 +457,27 @@
return startPlaying(when, grainOffset, grainDuration);
}
+void AudioBufferSourceNode::setLoopForBindings(bool looping)
+{
+ ASSERT(isMainThread());
+ Locker locker { m_processLock };
+ m_isLooping = looping;
+}
+
+void AudioBufferSourceNode::setLoopStartForBindings(double loopStart)
+{
+ ASSERT(isMainThread());
+ Locker locker { m_processLock };
+ m_loopStart = loopStart;
+}
+
+void AudioBufferSourceNode::setLoopEndForBindings(double loopEnd)
+{
+ ASSERT(isMainThread());
+ Locker locker { m_processLock };
+ m_loopEnd = loopEnd;
+}
+
ExceptionOr<void> AudioBufferSourceNode::startPlaying(double when, double grainOffset, std::optional<double> grainDuration)
{
ASSERT(isMainThread());
@@ -496,12 +517,11 @@
{
ASSERT(m_processLock.isHeld());
- auto buffer = this->buffer();
- if (!buffer)
+ if (!m_buffer)
return;
// Do sanity checking of grain parameters versus buffer size.
- double bufferDuration = buffer->duration();
+ double bufferDuration = m_buffer->duration();
m_grainOffset = std::min(bufferDuration, m_grainOffset);
@@ -508,7 +528,7 @@
if (!m_wasGrainDurationGiven)
m_grainDuration = bufferDuration - m_grainOffset;
- if (m_wasGrainDurationGiven && loop()) {
+ if (m_wasGrainDurationGiven && m_isLooping) {
// We're looping a grain with a grain duration specified. Schedule the loop
// to stop after grainDuration seconds after starting, possibly running the
// loop multiple times if grainDuration is larger than the buffer duration.
@@ -523,9 +543,9 @@
// When aligned to the sample-frame the playback will be identical to the PCM data stored in the buffer.
// Since playbackRate == 1 is very common, it's worth considering quality.
if (playbackRate().value() < 0)
- m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, buffer->sampleRate()) - 1;
+ m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, m_buffer->sampleRate()) - 1;
else
- m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, buffer->sampleRate());
+ m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, m_buffer->sampleRate());
}
double AudioBufferSourceNode::totalPitchRate()
@@ -533,8 +553,8 @@
// Incorporate buffer's sample-rate versus AudioContext's sample-rate.
// Normally it's not an issue because buffers are loaded at the AudioContext's sample-rate, but we can handle it in any case.
double sampleRateFactor = 1.0;
- if (buffer())
- sampleRateFactor = buffer()->sampleRate() / static_cast<double>(sampleRate());
+ if (m_buffer)
+ sampleRateFactor = m_buffer->sampleRate() / static_cast<double>(sampleRate());
double basePitchRate = playbackRate().finalValue();
double detune = pow(2, m_detune->finalValue() / 1200);
@@ -553,7 +573,16 @@
bool AudioBufferSourceNode::propagatesSilence() const
{
- return !isPlayingOrScheduled() || hasFinished() || !m_buffer;
+ ASSERT(context().isAudioThread());
+ if (!isPlayingOrScheduled() || hasFinished())
+ return true;
+
+ if (!m_processLock.tryLock()) {
+ // We weren't able to get the lock so we assume we have a buffer.
+ return false;
+ }
+ Locker locker { AdoptLock, m_processLock };
+ return !m_buffer;
}
} // namespace WebCore
Modified: trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.h (278286 => 278287)
--- trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.h 2021-06-01 00:04:51 UTC (rev 278286)
+++ trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.h 2021-06-01 00:07:14 UTC (rev 278287)
@@ -49,10 +49,12 @@
// AudioNode
void process(size_t framesToProcess) final;
- // setBuffer() is called on the main thread. This is the buffer we use for playback.
- ExceptionOr<void> setBuffer(RefPtr<AudioBuffer>&&);
- AudioBuffer* buffer() { return m_buffer.get(); }
+ // setBufferForBindings() is called on the main thread. This is the buffer we use for playback.
+ ExceptionOr<void> setBufferForBindings(RefPtr<AudioBuffer>&&);
+ // This function does not lock before accessing the buffer and should therefore only be called on the main thread.
+ AudioBuffer* bufferForBindings() WTF_IGNORES_THREAD_SAFETY_ANALYSIS { ASSERT(isMainThread()); return m_buffer.get(); }
+
// numberOfChannels() returns the number of output channels. This value equals the number of channels from the buffer.
// If a new buffer is set with a different number of channels, then this value will dynamically change.
unsigned numberOfChannels();
@@ -60,17 +62,15 @@
// Play-state
ExceptionOr<void> startLater(double when, double grainOffset, std::optional<double> grainDuration);
- // Note: the attribute was originally exposed as .looping, but to be more consistent in naming with <audio>
- // and with how it's described in the specification, the proper attribute name is .loop
- // The old attribute is kept for backwards compatibility.
- bool loop() const { return m_isLooping; }
- void setLoop(bool looping) { m_isLooping = looping; }
+ // This function doesn't grab the lock before accessing m_isLooping and is thus only safe to call on the main thread.
+ bool loopForBindings() const WTF_IGNORES_THREAD_SAFETY_ANALYSIS { ASSERT(isMainThread()); return m_isLooping; }
+ void setLoopForBindings(bool);
// Loop times in seconds.
- double loopStart() const { return m_loopStart; }
- double loopEnd() const { return m_loopEnd; }
- void setLoopStart(double loopStart) { m_loopStart = loopStart; }
- void setLoopEnd(double loopEnd) { m_loopEnd = loopEnd; }
+ double loopStartForBindings() const WTF_IGNORES_THREAD_SAFETY_ANALYSIS { ASSERT(isMainThread()); return m_loopStart; } // This function doesn't grab the lock and is thus only safe to call on the main thread.
+ double loopEndForBindings() const WTF_IGNORES_THREAD_SAFETY_ANALYSIS { ASSERT(isMainThread()); return m_loopEnd; } // This function doesn't grab the lock and is thus only safe to call on the main thread.
+ void setLoopStartForBindings(double);
+ void setLoopEndForBindings(double);
AudioParam& detune() { return m_detune.get(); }
AudioParam& playbackRate() { return m_playbackRate.get(); }
@@ -88,16 +88,16 @@
double latencyTime() const final { return 0; }
ExceptionOr<void> startPlaying(double when, double grainOffset, std::optional<double> grainDuration);
- void adjustGrainParameters();
+ void adjustGrainParameters() WTF_REQUIRES_LOCK(m_processLock);
// Returns true on success.
- bool renderFromBuffer(AudioBus*, unsigned destinationFrameOffset, size_t numberOfFrames, double startFrameOffset);
+ bool renderFromBuffer(AudioBus*, unsigned destinationFrameOffset, size_t numberOfFrames, double startFrameOffset) WTF_REQUIRES_LOCK(m_processLock);
// Render silence starting from "index" frame in AudioBus.
- inline bool renderSilenceAndFinishIfNotLooping(AudioBus*, unsigned index, size_t framesToProcess);
+ inline bool renderSilenceAndFinishIfNotLooping(AudioBus*, unsigned index, size_t framesToProcess) WTF_REQUIRES_LOCK(m_processLock);
// m_buffer holds the sample data which this node outputs.
- RefPtr<AudioBuffer> m_buffer;
+ RefPtr<AudioBuffer> m_buffer WTF_GUARDED_BY_LOCK(m_processLock); // Only modified on the main thread but used on the audio thread.
// Pointers for the buffer and destination.
UniqueArray<const float*> m_sourceChannels;
@@ -108,12 +108,12 @@
// If m_isLooping is false, then this node will be done playing and become inactive after it reaches the end of the sample data in the buffer.
// If true, it will wrap around to the start of the buffer each time it reaches the end.
- bool m_isLooping { false };
+ bool m_isLooping WTF_GUARDED_BY_LOCK(m_processLock) { false }; // Only modified on the main thread but queried on the audio thread.
bool m_wasBufferSet { false };
- double m_loopStart { 0 };
- double m_loopEnd { 0 };
+ double m_loopStart WTF_GUARDED_BY_LOCK(m_processLock) { 0 }; // Only modified on the main thread but queried on the audio thread.
+ double m_loopEnd WTF_GUARDED_BY_LOCK(m_processLock) { 0 }; // Only modified on the main thread but queried on the audio thread.
// m_virtualReadIndex is a sample-frame index into our buffer representing the current playback position.
// Since it's floating-point, it has sub-sample accuracy.
@@ -120,14 +120,14 @@
double m_virtualReadIndex { 0 };
// Granular playback
- bool m_isGrain { false };
- double m_grainOffset { 0 }; // in seconds
- double m_grainDuration; // in seconds
- double m_wasGrainDurationGiven { false };
+ bool m_isGrain WTF_GUARDED_BY_LOCK(m_processLock) { false };
+ double m_grainOffset WTF_GUARDED_BY_LOCK(m_processLock) { 0 }; // in seconds
+ double m_grainDuration WTF_GUARDED_BY_LOCK(m_processLock); // in seconds
+ double m_wasGrainDurationGiven WTF_GUARDED_BY_LOCK(m_processLock) { false };
// totalPitchRate() returns the instantaneous pitch rate (non-time preserving).
// It incorporates the base pitch rate, any sample-rate conversion factor from the buffer.
- double totalPitchRate();
+ double totalPitchRate() WTF_REQUIRES_LOCK(m_processLock);
// This synchronizes process() with setBuffer() which can cause dynamic channel count changes.
mutable Lock m_processLock;
Modified: trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.idl (278286 => 278287)
--- trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.idl 2021-06-01 00:04:51 UTC (rev 278286)
+++ trunk/Source/WebCore/Modules/webaudio/AudioBufferSourceNode.idl 2021-06-01 00:07:14 UTC (rev 278287)
@@ -31,14 +31,14 @@
] interface AudioBufferSourceNode : AudioScheduledSourceNode {
[EnabledBySetting=WebAudio] constructor (BaseAudioContext context, optional AudioBufferSourceOptions options);
- attribute AudioBuffer? buffer;
+ [ImplementedAs=bufferForBindings] attribute AudioBuffer? buffer;
readonly attribute AudioParam playbackRate;
readonly attribute AudioParam detune;
- attribute boolean loop;
- attribute double loopStart;
- attribute double loopEnd;
+ [ImplementedAs=loopForBindings] attribute boolean loop;
+ [ImplementedAs=loopStartForBindings] attribute double loopStart;
+ [ImplementedAs=loopEndForBindings] attribute double loopEnd;
[ImplementedAs=startLater] undefined start(optional double when = 0, optional double grainOffset = 0, optional double grainDuration);
};