summaryrefslogtreecommitdiff
path: root/dom/media/webaudio/WaveShaperNode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/webaudio/WaveShaperNode.cpp')
-rw-r--r--dom/media/webaudio/WaveShaperNode.cpp392
1 files changed, 392 insertions, 0 deletions
diff --git a/dom/media/webaudio/WaveShaperNode.cpp b/dom/media/webaudio/WaveShaperNode.cpp
new file mode 100644
index 0000000000..d5c617dcd4
--- /dev/null
+++ b/dom/media/webaudio/WaveShaperNode.cpp
@@ -0,0 +1,392 @@
+/* -*- 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 "WaveShaperNode.h"
+#include "mozilla/dom/WaveShaperNodeBinding.h"
+#include "AlignmentUtils.h"
+#include "AudioNode.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeStream.h"
+#include "mozilla/PodOperations.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(WaveShaperNode)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WaveShaperNode, AudioNode)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+ tmp->ClearCurve();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WaveShaperNode, AudioNode)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WaveShaperNode)
+ NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
+ NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCurve)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WaveShaperNode)
+NS_INTERFACE_MAP_END_INHERITING(AudioNode)
+
+NS_IMPL_ADDREF_INHERITED(WaveShaperNode, AudioNode)
+NS_IMPL_RELEASE_INHERITED(WaveShaperNode, AudioNode)
+
+static uint32_t ValueOf(OverSampleType aType)
+{
+ switch (aType) {
+ case OverSampleType::None: return 1;
+ case OverSampleType::_2x: return 2;
+ case OverSampleType::_4x: return 4;
+ default:
+ NS_NOTREACHED("We should never reach here");
+ return 1;
+ }
+}
+
+class Resampler final
+{
+public:
+ Resampler()
+ : mType(OverSampleType::None)
+ , mUpSampler(nullptr)
+ , mDownSampler(nullptr)
+ , mChannels(0)
+ , mSampleRate(0)
+ {
+ }
+
+ ~Resampler()
+ {
+ Destroy();
+ }
+
+ void Reset(uint32_t aChannels, TrackRate aSampleRate, OverSampleType aType)
+ {
+ if (aChannels == mChannels &&
+ aSampleRate == mSampleRate &&
+ aType == mType) {
+ return;
+ }
+
+ mChannels = aChannels;
+ mSampleRate = aSampleRate;
+ mType = aType;
+
+ Destroy();
+
+ if (aType == OverSampleType::None) {
+ mBuffer.Clear();
+ return;
+ }
+
+ mUpSampler = speex_resampler_init(aChannels,
+ aSampleRate,
+ aSampleRate * ValueOf(aType),
+ SPEEX_RESAMPLER_QUALITY_MIN,
+ nullptr);
+ mDownSampler = speex_resampler_init(aChannels,
+ aSampleRate * ValueOf(aType),
+ aSampleRate,
+ SPEEX_RESAMPLER_QUALITY_MIN,
+ nullptr);
+ mBuffer.SetLength(WEBAUDIO_BLOCK_SIZE*ValueOf(aType));
+ }
+
+ float* UpSample(uint32_t aChannel, const float* aInputData, uint32_t aBlocks)
+ {
+ uint32_t inSamples = WEBAUDIO_BLOCK_SIZE;
+ uint32_t outSamples = WEBAUDIO_BLOCK_SIZE*aBlocks;
+ float* outputData = mBuffer.Elements();
+
+ MOZ_ASSERT(mBuffer.Length() == outSamples);
+
+ WebAudioUtils::SpeexResamplerProcess(mUpSampler, aChannel,
+ aInputData, &inSamples,
+ outputData, &outSamples);
+
+ MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE && outSamples == WEBAUDIO_BLOCK_SIZE*aBlocks);
+
+ return outputData;
+ }
+
+ void DownSample(uint32_t aChannel, float* aOutputData, uint32_t aBlocks)
+ {
+ uint32_t inSamples = WEBAUDIO_BLOCK_SIZE*aBlocks;
+ uint32_t outSamples = WEBAUDIO_BLOCK_SIZE;
+ const float* inputData = mBuffer.Elements();
+
+ MOZ_ASSERT(mBuffer.Length() == inSamples);
+
+ WebAudioUtils::SpeexResamplerProcess(mDownSampler, aChannel,
+ inputData, &inSamples,
+ aOutputData, &outSamples);
+
+ MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE*aBlocks && outSamples == WEBAUDIO_BLOCK_SIZE);
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+ {
+ size_t amount = 0;
+ // Future: properly measure speex memory
+ amount += aMallocSizeOf(mUpSampler);
+ amount += aMallocSizeOf(mDownSampler);
+ amount += mBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+ }
+
+private:
+ void Destroy()
+ {
+ if (mUpSampler) {
+ speex_resampler_destroy(mUpSampler);
+ mUpSampler = nullptr;
+ }
+ if (mDownSampler) {
+ speex_resampler_destroy(mDownSampler);
+ mDownSampler = nullptr;
+ }
+ }
+
+private:
+ OverSampleType mType;
+ SpeexResamplerState* mUpSampler;
+ SpeexResamplerState* mDownSampler;
+ uint32_t mChannels;
+ TrackRate mSampleRate;
+ nsTArray<float> mBuffer;
+};
+
+class WaveShaperNodeEngine final : public AudioNodeEngine
+{
+public:
+ explicit WaveShaperNodeEngine(AudioNode* aNode)
+ : AudioNodeEngine(aNode)
+ , mType(OverSampleType::None)
+ {
+ }
+
+ enum Parameteres {
+ TYPE
+ };
+
+ void SetRawArrayData(nsTArray<float>& aCurve) override
+ {
+ mCurve.SwapElements(aCurve);
+ }
+
+ void SetInt32Parameter(uint32_t aIndex, int32_t aValue) override
+ {
+ switch (aIndex) {
+ case TYPE:
+ mType = static_cast<OverSampleType>(aValue);
+ break;
+ default:
+ NS_ERROR("Bad WaveShaperNode Int32Parameter");
+ }
+ }
+
+ template <uint32_t blocks>
+ void ProcessCurve(const float* aInputBuffer, float* aOutputBuffer)
+ {
+ for (uint32_t j = 0; j < WEBAUDIO_BLOCK_SIZE*blocks; ++j) {
+ // Index into the curve array based on the amplitude of the
+ // incoming signal by using an amplitude range of [-1, 1] and
+ // performing a linear interpolation of the neighbor values.
+ float index = (mCurve.Length() - 1) * (aInputBuffer[j] + 1.0f) / 2.0f;
+ if (index < 0.0f) {
+ aOutputBuffer[j] = mCurve[0];
+ } else {
+ int32_t indexLower = index;
+ if (static_cast<uint32_t>(indexLower) >= mCurve.Length() - 1) {
+ aOutputBuffer[j] = mCurve[mCurve.Length() - 1];
+ } else {
+ uint32_t indexHigher = indexLower + 1;
+ float interpolationFactor = index - indexLower;
+ aOutputBuffer[j] = (1.0f - interpolationFactor) * mCurve[indexLower] +
+ interpolationFactor * mCurve[indexHigher];
+ }
+ }
+ }
+ }
+
+ void ProcessBlock(AudioNodeStream* aStream,
+ GraphTime aFrom,
+ const AudioBlock& aInput,
+ AudioBlock* aOutput,
+ bool* aFinished) override
+ {
+ uint32_t channelCount = aInput.ChannelCount();
+ if (!mCurve.Length()) {
+ // Optimize the case where we don't have a curve buffer
+ *aOutput = aInput;
+ return;
+ }
+
+ // If the input is null, check to see if non-null output will be produced
+ bool nullInput = false;
+ if (channelCount == 0) {
+ float index = (mCurve.Length() - 1) * 0.5;
+ uint32_t indexLower = index;
+ uint32_t indexHigher = indexLower + 1;
+ float interpolationFactor = index - indexLower;
+ if ((1.0f - interpolationFactor) * mCurve[indexLower] +
+ interpolationFactor * mCurve[indexHigher] == 0.0) {
+ *aOutput = aInput;
+ return;
+ } else {
+ nullInput = true;
+ channelCount = 1;
+ }
+ }
+
+ aOutput->AllocateChannels(channelCount);
+ for (uint32_t i = 0; i < channelCount; ++i) {
+ const float* inputSamples;
+ float scaledInput[WEBAUDIO_BLOCK_SIZE + 4];
+ float* alignedScaledInput = ALIGNED16(scaledInput);
+ ASSERT_ALIGNED16(alignedScaledInput);
+ if (!nullInput) {
+ if (aInput.mVolume != 1.0f) {
+ AudioBlockCopyChannelWithScale(
+ static_cast<const float*>(aInput.mChannelData[i]),
+ aInput.mVolume,
+ alignedScaledInput);
+ inputSamples = alignedScaledInput;
+ } else {
+ inputSamples = static_cast<const float*>(aInput.mChannelData[i]);
+ }
+ } else {
+ PodZero(alignedScaledInput, WEBAUDIO_BLOCK_SIZE);
+ inputSamples = alignedScaledInput;
+ }
+ float* outputBuffer = aOutput->ChannelFloatsForWrite(i);
+ float* sampleBuffer;
+
+ switch (mType) {
+ case OverSampleType::None:
+ mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::None);
+ ProcessCurve<1>(inputSamples, outputBuffer);
+ break;
+ case OverSampleType::_2x:
+ mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::_2x);
+ sampleBuffer = mResampler.UpSample(i, inputSamples, 2);
+ ProcessCurve<2>(sampleBuffer, sampleBuffer);
+ mResampler.DownSample(i, outputBuffer, 2);
+ break;
+ case OverSampleType::_4x:
+ mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::_4x);
+ sampleBuffer = mResampler.UpSample(i, inputSamples, 4);
+ ProcessCurve<4>(sampleBuffer, sampleBuffer);
+ mResampler.DownSample(i, outputBuffer, 4);
+ break;
+ default:
+ NS_NOTREACHED("We should never reach here");
+ }
+ }
+ }
+
+ size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
+ {
+ size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+ amount += mCurve.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ amount += mResampler.SizeOfExcludingThis(aMallocSizeOf);
+ return amount;
+ }
+
+ size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
+ {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+private:
+ nsTArray<float> mCurve;
+ OverSampleType mType;
+ Resampler mResampler;
+};
+
+WaveShaperNode::WaveShaperNode(AudioContext* aContext)
+ : AudioNode(aContext,
+ 2,
+ ChannelCountMode::Max,
+ ChannelInterpretation::Speakers)
+ , mCurve(nullptr)
+ , mType(OverSampleType::None)
+{
+ mozilla::HoldJSObjects(this);
+
+ WaveShaperNodeEngine* engine = new WaveShaperNodeEngine(this);
+ mStream = AudioNodeStream::Create(aContext, engine,
+ AudioNodeStream::NO_STREAM_FLAGS,
+ aContext->Graph());
+}
+
+WaveShaperNode::~WaveShaperNode()
+{
+ ClearCurve();
+}
+
+void
+WaveShaperNode::ClearCurve()
+{
+ mCurve = nullptr;
+ mozilla::DropJSObjects(this);
+}
+
+JSObject*
+WaveShaperNode::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+ return WaveShaperNodeBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+WaveShaperNode::SetCurve(const Nullable<Float32Array>& aCurve, ErrorResult& aRv)
+{
+ nsTArray<float> curve;
+ if (!aCurve.IsNull()) {
+ const Float32Array& floats = aCurve.Value();
+
+ floats.ComputeLengthAndData();
+ if (floats.IsShared()) {
+ // Throw if the object is mapping shared memory (must opt in).
+ aRv.ThrowTypeError<MSG_TYPEDARRAY_IS_SHARED>(NS_LITERAL_STRING("Argument of WaveShaperNode.setCurve"));
+ return;
+ }
+
+ uint32_t argLength = floats.Length();
+ if (argLength < 2) {
+ aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+ return;
+ }
+
+ if (!curve.SetLength(argLength, fallible)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ PodCopy(curve.Elements(), floats.Data(), floats.Length());
+
+ mCurve = floats.Obj();
+ } else {
+ mCurve = nullptr;
+ }
+
+ AudioNodeStream* ns = mStream;
+ MOZ_ASSERT(ns, "Why don't we have a stream here?");
+ ns->SetRawArrayData(curve);
+}
+
+void
+WaveShaperNode::SetOversample(OverSampleType aType)
+{
+ mType = aType;
+ SendInt32ParameterToStream(WaveShaperNodeEngine::TYPE, static_cast<int32_t>(aType));
+}
+
+} // namespace dom
+} // namespace mozilla