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 "DynamicsCompressorNode.h" michael@0: #include "mozilla/dom/DynamicsCompressorNodeBinding.h" michael@0: #include "AudioNodeEngine.h" michael@0: #include "AudioNodeStream.h" michael@0: #include "AudioDestinationNode.h" michael@0: #include "WebAudioUtils.h" michael@0: #include "blink/DynamicsCompressor.h" michael@0: michael@0: using WebCore::DynamicsCompressor; michael@0: michael@0: namespace mozilla { michael@0: namespace dom { michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_INHERITED(DynamicsCompressorNode, AudioNode, michael@0: mThreshold, michael@0: mKnee, michael@0: mRatio, michael@0: mReduction, michael@0: mAttack, michael@0: mRelease) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DynamicsCompressorNode) michael@0: NS_INTERFACE_MAP_END_INHERITING(AudioNode) michael@0: michael@0: NS_IMPL_ADDREF_INHERITED(DynamicsCompressorNode, AudioNode) michael@0: NS_IMPL_RELEASE_INHERITED(DynamicsCompressorNode, AudioNode) michael@0: michael@0: class DynamicsCompressorNodeEngine : public AudioNodeEngine michael@0: { michael@0: public: michael@0: explicit DynamicsCompressorNodeEngine(AudioNode* aNode, michael@0: AudioDestinationNode* aDestination) 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 michael@0: // DynamicsCompressorNode::DynamicsCompressorNode. michael@0: , mThreshold(-24.f) michael@0: , mKnee(30.f) michael@0: , mRatio(12.f) michael@0: , mAttack(0.003f) michael@0: , mRelease(0.25f) michael@0: , mCompressor(new DynamicsCompressor(mDestination->SampleRate(), 2)) michael@0: { 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: THRESHOLD, michael@0: KNEE, michael@0: RATIO, michael@0: ATTACK, michael@0: RELEASE michael@0: }; michael@0: void SetTimelineParameter(uint32_t aIndex, michael@0: const AudioParamTimeline& aValue, michael@0: TrackRate aSampleRate) MOZ_OVERRIDE michael@0: { michael@0: MOZ_ASSERT(mSource && mDestination); michael@0: switch (aIndex) { michael@0: case THRESHOLD: michael@0: mThreshold = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mThreshold, mSource, mDestination); michael@0: break; michael@0: case KNEE: michael@0: mKnee = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mKnee, mSource, mDestination); michael@0: break; michael@0: case RATIO: michael@0: mRatio = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mRatio, mSource, mDestination); michael@0: break; michael@0: case ATTACK: michael@0: mAttack = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mAttack, mSource, mDestination); michael@0: break; michael@0: case RELEASE: michael@0: mRelease = aValue; michael@0: WebAudioUtils::ConvertAudioParamToTicks(mRelease, mSource, mDestination); michael@0: break; michael@0: default: michael@0: NS_ERROR("Bad DynamicsCompresssorNodeEngine 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: if (aInput.IsNull()) { michael@0: // Just output silence michael@0: *aOutput = aInput; michael@0: return; michael@0: } michael@0: michael@0: const uint32_t channelCount = aInput.mChannelData.Length(); michael@0: if (mCompressor->numberOfChannels() != channelCount) { michael@0: // Create a new compressor object with a new channel count michael@0: mCompressor = new WebCore::DynamicsCompressor(aStream->SampleRate(), michael@0: aInput.mChannelData.Length()); michael@0: } michael@0: michael@0: TrackTicks pos = aStream->GetCurrentPosition(); michael@0: mCompressor->setParameterValue(DynamicsCompressor::ParamThreshold, michael@0: mThreshold.GetValueAtTime(pos)); michael@0: mCompressor->setParameterValue(DynamicsCompressor::ParamKnee, michael@0: mKnee.GetValueAtTime(pos)); michael@0: mCompressor->setParameterValue(DynamicsCompressor::ParamRatio, michael@0: mRatio.GetValueAtTime(pos)); michael@0: mCompressor->setParameterValue(DynamicsCompressor::ParamAttack, michael@0: mAttack.GetValueAtTime(pos)); michael@0: mCompressor->setParameterValue(DynamicsCompressor::ParamRelease, michael@0: mRelease.GetValueAtTime(pos)); michael@0: michael@0: AllocateAudioBlock(channelCount, aOutput); michael@0: mCompressor->process(&aInput, aOutput, aInput.GetDuration()); michael@0: michael@0: SendReductionParamToMainThread(aStream, michael@0: mCompressor->parameterValue(DynamicsCompressor::ParamReduction)); michael@0: } michael@0: michael@0: virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE michael@0: { michael@0: // Not owned: michael@0: // - mSource (probably) michael@0: // - mDestination (probably) michael@0: // - Don't count the AudioParamTimelines, their inner refs are owned by the michael@0: // AudioNode. michael@0: size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf); michael@0: amount += mCompressor->sizeOfIncludingThis(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: void SendReductionParamToMainThread(AudioNodeStream* aStream, float aReduction) michael@0: { michael@0: MOZ_ASSERT(!NS_IsMainThread()); michael@0: michael@0: class Command : public nsRunnable michael@0: { michael@0: public: michael@0: Command(AudioNodeStream* aStream, float aReduction) michael@0: : mStream(aStream) michael@0: , mReduction(aReduction) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHODIMP Run() michael@0: { michael@0: nsRefPtr node; michael@0: { michael@0: // No need to keep holding the lock for the whole duration of this michael@0: // function, since we're holding a strong reference to it, so if michael@0: // we can obtain the reference, we will hold the node alive in michael@0: // this function. michael@0: MutexAutoLock lock(mStream->Engine()->NodeMutex()); michael@0: node = static_cast(mStream->Engine()->Node()); michael@0: } michael@0: if (node) { michael@0: AudioParam* reduction = node->Reduction(); michael@0: reduction->CancelAllEvents(); michael@0: reduction->SetValue(mReduction); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mStream; michael@0: float mReduction; michael@0: }; michael@0: michael@0: NS_DispatchToMainThread(new Command(aStream, aReduction)); michael@0: } michael@0: michael@0: private: michael@0: AudioNodeStream* mSource; michael@0: AudioNodeStream* mDestination; michael@0: AudioParamTimeline mThreshold; michael@0: AudioParamTimeline mKnee; michael@0: AudioParamTimeline mRatio; michael@0: AudioParamTimeline mAttack; michael@0: AudioParamTimeline mRelease; michael@0: nsAutoPtr mCompressor; michael@0: }; michael@0: michael@0: DynamicsCompressorNode::DynamicsCompressorNode(AudioContext* aContext) michael@0: : AudioNode(aContext, michael@0: 2, michael@0: ChannelCountMode::Explicit, michael@0: ChannelInterpretation::Speakers) michael@0: , mThreshold(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendThresholdToStream, -24.f)) michael@0: , mKnee(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendKneeToStream, 30.f)) michael@0: , mRatio(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendRatioToStream, 12.f)) michael@0: , mReduction(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: Callback, 0.f)) michael@0: , mAttack(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendAttackToStream, 0.003f)) michael@0: , mRelease(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(), michael@0: SendReleaseToStream, 0.25f)) michael@0: { michael@0: DynamicsCompressorNodeEngine* engine = new DynamicsCompressorNodeEngine(this, aContext->Destination()); 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: DynamicsCompressorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf); michael@0: amount += mThreshold->SizeOfIncludingThis(aMallocSizeOf); michael@0: amount += mKnee->SizeOfIncludingThis(aMallocSizeOf); michael@0: amount += mRatio->SizeOfIncludingThis(aMallocSizeOf); michael@0: amount += mAttack->SizeOfIncludingThis(aMallocSizeOf); michael@0: amount += mRelease->SizeOfIncludingThis(aMallocSizeOf); michael@0: return amount; michael@0: } michael@0: michael@0: size_t michael@0: DynamicsCompressorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const michael@0: { michael@0: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); michael@0: } michael@0: michael@0: JSObject* michael@0: DynamicsCompressorNode::WrapObject(JSContext* aCx) michael@0: { michael@0: return DynamicsCompressorNodeBinding::Wrap(aCx, this); michael@0: } michael@0: michael@0: void michael@0: DynamicsCompressorNode::SendThresholdToStream(AudioNode* aNode) michael@0: { michael@0: DynamicsCompressorNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, DynamicsCompressorNodeEngine::THRESHOLD, *This->mThreshold); michael@0: } michael@0: michael@0: void michael@0: DynamicsCompressorNode::SendKneeToStream(AudioNode* aNode) michael@0: { michael@0: DynamicsCompressorNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, DynamicsCompressorNodeEngine::KNEE, *This->mKnee); michael@0: } michael@0: michael@0: void michael@0: DynamicsCompressorNode::SendRatioToStream(AudioNode* aNode) michael@0: { michael@0: DynamicsCompressorNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, DynamicsCompressorNodeEngine::RATIO, *This->mRatio); michael@0: } michael@0: michael@0: void michael@0: DynamicsCompressorNode::SendAttackToStream(AudioNode* aNode) michael@0: { michael@0: DynamicsCompressorNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, DynamicsCompressorNodeEngine::ATTACK, *This->mAttack); michael@0: } michael@0: michael@0: void michael@0: DynamicsCompressorNode::SendReleaseToStream(AudioNode* aNode) michael@0: { michael@0: DynamicsCompressorNode* This = static_cast(aNode); michael@0: SendTimelineParameterToStream(This, DynamicsCompressorNodeEngine::RELEASE, *This->mRelease); michael@0: } michael@0: michael@0: } michael@0: } michael@0: