AudioBufferSourceNode.cpp [plain text]
#include "config.h"
#if ENABLE(WEB_AUDIO)
#include "AudioBufferSourceNode.h"
#include "AudioBuffer.h"
#include "AudioContext.h"
#include "AudioNodeOutput.h"
#include "AudioParam.h"
#include "AudioUtilities.h"
#include "FloatConversion.h"
#include "PannerNode.h"
#include "ScriptExecutionContext.h"
#include "WebKitAudioBufferSourceNode.h"
#include <wtf/IsoMallocInlines.h>
namespace WebCore {
WTF_MAKE_ISO_ALLOCATED_IMPL(AudioBufferSourceNode);
WTF_MAKE_ISO_ALLOCATED_IMPL(WebKitAudioBufferSourceNode);
const double DefaultGrainDuration = 0.020;
const double MaxRate = 1024;
ExceptionOr<Ref<AudioBufferSourceNode>> AudioBufferSourceNode::create(BaseAudioContext& context, AudioBufferSourceOptions&& options)
{
if (context.isStopped())
return Exception { InvalidStateError };
context.lazyInitialize();
auto node = adoptRef(*new AudioBufferSourceNode(context));
node->setBuffer(WTFMove(options.buffer));
node->detune().setValue(options.detune);
node->setLoop(options.loop);
node->setLoopEnd(options.loopEnd);
node->setLoopStart(options.loopStart);
node->playbackRate().setValue(options.playbackRate);
context.refNode(node);
return node;
}
AudioBufferSourceNode::AudioBufferSourceNode(BaseAudioContext& context)
: AudioScheduledSourceNode(context)
, m_detune(AudioParam::create(context, "detune"_s, 0.0, -FLT_MAX, FLT_MAX, AutomationRate::KRate, AutomationRateMode::Fixed))
, m_playbackRate(AudioParam::create(context, "playbackRate"_s, 1.0, -FLT_MAX, FLT_MAX, AutomationRate::KRate, AutomationRateMode::Fixed))
, m_grainDuration(DefaultGrainDuration)
{
setNodeType(NodeTypeAudioBufferSource);
addOutput(makeUnique<AudioNodeOutput>(this, 1));
initialize();
}
AudioBufferSourceNode::~AudioBufferSourceNode()
{
clearPannerNode();
uninitialize();
}
void AudioBufferSourceNode::process(size_t framesToProcess)
{
auto& outputBus = *output(0)->bus();
if (!isInitialized()) {
outputBus.zero();
return;
}
std::unique_lock<Lock> lock(m_processMutex, std::try_to_lock);
if (!lock.owns_lock()) {
outputBus.zero();
return;
}
if (!buffer()) {
outputBus.zero();
return;
}
if (numberOfChannels() != buffer()->numberOfChannels()) {
outputBus.zero();
return;
}
size_t quantumFrameOffset = 0;
size_t bufferFramesToProcess = 0;
updateSchedulingInfo(framesToProcess, outputBus, quantumFrameOffset, bufferFramesToProcess);
if (!bufferFramesToProcess) {
outputBus.zero();
return;
}
for (unsigned i = 0; i < outputBus.numberOfChannels(); ++i)
m_destinationChannels[i] = outputBus.channel(i)->mutableData();
if (!renderFromBuffer(&outputBus, quantumFrameOffset, bufferFramesToProcess)) {
outputBus.zero();
return;
}
float totalGain = legacyGainValue() * m_buffer->gain();
outputBus.copyWithGainFrom(outputBus, &m_lastGain, totalGain);
outputBus.clearSilentFlag();
}
bool AudioBufferSourceNode::renderSilenceAndFinishIfNotLooping(AudioBus*, unsigned index, size_t framesToProcess)
{
if (!loop()) {
if (framesToProcess > 0) {
for (unsigned i = 0; i < numberOfChannels(); ++i)
memset(m_destinationChannels[i] + index, 0, sizeof(float) * framesToProcess);
}
finish();
return true;
}
return false;
}
bool AudioBufferSourceNode::renderFromBuffer(AudioBus* bus, unsigned destinationFrameOffset, size_t numberOfFrames)
{
ASSERT(context().isAudioThread());
ASSERT(bus);
ASSERT(buffer());
if (!bus || !buffer())
return false;
unsigned numberOfChannels = this->numberOfChannels();
unsigned busNumberOfChannels = bus->numberOfChannels();
bool channelCountGood = numberOfChannels && numberOfChannels == busNumberOfChannels;
ASSERT(channelCountGood);
if (!channelCountGood)
return false;
size_t destinationLength = bus->length();
bool isLengthGood = destinationLength <= 4096 && numberOfFrames <= 4096;
ASSERT(isLengthGood);
if (!isLengthGood)
return false;
bool isOffsetGood = destinationFrameOffset <= destinationLength && destinationFrameOffset + numberOfFrames <= destinationLength;
ASSERT(isOffsetGood);
if (!isOffsetGood)
return false;
if (destinationFrameOffset) {
for (unsigned i = 0; i < numberOfChannels; ++i)
memset(m_destinationChannels[i], 0, sizeof(float) * destinationFrameOffset);
}
unsigned writeIndex = destinationFrameOffset;
size_t bufferLength = buffer()->length();
double bufferSampleRate = buffer()->sampleRate();
double pitchRate = totalPitchRate();
bool reverse = pitchRate < 0;
unsigned maxFrame;
if (m_isGrain)
maxFrame = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, bufferSampleRate);
else
maxFrame = bufferLength;
if (maxFrame > bufferLength)
maxFrame = bufferLength;
if (reverse && m_virtualReadIndex <= 0)
m_virtualReadIndex = maxFrame - 1;
else if (!reverse && m_virtualReadIndex >= maxFrame)
m_virtualReadIndex = 0;
double virtualMaxFrame = maxFrame;
double virtualMinFrame = 0;
double virtualDeltaFrames = maxFrame;
if (loop() && (m_loopStart || m_loopEnd) && m_loopStart >= 0 && m_loopEnd > 0 && m_loopStart < m_loopEnd) {
double loopMinFrame = m_loopStart * buffer()->sampleRate();
double loopMaxFrame = m_loopEnd * buffer()->sampleRate();
virtualMaxFrame = std::min(loopMaxFrame, virtualMaxFrame);
virtualMinFrame = std::max(loopMinFrame, virtualMinFrame);
virtualDeltaFrames = virtualMaxFrame - virtualMinFrame;
}
if (std::abs(pitchRate) > virtualDeltaFrames)
return false;
double virtualReadIndex = m_virtualReadIndex;
bool needsInterpolation = virtualReadIndex != floor(virtualReadIndex)
|| virtualDeltaFrames != floor(virtualDeltaFrames)
|| virtualMaxFrame != floor(virtualMaxFrame)
|| virtualMinFrame != floor(virtualMinFrame);
int framesToProcess = numberOfFrames;
const float** sourceChannels = m_sourceChannels.get();
float** destinationChannels = m_destinationChannels.get();
if (pitchRate == 1 && !needsInterpolation) {
unsigned readIndex = static_cast<unsigned>(virtualReadIndex);
unsigned deltaFrames = static_cast<unsigned>(virtualDeltaFrames);
maxFrame = static_cast<unsigned>(virtualMaxFrame);
while (framesToProcess > 0) {
int framesToEnd = maxFrame - readIndex;
int framesThisTime = std::min(framesToProcess, framesToEnd);
framesThisTime = std::max(0, framesThisTime);
for (unsigned i = 0; i < numberOfChannels; ++i)
memcpy(destinationChannels[i] + writeIndex, sourceChannels[i] + readIndex, sizeof(float) * framesThisTime);
writeIndex += framesThisTime;
readIndex += framesThisTime;
framesToProcess -= framesThisTime;
if (readIndex >= maxFrame) {
readIndex -= deltaFrames;
if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
break;
}
}
virtualReadIndex = readIndex;
} else if (pitchRate == -1 && !needsInterpolation) {
int readIndex = static_cast<int>(virtualReadIndex);
int deltaFrames = static_cast<int>(virtualDeltaFrames);
int minFrame = static_cast<int>(virtualMinFrame) - 1;
while (framesToProcess > 0) {
int framesToEnd = readIndex - minFrame;
int framesThisTime = std::min<int>(framesToProcess, framesToEnd);
framesThisTime = std::max<int>(0, framesThisTime);
while (framesThisTime--) {
for (unsigned i = 0; i < numberOfChannels; ++i) {
float* destination = destinationChannels[i];
const float* source = sourceChannels[i];
destination[writeIndex] = source[readIndex];
}
++writeIndex;
--readIndex;
--framesToProcess;
}
if (readIndex <= minFrame) {
readIndex += deltaFrames;
if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
break;
}
}
virtualReadIndex = readIndex;
} else if (!pitchRate) {
unsigned readIndex = static_cast<unsigned>(virtualReadIndex);
for (unsigned i = 0; i < numberOfChannels; ++i)
std::fill_n(destinationChannels[i], framesToProcess, sourceChannels[i][readIndex]);
} else if (reverse) {
unsigned maxFrame = static_cast<unsigned>(virtualMaxFrame);
unsigned minFrame = static_cast<unsigned>(floorf(virtualMinFrame));
while (framesToProcess--) {
unsigned readIndex = static_cast<unsigned>(floorf(virtualReadIndex));
double interpolationFactor = virtualReadIndex - readIndex;
unsigned readIndex2 = readIndex + 1;
if (readIndex2 >= maxFrame)
readIndex2 = loop() ? minFrame : maxFrame - 1;
for (unsigned i = 0; i < numberOfChannels; ++i) {
float* destination = destinationChannels[i];
const float* source = sourceChannels[i];
double sample1 = source[readIndex];
double sample2 = source[readIndex2];
double sample = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2;
destination[writeIndex] = narrowPrecisionToFloat(sample);
}
writeIndex++;
virtualReadIndex += pitchRate;
if (virtualReadIndex < virtualMinFrame) {
virtualReadIndex += virtualDeltaFrames;
if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
break;
}
}
} else {
while (framesToProcess--) {
unsigned readIndex = static_cast<unsigned>(virtualReadIndex);
double interpolationFactor = virtualReadIndex - readIndex;
unsigned readIndex2 = readIndex + 1;
if (readIndex2 >= bufferLength) {
if (loop()) {
readIndex2 = static_cast<unsigned>(virtualReadIndex + 1 - virtualDeltaFrames);
} else
readIndex2 = readIndex;
}
if (readIndex >= bufferLength || readIndex2 >= bufferLength)
break;
for (unsigned i = 0; i < numberOfChannels; ++i) {
float* destination = destinationChannels[i];
const float* source = sourceChannels[i];
double sample1 = source[readIndex];
double sample2 = source[readIndex2];
double sample = (1.0 - interpolationFactor) * sample1 + interpolationFactor * sample2;
destination[writeIndex] = narrowPrecisionToFloat(sample);
}
writeIndex++;
virtualReadIndex += pitchRate;
if (virtualReadIndex >= virtualMaxFrame) {
virtualReadIndex -= virtualDeltaFrames;
if (renderSilenceAndFinishIfNotLooping(bus, writeIndex, framesToProcess))
break;
}
}
}
bus->clearSilentFlag();
m_virtualReadIndex = virtualReadIndex;
return true;
}
void AudioBufferSourceNode::reset()
{
m_virtualReadIndex = 0;
m_lastGain = legacyGainValue();
}
ExceptionOr<void> AudioBufferSourceNode::setBuffer(RefPtr<AudioBuffer>&& buffer)
{
ASSERT(isMainThread());
DEBUG_LOG(LOGIDENTIFIER);
if (buffer && m_wasBufferSet && shouldThrowOnAttemptToOverwriteBuffer())
return Exception { InvalidStateError, "The buffer was already set"_s };
BaseAudioContext::AutoLocker contextLocker(context());
auto locker = holdLock(m_processMutex);
if (buffer) {
m_wasBufferSet = true;
unsigned numberOfChannels = buffer->numberOfChannels();
ASSERT(numberOfChannels <= AudioContext::maxNumberOfChannels());
output(0)->setNumberOfChannels(numberOfChannels);
m_sourceChannels = makeUniqueArray<const float*>(numberOfChannels);
m_destinationChannels = makeUniqueArray<float*>(numberOfChannels);
for (unsigned i = 0; i < numberOfChannels; ++i)
m_sourceChannels[i] = buffer->channelData(i)->data();
}
m_virtualReadIndex = 0;
m_buffer = WTFMove(buffer);
return { };
}
unsigned AudioBufferSourceNode::numberOfChannels()
{
return output(0)->numberOfChannels();
}
ExceptionOr<void> AudioBufferSourceNode::startLater(double when, double grainOffset, Optional<double> optionalGrainDuration)
{
double grainDuration = 0;
if (optionalGrainDuration)
grainDuration = optionalGrainDuration.value();
else if (buffer())
grainDuration = buffer()->duration() - grainOffset;
return startPlaying(Partial, when, grainOffset, grainDuration);
}
ExceptionOr<void> AudioBufferSourceNode::startPlaying(BufferPlaybackMode playbackMode, double when, double grainOffset, double grainDuration)
{
ASSERT(isMainThread());
ALWAYS_LOG(LOGIDENTIFIER, "when = ", when, ", offset = ", grainOffset, ", duration = ", grainDuration);
context().nodeWillBeginPlayback();
if (m_playbackState != UNSCHEDULED_STATE)
return Exception { InvalidStateError, "Cannot call start more than once."_s };
if (!std::isfinite(when) || (when < 0))
return Exception { RangeError, "when value should be positive"_s };
if (!std::isfinite(grainOffset) || (grainOffset < 0))
return Exception { RangeError, "offset value should be positive"_s };
if (!std::isfinite(grainDuration) || (grainDuration < 0))
return Exception { RangeError, "duration value should be positive"_s };
m_isGrain = playbackMode == Partial;
m_grainOffset = grainOffset;
m_grainDuration = grainDuration;
m_startTime = when;
if (buffer()) {
if (m_isGrain) {
double bufferDuration = buffer()->duration();
m_grainOffset = std::min(bufferDuration, grainOffset);
double maxDuration = bufferDuration - m_grainOffset;
m_grainDuration = std::min(maxDuration, grainDuration);
} else {
m_grainOffset = 0.0;
m_grainDuration = buffer()->duration();
}
if (playbackRate().value() < 0)
m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset + m_grainDuration, buffer()->sampleRate()) - 1;
else
m_virtualReadIndex = AudioUtilities::timeToSampleFrame(m_grainOffset, buffer()->sampleRate());
}
m_playbackState = SCHEDULED_STATE;
return { };
}
double AudioBufferSourceNode::totalPitchRate()
{
double dopplerRate = 1.0;
if (m_pannerNode)
dopplerRate = m_pannerNode->dopplerRate();
double sampleRateFactor = 1.0;
if (buffer())
sampleRateFactor = buffer()->sampleRate() / static_cast<double>(sampleRate());
double basePitchRate = playbackRate().finalValue();
double detune = pow(2, m_detune->finalValue() / 1200);
double totalRate = dopplerRate * sampleRateFactor * basePitchRate * detune;
totalRate = std::clamp(totalRate, -MaxRate, MaxRate);
bool isTotalRateValid = !std::isnan(totalRate) && !std::isinf(totalRate);
ASSERT(isTotalRateValid);
if (!isTotalRateValid)
totalRate = 1.0;
return totalRate;
}
bool AudioBufferSourceNode::propagatesSilence() const
{
return !isPlayingOrScheduled() || hasFinished() || !m_buffer;
}
void AudioBufferSourceNode::setPannerNode(PannerNodeBase* pannerNode)
{
if (m_pannerNode != pannerNode && !hasFinished()) {
if (pannerNode)
pannerNode->ref(AudioNode::RefTypeConnection);
if (m_pannerNode)
m_pannerNode->deref(AudioNode::RefTypeConnection);
m_pannerNode = pannerNode;
}
}
void AudioBufferSourceNode::clearPannerNode()
{
if (m_pannerNode) {
m_pannerNode->deref(AudioNode::RefTypeConnection);
m_pannerNode = nullptr;
}
}
void AudioBufferSourceNode::finish()
{
clearPannerNode();
ASSERT(!m_pannerNode);
AudioScheduledSourceNode::finish();
}
}
#endif // ENABLE(WEB_AUDIO)