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