зеркало из https://github.com/mozilla/gecko-dev.git
222 строки
7.7 KiB
C++
222 строки
7.7 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim:set ts=2 sw=2 sts=2 et cindent: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "DelayNode.h"
|
|
#include "mozilla/dom/DelayNodeBinding.h"
|
|
#include "AudioNodeEngine.h"
|
|
#include "AudioNodeStream.h"
|
|
#include "AudioDestinationNode.h"
|
|
#include "WebAudioUtils.h"
|
|
#include "DelayBuffer.h"
|
|
#include "PlayingRefChangeHandler.h"
|
|
|
|
namespace mozilla {
|
|
namespace dom {
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_INHERITED(DelayNode, AudioNode, mDelay)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DelayNode)
|
|
NS_INTERFACE_MAP_END_INHERITING(AudioNode)
|
|
|
|
NS_IMPL_ADDREF_INHERITED(DelayNode, AudioNode)
|
|
NS_IMPL_RELEASE_INHERITED(DelayNode, AudioNode)
|
|
|
|
class DelayNodeEngine final : public AudioNodeEngine {
|
|
typedef PlayingRefChangeHandler PlayingRefChanged;
|
|
|
|
public:
|
|
DelayNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
|
|
float aMaxDelayTicks)
|
|
: AudioNodeEngine(aNode),
|
|
mDestination(aDestination->Stream())
|
|
// Keep the default value in sync with the default value in
|
|
// DelayNode::DelayNode.
|
|
,
|
|
mDelay(0.f)
|
|
// Use a smoothing range of 20ms
|
|
,
|
|
mBuffer(
|
|
std::max(aMaxDelayTicks, static_cast<float>(WEBAUDIO_BLOCK_SIZE))),
|
|
mMaxDelay(aMaxDelayTicks),
|
|
mHaveProducedBeforeInput(false),
|
|
mLeftOverData(INT32_MIN) {}
|
|
|
|
DelayNodeEngine* AsDelayNodeEngine() override { return this; }
|
|
|
|
enum Parameters {
|
|
DELAY,
|
|
};
|
|
void RecvTimelineEvent(uint32_t aIndex, AudioTimelineEvent& aEvent) override {
|
|
MOZ_ASSERT(mDestination);
|
|
WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent, mDestination);
|
|
|
|
switch (aIndex) {
|
|
case DELAY:
|
|
mDelay.InsertEvent<int64_t>(aEvent);
|
|
break;
|
|
default:
|
|
NS_ERROR("Bad DelayNodeEngine TimelineParameter");
|
|
}
|
|
}
|
|
|
|
void ProcessBlock(AudioNodeStream* aStream, GraphTime aFrom,
|
|
const AudioBlock& aInput, AudioBlock* aOutput,
|
|
bool* aFinished) override {
|
|
MOZ_ASSERT(aStream->SampleRate() == mDestination->SampleRate());
|
|
|
|
if (!aInput.IsSilentOrSubnormal()) {
|
|
if (mLeftOverData <= 0) {
|
|
RefPtr<PlayingRefChanged> refchanged =
|
|
new PlayingRefChanged(aStream, PlayingRefChanged::ADDREF);
|
|
aStream->Graph()->DispatchToMainThreadStableState(refchanged.forget());
|
|
}
|
|
mLeftOverData = mBuffer.MaxDelayTicks();
|
|
} else if (mLeftOverData > 0) {
|
|
mLeftOverData -= WEBAUDIO_BLOCK_SIZE;
|
|
} else {
|
|
if (mLeftOverData != INT32_MIN) {
|
|
mLeftOverData = INT32_MIN;
|
|
aStream->ScheduleCheckForInactive();
|
|
|
|
// Delete our buffered data now we no longer need it
|
|
mBuffer.Reset();
|
|
|
|
RefPtr<PlayingRefChanged> refchanged =
|
|
new PlayingRefChanged(aStream, PlayingRefChanged::RELEASE);
|
|
aStream->Graph()->DispatchToMainThreadStableState(refchanged.forget());
|
|
}
|
|
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
|
|
return;
|
|
}
|
|
|
|
mBuffer.Write(aInput);
|
|
|
|
// Skip output update if mLastChunks has already been set by
|
|
// ProduceBlockBeforeInput() when in a cycle.
|
|
if (!mHaveProducedBeforeInput) {
|
|
UpdateOutputBlock(aStream, aFrom, aOutput, 0.0);
|
|
}
|
|
mHaveProducedBeforeInput = false;
|
|
mBuffer.NextBlock();
|
|
}
|
|
|
|
void UpdateOutputBlock(AudioNodeStream* aStream, GraphTime aFrom,
|
|
AudioBlock* aOutput, float minDelay) {
|
|
float maxDelay = mMaxDelay;
|
|
float sampleRate = aStream->SampleRate();
|
|
ChannelInterpretation channelInterpretation =
|
|
aStream->GetChannelInterpretation();
|
|
if (mDelay.HasSimpleValue()) {
|
|
// If this DelayNode is in a cycle, make sure the delay value is at least
|
|
// one block, even if that is greater than maxDelay.
|
|
float delayFrames = mDelay.GetValue() * sampleRate;
|
|
float delayFramesClamped =
|
|
std::max(minDelay, std::min(delayFrames, maxDelay));
|
|
mBuffer.Read(delayFramesClamped, aOutput, channelInterpretation);
|
|
} else {
|
|
// Compute the delay values for the duration of the input AudioChunk
|
|
// If this DelayNode is in a cycle, make sure the delay value is at least
|
|
// one block.
|
|
StreamTime tick = mDestination->GraphTimeToStreamTime(aFrom);
|
|
float values[WEBAUDIO_BLOCK_SIZE];
|
|
mDelay.GetValuesAtTime(tick, values, WEBAUDIO_BLOCK_SIZE);
|
|
|
|
float computedDelay[WEBAUDIO_BLOCK_SIZE];
|
|
for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) {
|
|
float delayAtTick = values[counter] * sampleRate;
|
|
float delayAtTickClamped =
|
|
std::max(minDelay, std::min(delayAtTick, maxDelay));
|
|
computedDelay[counter] = delayAtTickClamped;
|
|
}
|
|
mBuffer.Read(computedDelay, aOutput, channelInterpretation);
|
|
}
|
|
}
|
|
|
|
void ProduceBlockBeforeInput(AudioNodeStream* aStream, GraphTime aFrom,
|
|
AudioBlock* aOutput) override {
|
|
if (mLeftOverData <= 0) {
|
|
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
|
|
} else {
|
|
UpdateOutputBlock(aStream, aFrom, aOutput, WEBAUDIO_BLOCK_SIZE);
|
|
}
|
|
mHaveProducedBeforeInput = true;
|
|
}
|
|
|
|
bool IsActive() const override { return mLeftOverData != INT32_MIN; }
|
|
|
|
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
|
|
size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
|
|
// Not owned:
|
|
// - mDestination - probably not owned
|
|
// - mDelay - shares ref with AudioNode, don't count
|
|
amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
|
|
return amount;
|
|
}
|
|
|
|
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
|
|
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
RefPtr<AudioNodeStream> mDestination;
|
|
AudioParamTimeline mDelay;
|
|
DelayBuffer mBuffer;
|
|
float mMaxDelay;
|
|
bool mHaveProducedBeforeInput;
|
|
// How much data we have in our buffer which needs to be flushed out when our
|
|
// inputs finish.
|
|
int32_t mLeftOverData;
|
|
};
|
|
|
|
DelayNode::DelayNode(AudioContext* aContext, double aMaxDelay)
|
|
: AudioNode(aContext, 2, ChannelCountMode::Max,
|
|
ChannelInterpretation::Speakers) {
|
|
CreateAudioParam(mDelay, DelayNodeEngine::DELAY, "delayTime", 0.0f, 0.f,
|
|
aMaxDelay);
|
|
DelayNodeEngine* engine = new DelayNodeEngine(
|
|
this, aContext->Destination(), aContext->SampleRate() * aMaxDelay);
|
|
mStream = AudioNodeStream::Create(
|
|
aContext, engine, AudioNodeStream::NO_STREAM_FLAGS, aContext->Graph());
|
|
}
|
|
|
|
/* static */
|
|
already_AddRefed<DelayNode> DelayNode::Create(AudioContext& aAudioContext,
|
|
const DelayOptions& aOptions,
|
|
ErrorResult& aRv) {
|
|
if (aOptions.mMaxDelayTime <= 0. || aOptions.mMaxDelayTime >= 180.) {
|
|
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<DelayNode> audioNode =
|
|
new DelayNode(&aAudioContext, aOptions.mMaxDelayTime);
|
|
|
|
audioNode->Initialize(aOptions, aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
return nullptr;
|
|
}
|
|
|
|
audioNode->DelayTime()->SetValue(aOptions.mDelayTime);
|
|
return audioNode.forget();
|
|
}
|
|
|
|
size_t DelayNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
|
|
size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
|
|
amount += mDelay->SizeOfIncludingThis(aMallocSizeOf);
|
|
return amount;
|
|
}
|
|
|
|
size_t DelayNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
|
|
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
JSObject* DelayNode::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return DelayNode_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
} // namespace dom
|
|
} // namespace mozilla
|