Line data Source code
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 "AudioNodeStream.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 {
19 : namespace dom {
20 :
21 : using namespace std;
22 :
23 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(StereoPannerNode, AudioNode, mPan)
24 :
25 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(StereoPannerNode)
26 0 : NS_INTERFACE_MAP_END_INHERITING(AudioNode)
27 :
28 0 : NS_IMPL_ADDREF_INHERITED(StereoPannerNode, AudioNode)
29 0 : NS_IMPL_RELEASE_INHERITED(StereoPannerNode, AudioNode)
30 :
31 0 : class StereoPannerNodeEngine final : public AudioNodeEngine
32 : {
33 : public:
34 0 : StereoPannerNodeEngine(AudioNode* aNode,
35 : AudioDestinationNode* aDestination)
36 0 : : AudioNodeEngine(aNode)
37 0 : , mDestination(aDestination->Stream())
38 : // Keep the default value in sync with the default value in
39 : // StereoPannerNode::StereoPannerNode.
40 0 : , mPan(0.f)
41 : {
42 0 : }
43 :
44 : enum Parameters {
45 : PAN
46 : };
47 0 : void RecvTimelineEvent(uint32_t aIndex,
48 : AudioTimelineEvent& aEvent) override
49 : {
50 0 : MOZ_ASSERT(mDestination);
51 0 : WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent,
52 0 : mDestination);
53 :
54 0 : switch (aIndex) {
55 : case PAN:
56 0 : mPan.InsertEvent<int64_t>(aEvent);
57 0 : break;
58 : default:
59 0 : NS_ERROR("Bad StereoPannerNode TimelineParameter");
60 : }
61 0 : }
62 :
63 0 : void GetGainValuesForPanning(float aPanning,
64 : bool aMonoToStereo,
65 : float& aLeftGain,
66 : float& aRightGain)
67 : {
68 : // Clamp and normalize the panning in [0; 1]
69 0 : aPanning = std::min(std::max(aPanning, -1.f), 1.f);
70 :
71 0 : if (aMonoToStereo) {
72 0 : aPanning += 1;
73 0 : aPanning /= 2;
74 0 : } else if (aPanning <= 0) {
75 0 : aPanning += 1;
76 : }
77 :
78 0 : aLeftGain = cos(0.5 * M_PI * aPanning);
79 0 : aRightGain = sin(0.5 * M_PI * aPanning);
80 0 : }
81 :
82 0 : void SetToSilentStereoBlock(AudioBlock* aChunk)
83 : {
84 0 : for (uint32_t channel = 0; channel < 2; channel++) {
85 0 : float* samples = aChunk->ChannelFloatsForWrite(channel);
86 0 : for (uint32_t i = 0; i < WEBAUDIO_BLOCK_SIZE; i++) {
87 0 : samples[i] = 0.f;
88 : }
89 : }
90 0 : }
91 :
92 0 : void UpmixToStereoIfNeeded(const AudioBlock& aInput, AudioBlock* aOutput)
93 : {
94 0 : if (aInput.ChannelCount() == 2) {
95 0 : const float* inputL = static_cast<const float*>(aInput.mChannelData[0]);
96 0 : const float* inputR = static_cast<const float*>(aInput.mChannelData[1]);
97 0 : float* outputL = aOutput->ChannelFloatsForWrite(0);
98 0 : float* outputR = aOutput->ChannelFloatsForWrite(1);
99 :
100 0 : AudioBlockCopyChannelWithScale(inputL, aInput.mVolume, outputL);
101 0 : AudioBlockCopyChannelWithScale(inputR, aInput.mVolume, outputR);
102 : } else {
103 0 : MOZ_ASSERT(aInput.ChannelCount() == 1);
104 0 : GainMonoToStereo(aInput, aOutput, aInput.mVolume, aInput.mVolume);
105 : }
106 0 : }
107 :
108 0 : virtual void ProcessBlock(AudioNodeStream* aStream,
109 : GraphTime aFrom,
110 : const AudioBlock& aInput,
111 : AudioBlock* aOutput,
112 : bool *aFinished) override
113 : {
114 : // The output of this node is always stereo, no matter what the inputs are.
115 0 : MOZ_ASSERT(aInput.ChannelCount() <= 2);
116 0 : aOutput->AllocateChannels(2);
117 0 : bool monoToStereo = aInput.ChannelCount() == 1;
118 :
119 0 : if (aInput.IsNull()) {
120 : // If input is silent, so is the output
121 0 : SetToSilentStereoBlock(aOutput);
122 0 : } else if (mPan.HasSimpleValue()) {
123 0 : float panning = mPan.GetValue();
124 : // If the panning is 0.0, we can simply copy the input to the
125 : // output with gain applied, up-mixing to stereo if needed.
126 0 : if (panning == 0.0f) {
127 0 : UpmixToStereoIfNeeded(aInput, aOutput);
128 : } else {
129 : // Optimize the case where the panning is constant for this processing
130 : // block: we can just apply a constant gain on the left and right
131 : // channel
132 : float gainL, gainR;
133 :
134 0 : GetGainValuesForPanning(panning, monoToStereo, gainL, gainR);
135 0 : ApplyStereoPanning(aInput, aOutput,
136 0 : gainL * aInput.mVolume,
137 0 : gainR * aInput.mVolume,
138 0 : panning <= 0);
139 : }
140 : } else {
141 : float computedGain[2*WEBAUDIO_BLOCK_SIZE + 4];
142 : bool onLeft[WEBAUDIO_BLOCK_SIZE];
143 :
144 : float values[WEBAUDIO_BLOCK_SIZE];
145 0 : StreamTime tick = mDestination->GraphTimeToStreamTime(aFrom);
146 0 : mPan.GetValuesAtTime(tick, values, WEBAUDIO_BLOCK_SIZE);
147 :
148 0 : float* alignedComputedGain = ALIGNED16(computedGain);
149 0 : ASSERT_ALIGNED16(alignedComputedGain);
150 0 : for (size_t counter = 0; counter < WEBAUDIO_BLOCK_SIZE; ++counter) {
151 : float left, right;
152 0 : GetGainValuesForPanning(values[counter], monoToStereo, left, right);
153 :
154 0 : alignedComputedGain[counter] = left * aInput.mVolume;
155 0 : alignedComputedGain[WEBAUDIO_BLOCK_SIZE + counter] = right * aInput.mVolume;
156 0 : onLeft[counter] = values[counter] <= 0;
157 : }
158 :
159 : // Apply the gain to the output buffer
160 0 : ApplyStereoPanning(aInput, aOutput, alignedComputedGain, &alignedComputedGain[WEBAUDIO_BLOCK_SIZE], onLeft);
161 : }
162 0 : }
163 :
164 0 : virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
165 : {
166 0 : return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
167 : }
168 :
169 : AudioNodeStream* mDestination;
170 : AudioParamTimeline mPan;
171 : };
172 :
173 0 : StereoPannerNode::StereoPannerNode(AudioContext* aContext)
174 : : AudioNode(aContext,
175 : 2,
176 : ChannelCountMode::Clamped_max,
177 : ChannelInterpretation::Speakers)
178 0 : , mPan(new AudioParam(this, StereoPannerNodeEngine::PAN, "pan", 0.f, -1.f, 1.f))
179 : {
180 0 : StereoPannerNodeEngine* engine = new StereoPannerNodeEngine(this, aContext->Destination());
181 0 : mStream = AudioNodeStream::Create(aContext, engine,
182 : AudioNodeStream::NO_STREAM_FLAGS,
183 0 : aContext->Graph());
184 0 : }
185 :
186 : /* static */ already_AddRefed<StereoPannerNode>
187 0 : StereoPannerNode::Create(AudioContext& aAudioContext,
188 : const StereoPannerOptions& aOptions,
189 : ErrorResult& aRv)
190 : {
191 0 : if (aAudioContext.CheckClosed(aRv)) {
192 0 : return nullptr;
193 : }
194 :
195 0 : RefPtr<StereoPannerNode> audioNode = new StereoPannerNode(&aAudioContext);
196 :
197 0 : audioNode->Initialize(aOptions, aRv);
198 0 : if (NS_WARN_IF(aRv.Failed())) {
199 0 : return nullptr;
200 : }
201 :
202 0 : audioNode->Pan()->SetValue(aOptions.mPan);
203 0 : return audioNode.forget();
204 : }
205 :
206 : size_t
207 0 : StereoPannerNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
208 : {
209 0 : size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
210 0 : amount += mPan->SizeOfIncludingThis(aMallocSizeOf);
211 0 : return amount;
212 : }
213 :
214 : size_t
215 0 : StereoPannerNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
216 : {
217 0 : return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
218 : }
219 :
220 : JSObject*
221 0 : StereoPannerNode::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
222 : {
223 0 : return StereoPannerNodeBinding::Wrap(aCx, this, aGivenProto);
224 : }
225 :
226 : } // namespace dom
227 : } // namespace mozilla
|