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 "StereoPannerNode.h"
8 #include "mozilla/dom/StereoPannerNodeBinding.h"
9 #include "AudioNodeEngine.h"
10 #include "AudioNodeTrack.h"
11 #include "AudioDestinationNode.h"
12 #include "AlignmentUtils.h"
13 #include "WebAudioUtils.h"
14 #include "PanningUtils.h"
15 #include "AudioParamTimeline.h"
16 #include "AudioParam.h"
17 
18 namespace mozilla::dom {
19 
20 NS_IMPL_CYCLE_COLLECTION_INHERITED(StereoPannerNode, AudioNode, mPan)
21 
22 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(StereoPannerNode)
23 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
24 
25 NS_IMPL_ADDREF_INHERITED(StereoPannerNode, AudioNode)
26 NS_IMPL_RELEASE_INHERITED(StereoPannerNode, AudioNode)
27 
28 class StereoPannerNodeEngine final : public AudioNodeEngine {
29  public:
StereoPannerNodeEngine(AudioNode * aNode,AudioDestinationNode * aDestination)30   StereoPannerNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
31       : AudioNodeEngine(aNode),
32         mDestination(aDestination->Track())
33         // Keep the default value in sync with the default value in
34         // StereoPannerNode::StereoPannerNode.
35         ,
36         mPan(0.f) {}
37 
38   enum Parameters { PAN };
RecvTimelineEvent(uint32_t aIndex,AudioTimelineEvent & aEvent)39   void RecvTimelineEvent(uint32_t aIndex, AudioTimelineEvent& aEvent) override {
40     MOZ_ASSERT(mDestination);
41     WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent, mDestination);
42 
43     switch (aIndex) {
44       case PAN:
45         mPan.InsertEvent<int64_t>(aEvent);
46         break;
47       default:
48         NS_ERROR("Bad StereoPannerNode TimelineParameter");
49     }
50   }
51 
GetGainValuesForPanning(float aPanning,bool aMonoToStereo,float & aLeftGain,float & aRightGain)52   void GetGainValuesForPanning(float aPanning, bool aMonoToStereo,
53                                float& aLeftGain, float& aRightGain) {
54     // Clamp and normalize the panning in [0; 1]
55     aPanning = std::min(std::max(aPanning, -1.f), 1.f);
56 
57     if (aMonoToStereo) {
58       aPanning += 1;
59       aPanning /= 2;
60     } else if (aPanning <= 0) {
61       aPanning += 1;
62     }
63 
64     aLeftGain = cos(0.5 * M_PI * aPanning);
65     aRightGain = sin(0.5 * M_PI * aPanning);
66   }
67 
SetToSilentStereoBlock(AudioBlock * aChunk)68   void SetToSilentStereoBlock(AudioBlock* aChunk) {
69     for (uint32_t channel = 0; channel < 2; channel++) {
70       float* samples = aChunk->ChannelFloatsForWrite(channel);
71       for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; i++) {
72         samples[i] = 0.f;
73       }
74     }
75   }
76 
UpmixToStereoIfNeeded(const AudioBlock & aInput,AudioBlock * aOutput)77   void UpmixToStereoIfNeeded(const AudioBlock& aInput, AudioBlock* aOutput) {
78     if (aInput.ChannelCount() == 2) {
79       *aOutput = aInput;
80     } else {
81       MOZ_ASSERT(aInput.ChannelCount() == 1);
82       aOutput->SetBuffer(aInput.GetBuffer());
83       aOutput->mChannelData.SetLength(2);
84       for (uint32_t i = 0; i < 2; ++i) {
85         aOutput->mChannelData[i] = aInput.ChannelData<float>()[0];
86       }
87       // 1/sqrt(2) multiplier is because StereoPanner up-mixing differs from
88       // input up-mixing.
89       aOutput->mVolume = M_SQRT1_2 * aInput.mVolume;
90       aOutput->mBufferFormat = AUDIO_FORMAT_FLOAT32;
91     }
92   }
93 
ProcessBlock(AudioNodeTrack * aTrack,GraphTime aFrom,const AudioBlock & aInput,AudioBlock * aOutput,bool * aFinished)94   virtual void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
95                             const AudioBlock& aInput, AudioBlock* aOutput,
96                             bool* aFinished) override {
97     // The output of this node is always stereo, no matter what the inputs are.
98     MOZ_ASSERT(aInput.ChannelCount() <= 2);
99     bool monoToStereo = aInput.ChannelCount() == 1;
100 
101     if (aInput.IsNull()) {
102       // If input is silent, so is the output
103       aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
104     } else if (mPan.HasSimpleValue()) {
105       float panning = mPan.GetValue();
106       // If the panning is 0.0, we can simply copy the input to the
107       // output with gain applied, up-mixing to stereo if needed.
108       if (panning == 0.0f) {
109         UpmixToStereoIfNeeded(aInput, aOutput);
110       } else {
111         // Optimize the case where the panning is constant for this processing
112         // block: we can just apply a constant gain on the left and right
113         // channel
114         float gainL, gainR;
115 
116         GetGainValuesForPanning(panning, monoToStereo, gainL, gainR);
117         ApplyStereoPanning(aInput, aOutput, gainL, gainR, panning <= 0);
118       }
119     } else {
120       float computedGain[2 * WEBAUDIO_BLOCK_SIZE + 4];
121       bool onLeft[WEBAUDIO_BLOCK_SIZE];
122 
123       float values[WEBAUDIO_BLOCK_SIZE];
124       TrackTime tick = mDestination->GraphTimeToTrackTime(aFrom);
125       mPan.GetValuesAtTime(tick, values, WEBAUDIO_BLOCK_SIZE);
126 
127       float* alignedComputedGain = ALIGNED16(computedGain);
128       ASSERT_ALIGNED16(alignedComputedGain);
129       for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) {
130         float left, right;
131         GetGainValuesForPanning(values[counter], monoToStereo, left, right);
132 
133         alignedComputedGain[counter] = left;
134         alignedComputedGain[WEBAUDIO_BLOCK_SIZE + counter] = right;
135         onLeft[counter] = values[counter] <= 0;
136       }
137 
138       // Apply the gain to the output buffer
139       ApplyStereoPanning(aInput, aOutput, alignedComputedGain,
140                          &alignedComputedGain[WEBAUDIO_BLOCK_SIZE], onLeft);
141     }
142   }
143 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const144   virtual size_t SizeOfIncludingThis(
145       MallocSizeOf aMallocSizeOf) const override {
146     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
147   }
148 
149   RefPtr<AudioNodeTrack> mDestination;
150   AudioParamTimeline mPan;
151 };
152 
StereoPannerNode(AudioContext * aContext)153 StereoPannerNode::StereoPannerNode(AudioContext* aContext)
154     : AudioNode(aContext, 2, ChannelCountMode::Clamped_max,
155                 ChannelInterpretation::Speakers) {
156   mPan =
157       CreateAudioParam(StereoPannerNodeEngine::PAN, u"pan"_ns, 0.f, -1.f, 1.f);
158   StereoPannerNodeEngine* engine =
159       new StereoPannerNodeEngine(this, aContext->Destination());
160   mTrack = AudioNodeTrack::Create(
161       aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
162 }
163 
164 /* static */
Create(AudioContext & aAudioContext,const StereoPannerOptions & aOptions,ErrorResult & aRv)165 already_AddRefed<StereoPannerNode> StereoPannerNode::Create(
166     AudioContext& aAudioContext, const StereoPannerOptions& aOptions,
167     ErrorResult& aRv) {
168   RefPtr<StereoPannerNode> audioNode = new StereoPannerNode(&aAudioContext);
169 
170   audioNode->Initialize(aOptions, aRv);
171   if (NS_WARN_IF(aRv.Failed())) {
172     return nullptr;
173   }
174 
175   audioNode->Pan()->SetInitialValue(aOptions.mPan);
176   return audioNode.forget();
177 }
178 
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const179 size_t StereoPannerNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
180   size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
181   amount += mPan->SizeOfIncludingThis(aMallocSizeOf);
182   return amount;
183 }
184 
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const185 size_t StereoPannerNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
186   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
187 }
188 
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)189 JSObject* StereoPannerNode::WrapObject(JSContext* aCx,
190                                        JS::Handle<JSObject*> aGivenProto) {
191   return StereoPannerNode_Binding::Wrap(aCx, this, aGivenProto);
192 }
193 
194 }  // namespace mozilla::dom
195