Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "IIRFilterNode.h"
#include "AudioNodeEngine.h"
#include "AudioDestinationNode.h"
#include "blink/IIRFilter.h"
#include "PlayingRefChangeHandler.h"
#include "AlignmentUtils.h"
#include "nsPrintfCString.h"
#include "nsGlobalWindowInner.h"
#include "Tracing.h"
namespace mozilla::dom {
class IIRFilterNodeEngine final : public AudioNodeEngine {
public:
IIRFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
const AudioDoubleArray& aFeedforward,
const AudioDoubleArray& aFeedback, uint64_t aWindowID)
: AudioNodeEngine(aNode),
mDestination(aDestination->Track()),
mFeedforward(aFeedforward.Clone()),
mFeedback(aFeedback.Clone()),
mWindowID(aWindowID) {}
void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
const AudioBlock& aInput, AudioBlock* aOutput,
bool* aFinished) override {
TRACE("IIRFilterNodeEngine::ProcessBlock");
float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4];
float* alignedInputBuffer = ALIGNED16(inputBuffer);
ASSERT_ALIGNED16(alignedInputBuffer);
if (aInput.IsNull()) {
if (!mIIRFilters.IsEmpty()) {
bool allZero = true;
for (uint32_t i = 0; i < mIIRFilters.Length(); ++i) {
allZero &= mIIRFilters[i]->buffersAreZero();
}
// all filter buffer values are zero, so the output will be zero
// as well.
if (allZero) {
mIIRFilters.Clear();
aTrack->ScheduleCheckForInactive();
RefPtr<PlayingRefChangeHandler> refchanged =
new PlayingRefChangeHandler(aTrack,
PlayingRefChangeHandler::RELEASE);
aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
return;
}
PodZero(alignedInputBuffer, WEBAUDIO_BLOCK_SIZE);
}
} else if (mIIRFilters.Length() != aInput.ChannelCount()) {
if (mIIRFilters.IsEmpty()) {
RefPtr<PlayingRefChangeHandler> refchanged =
new PlayingRefChangeHandler(aTrack,
PlayingRefChangeHandler::ADDREF);
aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
} else {
WebAudioUtils::LogToDeveloperConsole(
mWindowID, "IIRFilterChannelCountChangeWarning");
}
// Adjust the number of filters based on the number of channels
mIIRFilters.SetLength(aInput.ChannelCount());
for (size_t i = 0; i < aInput.ChannelCount(); ++i) {
mIIRFilters[i] =
MakeUnique<blink::IIRFilter>(&mFeedforward, &mFeedback);
}
}
uint32_t numberOfChannels = mIIRFilters.Length();
aOutput->AllocateChannels(numberOfChannels);
for (uint32_t i = 0; i < numberOfChannels; ++i) {
const float* input;
if (aInput.IsNull()) {
input = alignedInputBuffer;
} else {
input = static_cast<const float*>(aInput.mChannelData[i]);
if (aInput.mVolume != 1.0) {
AudioBlockCopyChannelWithScale(input, aInput.mVolume,
alignedInputBuffer);
input = alignedInputBuffer;
}
}
mIIRFilters[i]->process(input, aOutput->ChannelFloatsForWrite(i),
aInput.GetDuration());
}
}
bool IsActive() const override { return !mIIRFilters.IsEmpty(); }
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
// Not owned:
// - mDestination - probably not owned
// - AudioParamTimelines - counted in the AudioNode
size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
amount += mIIRFilters.ShallowSizeOfExcludingThis(aMallocSizeOf);
return amount;
}
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
private:
RefPtr<AudioNodeTrack> mDestination;
nsTArray<UniquePtr<blink::IIRFilter>> mIIRFilters;
AudioDoubleArray mFeedforward;
AudioDoubleArray mFeedback;
uint64_t mWindowID;
};
IIRFilterNode::IIRFilterNode(AudioContext* aContext,
const Sequence<double>& aFeedforward,
const Sequence<double>& aFeedback)
: AudioNode(aContext, 2, ChannelCountMode::Max,
ChannelInterpretation::Speakers) {
mFeedforward.SetLength(aFeedforward.Length());
PodCopy(mFeedforward.Elements(), aFeedforward.Elements(),
aFeedforward.Length());
mFeedback.SetLength(aFeedback.Length());
PodCopy(mFeedback.Elements(), aFeedback.Elements(), aFeedback.Length());
// Scale coefficients -- we guarantee that mFeedback != 0 when creating
// the IIRFilterNode.
double scale = mFeedback[0];
double* elements = mFeedforward.Elements();
for (size_t i = 0; i < mFeedforward.Length(); ++i) {
elements[i] /= scale;
}
elements = mFeedback.Elements();
for (size_t i = 0; i < mFeedback.Length(); ++i) {
elements[i] /= scale;
}
// We check that this is exactly equal to one later in blink/IIRFilter.cpp
elements[0] = 1.0;
uint64_t windowID = 0;
if (nsGlobalWindowInner* win = aContext->GetOwnerWindow()) {
windowID = win->WindowID();
}
IIRFilterNodeEngine* engine = new IIRFilterNodeEngine(
this, aContext->Destination(), mFeedforward, mFeedback, windowID);
mTrack = AudioNodeTrack::Create(
aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
}
/* static */
already_AddRefed<IIRFilterNode> IIRFilterNode::Create(
AudioContext& aAudioContext, const IIRFilterOptions& aOptions,
ErrorResult& aRv) {
if (aOptions.mFeedforward.Length() == 0 ||
aOptions.mFeedforward.Length() > 20) {
aRv.ThrowNotSupportedError(
nsPrintfCString("\"feedforward\" length %zu is not in the range [1,20]",
aOptions.mFeedforward.Length()));
return nullptr;
}
if (aOptions.mFeedback.Length() == 0 || aOptions.mFeedback.Length() > 20) {
aRv.ThrowNotSupportedError(
nsPrintfCString("\"feedback\" length %zu is not in the range [1,20]",
aOptions.mFeedback.Length()));
return nullptr;
}
bool feedforwardAllZeros = true;
for (size_t i = 0; i < aOptions.mFeedforward.Length(); ++i) {
if (aOptions.mFeedforward.Elements()[i] != 0.0) {
feedforwardAllZeros = false;
break;
}
}
if (feedforwardAllZeros) {
aRv.ThrowInvalidStateError(
"\"feedforward\" must contain some nonzero values");
return nullptr;
}
if (aOptions.mFeedback[0] == 0.0) {
aRv.ThrowInvalidStateError("First value in \"feedback\" must be nonzero");
return nullptr;
}
RefPtr<IIRFilterNode> audioNode = new IIRFilterNode(
&aAudioContext, aOptions.mFeedforward, aOptions.mFeedback);
audioNode->Initialize(aOptions, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
return audioNode.forget();
}
size_t IIRFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
return amount;
}
size_t IIRFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
JSObject* IIRFilterNode::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return IIRFilterNode_Binding::Wrap(aCx, this, aGivenProto);
}
void IIRFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz,
const Float32Array& aMagResponse,
const Float32Array& aPhaseResponse) {
aFrequencyHz.ProcessData([&](const Span<float>& aFrequencyData,
JS::AutoCheckCannotGC&&) {
aMagResponse.ProcessData([&](const Span<float>& aMagData,
JS::AutoCheckCannotGC&&) {
aPhaseResponse.ProcessData([&](const Span<float>& aPhaseData,
JS::AutoCheckCannotGC&&) {
uint32_t length = std::min(
{aFrequencyData.Length(), aMagData.Length(), aPhaseData.Length()});
if (!length) {
return;
}
auto frequencies = MakeUniqueForOverwriteFallible<float[]>(length);
if (!frequencies) {
return;
}
const double nyquist = Context()->SampleRate() * 0.5;
// Normalize the frequencies
std::transform(aFrequencyData.begin(), aFrequencyData.begin() + length,
frequencies.get(), [&](float aFrequency) {
if (aFrequency >= 0 && aFrequency <= nyquist) {
return static_cast<float>(aFrequency / nyquist);
}
return std::numeric_limits<float>::quiet_NaN();
});
blink::IIRFilter filter(&mFeedforward, &mFeedback);
filter.getFrequencyResponse(int(length), frequencies.get(),
aMagData.Elements(), aPhaseData.Elements());
});
});
});
}
} // namespace mozilla::dom