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