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 "ConvolverNode.h" michael@0: #include "mozilla/dom/ConvolverNodeBinding.h" michael@0: #include "AudioNodeEngine.h" michael@0: #include "AudioNodeStream.h" michael@0: #include "blink/Reverb.h" michael@0: #include "PlayingRefChangeHandler.h" michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(ConvolverNode, AudioNode, mBuffer) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(ConvolverNode) michael@0: NS_INTERFACE_MAP_END_INHERITING(AudioNode) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(ConvolverNode, AudioNode) michael@0: NS_IMPL_RELEASE_INHERITED(ConvolverNode, AudioNode) michael@0: michael@0: class ConvolverNodeEngine : public AudioNodeEngine michael@0: { michael@0: typedef PlayingRefChangeHandler PlayingRefChanged; michael@0: public: michael@0: ConvolverNodeEngine(AudioNode* aNode, bool aNormalize) michael@0: : AudioNodeEngine(aNode) michael@0: , mBufferLength(0) michael@0: , mLeftOverData(INT32_MIN) michael@0: , mSampleRate(0.0f) michael@0: , mUseBackgroundThreads(!aNode->Context()->IsOffline()) michael@0: , mNormalize(aNormalize) michael@0: { michael@0: } michael@0: michael@0: enum Parameters { michael@0: BUFFER_LENGTH, michael@0: SAMPLE_RATE, michael@0: NORMALIZE michael@0: }; michael@0: virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam) MOZ_OVERRIDE michael@0: { michael@0: switch (aIndex) { michael@0: case BUFFER_LENGTH: michael@0: // BUFFER_LENGTH is the first parameter that we set when setting a new buffer, michael@0: // so we should be careful to invalidate the rest of our state here. michael@0: mBuffer = nullptr; michael@0: mSampleRate = 0.0f; michael@0: mBufferLength = aParam; michael@0: mLeftOverData = INT32_MIN; michael@0: break; michael@0: case SAMPLE_RATE: michael@0: mSampleRate = aParam; michael@0: break; michael@0: case NORMALIZE: michael@0: mNormalize = !!aParam; michael@0: break; michael@0: default: michael@0: NS_ERROR("Bad ConvolverNodeEngine Int32Parameter"); michael@0: } michael@0: } michael@0: virtual void SetDoubleParameter(uint32_t aIndex, double aParam) MOZ_OVERRIDE michael@0: { michael@0: switch (aIndex) { michael@0: case SAMPLE_RATE: michael@0: mSampleRate = aParam; michael@0: AdjustReverb(); michael@0: break; michael@0: default: michael@0: NS_ERROR("Bad ConvolverNodeEngine DoubleParameter"); michael@0: } michael@0: } michael@0: virtual void SetBuffer(already_AddRefed aBuffer) michael@0: { michael@0: mBuffer = aBuffer; michael@0: AdjustReverb(); michael@0: } michael@0: michael@0: void AdjustReverb() michael@0: { michael@0: // Note about empirical tuning (this is copied from Blink) michael@0: // The maximum FFT size affects reverb performance and accuracy. michael@0: // If the reverb is single-threaded and processes entirely in the real-time audio thread, michael@0: // it's important not to make this too high. In this case 8192 is a good value. michael@0: // But, the Reverb object is multi-threaded, so we want this as high as possible without losing too much accuracy. michael@0: // Very large FFTs will have worse phase errors. Given these constraints 32768 is a good compromise. michael@0: const size_t MaxFFTSize = 32768; michael@0: michael@0: if (!mBuffer || !mBufferLength || !mSampleRate) { michael@0: mReverb = nullptr; michael@0: mLeftOverData = INT32_MIN; michael@0: return; michael@0: } michael@0: michael@0: mReverb = new WebCore::Reverb(mBuffer, mBufferLength, michael@0: WEBAUDIO_BLOCK_SIZE, michael@0: MaxFFTSize, 2, mUseBackgroundThreads, michael@0: mNormalize, mSampleRate); 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: if (!mReverb) { michael@0: *aOutput = aInput; michael@0: return; michael@0: } michael@0: michael@0: AudioChunk input = aInput; michael@0: if (aInput.IsNull()) { michael@0: if (mLeftOverData > 0) { michael@0: mLeftOverData -= WEBAUDIO_BLOCK_SIZE; michael@0: AllocateAudioBlock(1, &input); michael@0: WriteZeroesToAudioBlock(&input, 0, WEBAUDIO_BLOCK_SIZE); michael@0: } else { michael@0: if (mLeftOverData != INT32_MIN) { michael@0: mLeftOverData = INT32_MIN; michael@0: nsRefPtr refchanged = michael@0: new PlayingRefChanged(aStream, PlayingRefChanged::RELEASE); michael@0: aStream->Graph()-> michael@0: DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); michael@0: } michael@0: aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); michael@0: return; michael@0: } michael@0: } else { michael@0: if (aInput.mVolume != 1.0f) { michael@0: // Pre-multiply the input's volume michael@0: uint32_t numChannels = aInput.mChannelData.Length(); michael@0: AllocateAudioBlock(numChannels, &input); michael@0: for (uint32_t i = 0; i < numChannels; ++i) { michael@0: const float* src = static_cast(aInput.mChannelData[i]); michael@0: float* dest = static_cast(const_cast(input.mChannelData[i])); michael@0: AudioBlockCopyChannelWithScale(src, aInput.mVolume, dest); michael@0: } michael@0: } michael@0: michael@0: if (mLeftOverData <= 0) { michael@0: nsRefPtr refchanged = michael@0: new PlayingRefChanged(aStream, PlayingRefChanged::ADDREF); michael@0: aStream->Graph()-> michael@0: DispatchToMainThreadAfterStreamStateUpdate(refchanged.forget()); michael@0: } michael@0: mLeftOverData = mBufferLength; michael@0: MOZ_ASSERT(mLeftOverData > 0); michael@0: } michael@0: AllocateAudioBlock(2, aOutput); michael@0: michael@0: mReverb->process(&input, aOutput, WEBAUDIO_BLOCK_SIZE); 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: if (mBuffer && !mBuffer->IsShared()) { michael@0: amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: if (mReverb) { michael@0: amount += mReverb->sizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: 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: nsRefPtr mBuffer; michael@0: nsAutoPtr mReverb; michael@0: int32_t mBufferLength; michael@0: int32_t mLeftOverData; michael@0: float mSampleRate; michael@0: bool mUseBackgroundThreads; michael@0: bool mNormalize; michael@0: }; michael@0: michael@0: ConvolverNode::ConvolverNode(AudioContext* aContext) michael@0: : AudioNode(aContext, michael@0: 2, michael@0: ChannelCountMode::Clamped_max, michael@0: ChannelInterpretation::Speakers) michael@0: , mNormalize(true) michael@0: { michael@0: ConvolverNodeEngine* engine = new ConvolverNodeEngine(this, mNormalize); michael@0: mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM); michael@0: } michael@0: michael@0: size_t michael@0: ConvolverNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); michael@0: if (mBuffer) { michael@0: // NB: mBuffer might be shared with the associated engine, by convention michael@0: // the AudioNode will report. michael@0: amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf); michael@0: } michael@0: return amount; michael@0: } michael@0: michael@0: size_t michael@0: ConvolverNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: JSObject* michael@0: ConvolverNode::WrapObject(JSContext* aCx) michael@0: { michael@0: return ConvolverNodeBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: ConvolverNode::SetBuffer(JSContext* aCx, AudioBuffer* aBuffer, ErrorResult& aRv) michael@0: { michael@0: if (aBuffer) { michael@0: switch (aBuffer->NumberOfChannels()) { michael@0: case 1: michael@0: case 2: michael@0: case 4: michael@0: // Supported number of channels michael@0: break; michael@0: default: michael@0: aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: mBuffer = aBuffer; michael@0: michael@0: // Send the buffer to the stream michael@0: AudioNodeStream* ns = static_cast(mStream.get()); michael@0: MOZ_ASSERT(ns, "Why don't we have a stream here?"); michael@0: if (mBuffer) { michael@0: uint32_t length = mBuffer->Length(); michael@0: nsRefPtr data = michael@0: mBuffer->GetThreadSharedChannelsForRate(aCx); michael@0: if (data && length < WEBAUDIO_BLOCK_SIZE) { michael@0: // For very small impulse response buffers, we need to pad the michael@0: // buffer with 0 to make sure that the Reverb implementation michael@0: // has enough data to compute FFTs from. michael@0: length = WEBAUDIO_BLOCK_SIZE; michael@0: nsRefPtr paddedBuffer = michael@0: new ThreadSharedFloatArrayBufferList(data->GetChannels()); michael@0: float* channelData = (float*) malloc(sizeof(float) * length * data->GetChannels()); michael@0: for (uint32_t i = 0; i < data->GetChannels(); ++i) { michael@0: PodCopy(channelData + length * i, data->GetData(i), mBuffer->Length()); michael@0: PodZero(channelData + length * i + mBuffer->Length(), WEBAUDIO_BLOCK_SIZE - mBuffer->Length()); michael@0: paddedBuffer->SetData(i, (i == 0) ? channelData : nullptr, channelData); michael@0: } michael@0: data = paddedBuffer; michael@0: } michael@0: SendInt32ParameterToStream(ConvolverNodeEngine::BUFFER_LENGTH, length); michael@0: SendDoubleParameterToStream(ConvolverNodeEngine::SAMPLE_RATE, michael@0: mBuffer->SampleRate()); michael@0: ns->SetBuffer(data.forget()); michael@0: } else { michael@0: ns->SetBuffer(nullptr); michael@0: } michael@0: } michael@0: michael@0: void michael@0: ConvolverNode::SetNormalize(bool aNormalize) michael@0: { michael@0: mNormalize = aNormalize; michael@0: SendInt32ParameterToStream(ConvolverNodeEngine::NORMALIZE, aNormalize); michael@0: } michael@0: michael@0: } michael@0: } michael@0: