1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #include "DynamicsCompressorNode.h"
8 #include "mozilla/dom/DynamicsCompressorNodeBinding.h"
9 #include "AudioNodeEngine.h"
10 #include "AudioNodeTrack.h"
11 #include "AudioDestinationNode.h"
12 #include "WebAudioUtils.h"
13 #include "blink/DynamicsCompressor.h"
14 
15 using WebCore::DynamicsCompressor;
16 
17 namespace mozilla::dom {
18 
19 NS_IMPL_CYCLE_COLLECTION_INHERITED(DynamicsCompressorNode, AudioNode,
20                                    mThreshold, mKnee, mRatio, mAttack, mRelease)
21 
22 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DynamicsCompressorNode)
23 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
24 
25 NS_IMPL_ADDREF_INHERITED(DynamicsCompressorNode, AudioNode)
26 NS_IMPL_RELEASE_INHERITED(DynamicsCompressorNode, AudioNode)
27 
28 class DynamicsCompressorNodeEngine final : public AudioNodeEngine {
29  public:
DynamicsCompressorNodeEngine(AudioNode * aNode,AudioDestinationNode * aDestination)30   explicit DynamicsCompressorNodeEngine(AudioNode* aNode,
31                                         AudioDestinationNode* aDestination)
32       : AudioNodeEngine(aNode),
33         mDestination(aDestination->Track())
34         // Keep the default value in sync with the default value in
35         // DynamicsCompressorNode::DynamicsCompressorNode.
36         ,
37         mThreshold(-24.f),
38         mKnee(30.f),
39         mRatio(12.f),
40         mAttack(0.003f),
41         mRelease(0.25f),
42         mCompressor(new DynamicsCompressor(mDestination->mSampleRate, 2)) {}
43 
44   enum Parameters { THRESHOLD, KNEE, RATIO, ATTACK, RELEASE };
RecvTimelineEvent(uint32_t aIndex,AudioTimelineEvent & aEvent)45   void RecvTimelineEvent(uint32_t aIndex, AudioTimelineEvent& aEvent) override {
46     MOZ_ASSERT(mDestination);
47 
48     WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent, mDestination);
49 
50     switch (aIndex) {
51       case THRESHOLD:
52         mThreshold.InsertEvent<int64_t>(aEvent);
53         break;
54       case KNEE:
55         mKnee.InsertEvent<int64_t>(aEvent);
56         break;
57       case RATIO:
58         mRatio.InsertEvent<int64_t>(aEvent);
59         break;
60       case ATTACK:
61         mAttack.InsertEvent<int64_t>(aEvent);
62         break;
63       case RELEASE:
64         mRelease.InsertEvent<int64_t>(aEvent);
65         break;
66       default:
67         NS_ERROR("Bad DynamicsCompresssorNodeEngine TimelineParameter");
68     }
69   }
70 
ProcessBlock(AudioNodeTrack * aTrack,GraphTime aFrom,const AudioBlock & aInput,AudioBlock * aOutput,bool * aFinished)71   void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
72                     const AudioBlock& aInput, AudioBlock* aOutput,
73                     bool* aFinished) override {
74     if (aInput.IsNull()) {
75       // Just output silence
76       *aOutput = aInput;
77       return;
78     }
79 
80     const uint32_t channelCount = aInput.ChannelCount();
81     if (mCompressor->numberOfChannels() != channelCount) {
82       // Create a new compressor object with a new channel count
83       mCompressor = MakeUnique<WebCore::DynamicsCompressor>(
84           aTrack->mSampleRate, aInput.ChannelCount());
85     }
86 
87     TrackTime pos = mDestination->GraphTimeToTrackTime(aFrom);
88     mCompressor->setParameterValue(DynamicsCompressor::ParamThreshold,
89                                    mThreshold.GetValueAtTime(pos));
90     mCompressor->setParameterValue(DynamicsCompressor::ParamKnee,
91                                    mKnee.GetValueAtTime(pos));
92     mCompressor->setParameterValue(DynamicsCompressor::ParamRatio,
93                                    mRatio.GetValueAtTime(pos));
94     mCompressor->setParameterValue(DynamicsCompressor::ParamAttack,
95                                    mAttack.GetValueAtTime(pos));
96     mCompressor->setParameterValue(DynamicsCompressor::ParamRelease,
97                                    mRelease.GetValueAtTime(pos));
98 
99     aOutput->AllocateChannels(channelCount);
100     mCompressor->process(&aInput, aOutput, aInput.GetDuration());
101 
102     SendReductionParamToMainThread(
103         aTrack,
104         mCompressor->parameterValue(DynamicsCompressor::ParamReduction));
105   }
106 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const107   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
108     // Not owned:
109     // - mDestination (probably)
110     // - Don't count the AudioParamTimelines, their inner refs are owned by the
111     // AudioNode.
112     size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
113     amount += mCompressor->sizeOfIncludingThis(aMallocSizeOf);
114     return amount;
115   }
116 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const117   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
118     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
119   }
120 
121  private:
SendReductionParamToMainThread(AudioNodeTrack * aTrack,float aReduction)122   void SendReductionParamToMainThread(AudioNodeTrack* aTrack,
123                                       float aReduction) {
124     MOZ_ASSERT(!NS_IsMainThread());
125 
126     class Command final : public Runnable {
127      public:
128       Command(AudioNodeTrack* aTrack, float aReduction)
129           : mozilla::Runnable("Command"),
130             mTrack(aTrack),
131             mReduction(aReduction) {}
132 
133       NS_IMETHOD Run() override {
134         RefPtr<DynamicsCompressorNode> node =
135             static_cast<DynamicsCompressorNode*>(
136                 mTrack->Engine()->NodeMainThread());
137         if (node) {
138           node->SetReduction(mReduction);
139         }
140         return NS_OK;
141       }
142 
143      private:
144       RefPtr<AudioNodeTrack> mTrack;
145       float mReduction;
146     };
147 
148     mAbstractMainThread->Dispatch(do_AddRef(new Command(aTrack, aReduction)));
149   }
150 
151  private:
152   RefPtr<AudioNodeTrack> mDestination;
153   AudioParamTimeline mThreshold;
154   AudioParamTimeline mKnee;
155   AudioParamTimeline mRatio;
156   AudioParamTimeline mAttack;
157   AudioParamTimeline mRelease;
158   UniquePtr<DynamicsCompressor> mCompressor;
159 };
160 
DynamicsCompressorNode(AudioContext * aContext)161 DynamicsCompressorNode::DynamicsCompressorNode(AudioContext* aContext)
162     : AudioNode(aContext, 2, ChannelCountMode::Clamped_max,
163                 ChannelInterpretation::Speakers),
164       mReduction(0) {
165   mThreshold = CreateAudioParam(DynamicsCompressorNodeEngine::THRESHOLD,
166                                 u"threshold"_ns, -24.f, -100.f, 0.f);
167   mKnee = CreateAudioParam(DynamicsCompressorNodeEngine::KNEE, u"knee"_ns, 30.f,
168                            0.f, 40.f);
169   mRatio = CreateAudioParam(DynamicsCompressorNodeEngine::RATIO, u"ratio"_ns,
170                             12.f, 1.f, 20.f);
171   mAttack = CreateAudioParam(DynamicsCompressorNodeEngine::ATTACK, u"attack"_ns,
172                              0.003f, 0.f, 1.f);
173   mRelease = CreateAudioParam(DynamicsCompressorNodeEngine::RELEASE,
174                               u"release"_ns, 0.25f, 0.f, 1.f);
175   DynamicsCompressorNodeEngine* engine =
176       new DynamicsCompressorNodeEngine(this, aContext->Destination());
177   mTrack = AudioNodeTrack::Create(
178       aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
179 }
180 
181 /* static */
Create(AudioContext & aAudioContext,const DynamicsCompressorOptions & aOptions,ErrorResult & aRv)182 already_AddRefed<DynamicsCompressorNode> DynamicsCompressorNode::Create(
183     AudioContext& aAudioContext, const DynamicsCompressorOptions& aOptions,
184     ErrorResult& aRv) {
185   RefPtr<DynamicsCompressorNode> audioNode =
186       new DynamicsCompressorNode(&aAudioContext);
187 
188   audioNode->Initialize(aOptions, aRv);
189   if (NS_WARN_IF(aRv.Failed())) {
190     return nullptr;
191   }
192 
193   audioNode->Attack()->SetInitialValue(aOptions.mAttack);
194   audioNode->Knee()->SetInitialValue(aOptions.mKnee);
195   audioNode->Ratio()->SetInitialValue(aOptions.mRatio);
196   audioNode->GetRelease()->SetInitialValue(aOptions.mRelease);
197   audioNode->Threshold()->SetInitialValue(aOptions.mThreshold);
198 
199   return audioNode.forget();
200 }
201 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const202 size_t DynamicsCompressorNode::SizeOfExcludingThis(
203     MallocSizeOf aMallocSizeOf) const {
204   size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
205   amount += mThreshold->SizeOfIncludingThis(aMallocSizeOf);
206   amount += mKnee->SizeOfIncludingThis(aMallocSizeOf);
207   amount += mRatio->SizeOfIncludingThis(aMallocSizeOf);
208   amount += mAttack->SizeOfIncludingThis(aMallocSizeOf);
209   amount += mRelease->SizeOfIncludingThis(aMallocSizeOf);
210   return amount;
211 }
212 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const213 size_t DynamicsCompressorNode::SizeOfIncludingThis(
214     MallocSizeOf aMallocSizeOf) const {
215   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
216 }
217 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)218 JSObject* DynamicsCompressorNode::WrapObject(
219     JSContext* aCx, JS::Handle<JSObject*> aGivenProto) {
220   return DynamicsCompressorNode_Binding::Wrap(aCx, this, aGivenProto);
221 }
222 
223 }  // namespace mozilla::dom
224