michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim:set ts=2 sw=2 sts=2 et cindent: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "WaveShaperNode.h" michael@0: #include "mozilla/dom/WaveShaperNodeBinding.h" michael@0: #include "AudioNode.h" michael@0: #include "AudioNodeEngine.h" michael@0: #include "AudioNodeStream.h" michael@0: #include "mozilla/PodOperations.h" michael@0: #include "speex/speex_resampler.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(WaveShaperNode) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WaveShaperNode, AudioNode) michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER michael@0: tmp->ClearCurve(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WaveShaperNode, AudioNode) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WaveShaperNode) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCurve) michael@0: NS_IMPL_CYCLE_COLLECTION_TRACE_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WaveShaperNode) michael@0: NS_INTERFACE_MAP_END_INHERITING(AudioNode) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(WaveShaperNode, AudioNode) michael@0: NS_IMPL_RELEASE_INHERITED(WaveShaperNode, AudioNode) michael@0: michael@0: static uint32_t ValueOf(OverSampleType aType) michael@0: { michael@0: switch (aType) { michael@0: case OverSampleType::None: return 1; michael@0: case OverSampleType::_2x: return 2; michael@0: case OverSampleType::_4x: return 4; michael@0: default: michael@0: NS_NOTREACHED("We should never reach here"); michael@0: return 1; michael@0: } michael@0: } michael@0: michael@0: class Resampler michael@0: { michael@0: public: michael@0: Resampler() michael@0: : mType(OverSampleType::None) michael@0: , mUpSampler(nullptr) michael@0: , mDownSampler(nullptr) michael@0: , mChannels(0) michael@0: , mSampleRate(0) michael@0: { michael@0: } michael@0: michael@0: ~Resampler() michael@0: { michael@0: Destroy(); michael@0: } michael@0: michael@0: void Reset(uint32_t aChannels, TrackRate aSampleRate, OverSampleType aType) michael@0: { michael@0: if (aChannels == mChannels && michael@0: aSampleRate == mSampleRate && michael@0: aType == mType) { michael@0: return; michael@0: } michael@0: michael@0: mChannels = aChannels; michael@0: mSampleRate = aSampleRate; michael@0: mType = aType; michael@0: michael@0: Destroy(); michael@0: michael@0: if (aType == OverSampleType::None) { michael@0: mBuffer.Clear(); michael@0: return; michael@0: } michael@0: michael@0: mUpSampler = speex_resampler_init(aChannels, michael@0: aSampleRate, michael@0: aSampleRate * ValueOf(aType), michael@0: SPEEX_RESAMPLER_QUALITY_DEFAULT, michael@0: nullptr); michael@0: mDownSampler = speex_resampler_init(aChannels, michael@0: aSampleRate * ValueOf(aType), michael@0: aSampleRate, michael@0: SPEEX_RESAMPLER_QUALITY_DEFAULT, michael@0: nullptr); michael@0: mBuffer.SetLength(WEBAUDIO_BLOCK_SIZE*ValueOf(aType)); michael@0: } michael@0: michael@0: float* UpSample(uint32_t aChannel, const float* aInputData, uint32_t aBlocks) michael@0: { michael@0: uint32_t inSamples = WEBAUDIO_BLOCK_SIZE; michael@0: uint32_t outSamples = WEBAUDIO_BLOCK_SIZE*aBlocks; michael@0: float* outputData = mBuffer.Elements(); michael@0: michael@0: MOZ_ASSERT(mBuffer.Length() == outSamples); michael@0: michael@0: WebAudioUtils::SpeexResamplerProcess(mUpSampler, aChannel, michael@0: aInputData, &inSamples, michael@0: outputData, &outSamples); michael@0: michael@0: MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE && outSamples == WEBAUDIO_BLOCK_SIZE*aBlocks); michael@0: michael@0: return outputData; michael@0: } michael@0: michael@0: void DownSample(uint32_t aChannel, float* aOutputData, uint32_t aBlocks) michael@0: { michael@0: uint32_t inSamples = WEBAUDIO_BLOCK_SIZE*aBlocks; michael@0: uint32_t outSamples = WEBAUDIO_BLOCK_SIZE; michael@0: const float* inputData = mBuffer.Elements(); michael@0: michael@0: MOZ_ASSERT(mBuffer.Length() == inSamples); michael@0: michael@0: WebAudioUtils::SpeexResamplerProcess(mDownSampler, aChannel, michael@0: inputData, &inSamples, michael@0: aOutputData, &outSamples); michael@0: michael@0: MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE*aBlocks && outSamples == WEBAUDIO_BLOCK_SIZE); michael@0: } michael@0: michael@0: size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = 0; michael@0: // Future: properly measure speex memory michael@0: amount += aMallocSizeOf(mUpSampler); michael@0: amount += aMallocSizeOf(mDownSampler); michael@0: amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf); michael@0: return amount; michael@0: } michael@0: michael@0: private: michael@0: void Destroy() michael@0: { michael@0: if (mUpSampler) { michael@0: speex_resampler_destroy(mUpSampler); michael@0: mUpSampler = nullptr; michael@0: } michael@0: if (mDownSampler) { michael@0: speex_resampler_destroy(mDownSampler); michael@0: mDownSampler = nullptr; michael@0: } michael@0: } michael@0: michael@0: private: michael@0: OverSampleType mType; michael@0: SpeexResamplerState* mUpSampler; michael@0: SpeexResamplerState* mDownSampler; michael@0: uint32_t mChannels; michael@0: TrackRate mSampleRate; michael@0: nsTArray mBuffer; michael@0: }; michael@0: michael@0: class WaveShaperNodeEngine : public AudioNodeEngine michael@0: { michael@0: public: michael@0: explicit WaveShaperNodeEngine(AudioNode* aNode) michael@0: : AudioNodeEngine(aNode) michael@0: , mType(OverSampleType::None) michael@0: { michael@0: } michael@0: michael@0: enum Parameteres { michael@0: TYPE michael@0: }; michael@0: michael@0: virtual void SetRawArrayData(nsTArray& aCurve) MOZ_OVERRIDE michael@0: { michael@0: mCurve.SwapElements(aCurve); michael@0: } michael@0: michael@0: virtual void SetInt32Parameter(uint32_t aIndex, int32_t aValue) MOZ_OVERRIDE michael@0: { michael@0: switch (aIndex) { michael@0: case TYPE: michael@0: mType = static_cast(aValue); michael@0: break; michael@0: default: michael@0: NS_ERROR("Bad WaveShaperNode Int32Parameter"); michael@0: } michael@0: } michael@0: michael@0: template michael@0: void ProcessCurve(const float* aInputBuffer, float* aOutputBuffer) michael@0: { michael@0: for (uint32_t j = 0; j < WEBAUDIO_BLOCK_SIZE*blocks; ++j) { michael@0: // Index into the curve array based on the amplitude of the michael@0: // incoming signal by clamping the amplitude to [-1, 1] and michael@0: // performing a linear interpolation of the neighbor values. michael@0: float index = std::max(0.0f, std::min(float(mCurve.Length() - 1), michael@0: mCurve.Length() * (aInputBuffer[j] + 1) / 2)); michael@0: uint32_t indexLower = uint32_t(index); michael@0: uint32_t indexHigher = uint32_t(index + 1.0f); michael@0: if (indexHigher == mCurve.Length()) { michael@0: aOutputBuffer[j] = mCurve[indexLower]; michael@0: } else { michael@0: float interpolationFactor = index - indexLower; michael@0: aOutputBuffer[j] = (1.0f - interpolationFactor) * mCurve[indexLower] + michael@0: interpolationFactor * mCurve[indexHigher]; michael@0: } michael@0: } michael@0: } michael@0: michael@0: virtual void ProcessBlock(AudioNodeStream* aStream, michael@0: const AudioChunk& aInput, michael@0: AudioChunk* aOutput, michael@0: bool* aFinished) michael@0: { michael@0: uint32_t channelCount = aInput.mChannelData.Length(); michael@0: if (!mCurve.Length() || !channelCount) { michael@0: // Optimize the case where we don't have a curve buffer, michael@0: // or the input is null. michael@0: *aOutput = aInput; michael@0: return; michael@0: } michael@0: michael@0: AllocateAudioBlock(channelCount, aOutput); michael@0: for (uint32_t i = 0; i < channelCount; ++i) { michael@0: const float* inputBuffer = static_cast(aInput.mChannelData[i]); michael@0: float* outputBuffer = const_cast (static_cast(aOutput->mChannelData[i])); michael@0: float* sampleBuffer; michael@0: michael@0: switch (mType) { michael@0: case OverSampleType::None: michael@0: mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::None); michael@0: ProcessCurve<1>(inputBuffer, outputBuffer); michael@0: break; michael@0: case OverSampleType::_2x: michael@0: mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::_2x); michael@0: sampleBuffer = mResampler.UpSample(i, inputBuffer, 2); michael@0: ProcessCurve<2>(sampleBuffer, sampleBuffer); michael@0: mResampler.DownSample(i, outputBuffer, 2); michael@0: break; michael@0: case OverSampleType::_4x: michael@0: mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::_4x); michael@0: sampleBuffer = mResampler.UpSample(i, inputBuffer, 4); michael@0: ProcessCurve<4>(sampleBuffer, sampleBuffer); michael@0: mResampler.DownSample(i, outputBuffer, 4); michael@0: break; michael@0: default: michael@0: NS_NOTREACHED("We should never reach here"); michael@0: } michael@0: } michael@0: } michael@0: michael@0: virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); michael@0: amount += mCurve.SizeOfExcludingThis(aMallocSizeOf); michael@0: amount += mResampler.SizeOfExcludingThis(aMallocSizeOf); michael@0: return amount; michael@0: } michael@0: michael@0: virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: private: michael@0: nsTArray mCurve; michael@0: OverSampleType mType; michael@0: Resampler mResampler; michael@0: }; michael@0: michael@0: WaveShaperNode::WaveShaperNode(AudioContext* aContext) michael@0: : AudioNode(aContext, michael@0: 2, michael@0: ChannelCountMode::Max, michael@0: ChannelInterpretation::Speakers) michael@0: , mCurve(nullptr) michael@0: , mType(OverSampleType::None) michael@0: { michael@0: mozilla::HoldJSObjects(this); michael@0: michael@0: WaveShaperNodeEngine* engine = new WaveShaperNodeEngine(this); michael@0: mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM); michael@0: } michael@0: michael@0: WaveShaperNode::~WaveShaperNode() michael@0: { michael@0: ClearCurve(); michael@0: } michael@0: michael@0: void michael@0: WaveShaperNode::ClearCurve() michael@0: { michael@0: mCurve = nullptr; michael@0: mozilla::DropJSObjects(this); michael@0: } michael@0: michael@0: JSObject* michael@0: WaveShaperNode::WrapObject(JSContext *aCx) michael@0: { michael@0: return WaveShaperNodeBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: WaveShaperNode::SetCurve(const Nullable& aCurve) michael@0: { michael@0: nsTArray curve; michael@0: if (!aCurve.IsNull()) { michael@0: const Float32Array& floats = aCurve.Value(); michael@0: michael@0: mCurve = floats.Obj(); michael@0: michael@0: floats.ComputeLengthAndData(); michael@0: michael@0: curve.SetLength(floats.Length()); michael@0: PodCopy(curve.Elements(), floats.Data(), floats.Length()); michael@0: } else { michael@0: mCurve = nullptr; michael@0: } michael@0: michael@0: AudioNodeStream* ns = static_cast(mStream.get()); michael@0: MOZ_ASSERT(ns, "Why don't we have a stream here?"); michael@0: ns->SetRawArrayData(curve); michael@0: } michael@0: michael@0: void michael@0: WaveShaperNode::SetOversample(OverSampleType aType) michael@0: { michael@0: mType = aType; michael@0: SendInt32ParameterToStream(WaveShaperNodeEngine::TYPE, static_cast(aType)); michael@0: } michael@0: michael@0: } michael@0: }