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 "DelayNode.h" michael@0: #include "mozilla/dom/DelayNodeBinding.h" michael@0: #include "AudioNodeEngine.h" michael@0: #include "AudioNodeStream.h" michael@0: #include "AudioDestinationNode.h" michael@0: #include "WebAudioUtils.h" michael@0: #include "DelayBuffer.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(DelayNode, AudioNode, michael@0: mDelay) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DelayNode) michael@0: NS_INTERFACE_MAP_END_INHERITING(AudioNode) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(DelayNode, AudioNode) michael@0: NS_IMPL_RELEASE_INHERITED(DelayNode, AudioNode) michael@0: michael@0: class DelayNodeEngine : public AudioNodeEngine michael@0: { michael@0: typedef PlayingRefChangeHandler PlayingRefChanged; michael@0: public: michael@0: DelayNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination, michael@0: double aMaxDelayTicks) michael@0: : AudioNodeEngine(aNode) michael@0: , mSource(nullptr) michael@0: , mDestination(static_cast (aDestination->Stream())) michael@0: // Keep the default value in sync with the default value in DelayNode::DelayNode. michael@0: , mDelay(0.f) michael@0: // Use a smoothing range of 20ms michael@0: , mBuffer(std::max(aMaxDelayTicks, michael@0: static_cast(WEBAUDIO_BLOCK_SIZE)), michael@0: WebAudioUtils::ComputeSmoothingRate(0.02, michael@0: mDestination->SampleRate())) michael@0: , mMaxDelay(aMaxDelayTicks) michael@0: , mLastOutputPosition(-1) michael@0: , mLeftOverData(INT32_MIN) michael@0: { michael@0: } michael@0: michael@0: virtual DelayNodeEngine* AsDelayNodeEngine() michael@0: { michael@0: return this; michael@0: } michael@0: michael@0: void SetSourceStream(AudioNodeStream* aSource) michael@0: { michael@0: mSource = aSource; michael@0: } michael@0: michael@0: enum Parameters { michael@0: DELAY, michael@0: }; michael@0: void SetTimelineParameter(uint32_t aIndex, michael@0: const AudioParamTimeline& aValue, michael@0: TrackRate aSampleRate) MOZ_OVERRIDE michael@0: { michael@0: switch (aIndex) { michael@0: case DELAY: michael@0: MOZ_ASSERT(mSource && mDestination); michael@0: mDelay = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mDelay, mSource, mDestination); michael@0: break; michael@0: default: michael@0: NS_ERROR("Bad DelayNodeEngine TimelineParameter"); 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) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(mSource == aStream, "Invalid source stream"); michael@0: MOZ_ASSERT(aStream->SampleRate() == mDestination->SampleRate()); michael@0: michael@0: if (!aInput.IsNull()) { 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 = mBuffer.MaxDelayTicks(); michael@0: } else if (mLeftOverData > 0) { michael@0: mLeftOverData -= WEBAUDIO_BLOCK_SIZE; michael@0: } else { michael@0: if (mLeftOverData != INT32_MIN) { michael@0: mLeftOverData = INT32_MIN; michael@0: // Delete our buffered data now we no longer need it michael@0: mBuffer.Reset(); michael@0: 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 = aInput; michael@0: return; michael@0: } michael@0: michael@0: mBuffer.Write(aInput); michael@0: michael@0: UpdateOutputBlock(aOutput); michael@0: mBuffer.NextBlock(); michael@0: } michael@0: michael@0: void UpdateOutputBlock(AudioChunk* aOutput) michael@0: { michael@0: TrackTicks tick = mSource->GetCurrentPosition(); michael@0: if (tick == mLastOutputPosition) { michael@0: return; // mLastChunks is already set on the stream michael@0: } michael@0: michael@0: mLastOutputPosition = tick; michael@0: bool inCycle = mSource->AsProcessedStream()->InCycle(); michael@0: double minDelay = inCycle ? static_cast(WEBAUDIO_BLOCK_SIZE) : 0.0; michael@0: double maxDelay = mMaxDelay; michael@0: double sampleRate = mSource->SampleRate(); michael@0: ChannelInterpretation channelInterpretation = michael@0: mSource->GetChannelInterpretation(); michael@0: if (mDelay.HasSimpleValue()) { michael@0: // If this DelayNode is in a cycle, make sure the delay value is at least michael@0: // one block, even if that is greater than maxDelay. michael@0: double delayFrames = mDelay.GetValue() * sampleRate; michael@0: double delayFramesClamped = michael@0: std::max(minDelay, std::min(delayFrames, maxDelay)); michael@0: mBuffer.Read(delayFramesClamped, aOutput, channelInterpretation); michael@0: } else { michael@0: // Compute the delay values for the duration of the input AudioChunk michael@0: // If this DelayNode is in a cycle, make sure the delay value is at least michael@0: // one block. michael@0: double computedDelay[WEBAUDIO_BLOCK_SIZE]; michael@0: for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) { michael@0: double delayAtTick = mDelay.GetValueAtTime(tick, counter) * sampleRate; michael@0: double delayAtTickClamped = michael@0: std::max(minDelay, std::min(delayAtTick, maxDelay)); michael@0: computedDelay[counter] = delayAtTickClamped; michael@0: } michael@0: mBuffer.Read(computedDelay, aOutput, channelInterpretation); michael@0: } michael@0: } michael@0: michael@0: virtual void ProduceBlockBeforeInput(AudioChunk* aOutput) MOZ_OVERRIDE michael@0: { michael@0: if (mLeftOverData <= 0) { michael@0: aOutput->SetNull(WEBAUDIO_BLOCK_SIZE); michael@0: } else { michael@0: UpdateOutputBlock(aOutput); 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: // Not owned: michael@0: // - mSource - probably not owned michael@0: // - mDestination - probably not owned michael@0: // - mDelay - shares ref with AudioNode, don't count michael@0: amount += mBuffer.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: AudioNodeStream* mSource; michael@0: AudioNodeStream* mDestination; michael@0: AudioParamTimeline mDelay; michael@0: DelayBuffer mBuffer; michael@0: double mMaxDelay; michael@0: TrackTicks mLastOutputPosition; michael@0: // How much data we have in our buffer which needs to be flushed out when our inputs michael@0: // finish. michael@0: int32_t mLeftOverData; michael@0: }; michael@0: michael@0: DelayNode::DelayNode(AudioContext* aContext, double aMaxDelay) michael@0: : AudioNode(aContext, michael@0: 2, michael@0: ChannelCountMode::Max, michael@0: ChannelInterpretation::Speakers) michael@0: , mDelay(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendDelayToStream, 0.0f)) michael@0: { michael@0: DelayNodeEngine* engine = michael@0: new DelayNodeEngine(this, aContext->Destination(), michael@0: aContext->SampleRate() * aMaxDelay); michael@0: mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM); michael@0: engine->SetSourceStream(static_cast (mStream.get())); michael@0: } michael@0: michael@0: size_t michael@0: DelayNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); michael@0: amount += mDelay->SizeOfIncludingThis(aMallocSizeOf); michael@0: return amount; michael@0: } michael@0: michael@0: size_t michael@0: DelayNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: JSObject* michael@0: DelayNode::WrapObject(JSContext* aCx) michael@0: { michael@0: return DelayNodeBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: DelayNode::SendDelayToStream(AudioNode* aNode) michael@0: { michael@0: DelayNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, DelayNodeEngine::DELAY, *This->mDelay); michael@0: } michael@0: michael@0: } michael@0: } michael@0: