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 "WaveShaperNode.h"
8 #include "mozilla/dom/WaveShaperNodeBinding.h"
9 #include "AlignmentUtils.h"
10 #include "AudioNode.h"
11 #include "AudioNodeEngine.h"
12 #include "AudioNodeTrack.h"
13 #include "mozilla/PodOperations.h"
14
15 namespace mozilla::dom {
16
17 NS_IMPL_CYCLE_COLLECTION_CLASS(WaveShaperNode)
18
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WaveShaperNode,AudioNode)19 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WaveShaperNode, AudioNode)
20 NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
21 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
22
23 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WaveShaperNode, AudioNode)
24 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
25
26 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WaveShaperNode)
27 NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
28 NS_IMPL_CYCLE_COLLECTION_TRACE_END
29
30 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WaveShaperNode)
31 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
32
33 NS_IMPL_ADDREF_INHERITED(WaveShaperNode, AudioNode)
34 NS_IMPL_RELEASE_INHERITED(WaveShaperNode, AudioNode)
35
36 static uint32_t ValueOf(OverSampleType aType) {
37 switch (aType) {
38 case OverSampleType::None:
39 return 1;
40 case OverSampleType::_2x:
41 return 2;
42 case OverSampleType::_4x:
43 return 4;
44 default:
45 MOZ_ASSERT_UNREACHABLE("We should never reach here");
46 return 1;
47 }
48 }
49
50 class Resampler final {
51 public:
Resampler()52 Resampler()
53 : mType(OverSampleType::None),
54 mUpSampler(nullptr),
55 mDownSampler(nullptr),
56 mChannels(0),
57 mSampleRate(0) {}
58
~Resampler()59 ~Resampler() { Destroy(); }
60
Reset(uint32_t aChannels,TrackRate aSampleRate,OverSampleType aType)61 void Reset(uint32_t aChannels, TrackRate aSampleRate, OverSampleType aType) {
62 if (aChannels == mChannels && aSampleRate == mSampleRate &&
63 aType == mType) {
64 return;
65 }
66
67 mChannels = aChannels;
68 mSampleRate = aSampleRate;
69 mType = aType;
70
71 Destroy();
72
73 if (aType == OverSampleType::None) {
74 mBuffer.Clear();
75 return;
76 }
77
78 mUpSampler = speex_resampler_init(aChannels, aSampleRate,
79 aSampleRate * ValueOf(aType),
80 SPEEX_RESAMPLER_QUALITY_MIN, nullptr);
81 mDownSampler =
82 speex_resampler_init(aChannels, aSampleRate * ValueOf(aType),
83 aSampleRate, SPEEX_RESAMPLER_QUALITY_MIN, nullptr);
84 mBuffer.SetLength(WEBAUDIO_BLOCK_SIZE * ValueOf(aType));
85 }
86
UpSample(uint32_t aChannel,const float * aInputData,uint32_t aBlocks)87 float* UpSample(uint32_t aChannel, const float* aInputData,
88 uint32_t aBlocks) {
89 uint32_t inSamples = WEBAUDIO_BLOCK_SIZE;
90 uint32_t outSamples = WEBAUDIO_BLOCK_SIZE * aBlocks;
91 float* outputData = mBuffer.Elements();
92
93 MOZ_ASSERT(mBuffer.Length() == outSamples);
94
95 WebAudioUtils::SpeexResamplerProcess(mUpSampler, aChannel, aInputData,
96 &inSamples, outputData, &outSamples);
97
98 MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE &&
99 outSamples == WEBAUDIO_BLOCK_SIZE * aBlocks);
100
101 return outputData;
102 }
103
DownSample(uint32_t aChannel,float * aOutputData,uint32_t aBlocks)104 void DownSample(uint32_t aChannel, float* aOutputData, uint32_t aBlocks) {
105 uint32_t inSamples = WEBAUDIO_BLOCK_SIZE * aBlocks;
106 uint32_t outSamples = WEBAUDIO_BLOCK_SIZE;
107 const float* inputData = mBuffer.Elements();
108
109 MOZ_ASSERT(mBuffer.Length() == inSamples);
110
111 WebAudioUtils::SpeexResamplerProcess(mDownSampler, aChannel, inputData,
112 &inSamples, aOutputData, &outSamples);
113
114 MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE * aBlocks &&
115 outSamples == WEBAUDIO_BLOCK_SIZE);
116 }
117
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const118 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
119 size_t amount = 0;
120 // Future: properly measure speex memory
121 amount += aMallocSizeOf(mUpSampler);
122 amount += aMallocSizeOf(mDownSampler);
123 amount += mBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
124 return amount;
125 }
126
127 private:
Destroy()128 void Destroy() {
129 if (mUpSampler) {
130 speex_resampler_destroy(mUpSampler);
131 mUpSampler = nullptr;
132 }
133 if (mDownSampler) {
134 speex_resampler_destroy(mDownSampler);
135 mDownSampler = nullptr;
136 }
137 }
138
139 private:
140 OverSampleType mType;
141 SpeexResamplerState* mUpSampler;
142 SpeexResamplerState* mDownSampler;
143 uint32_t mChannels;
144 TrackRate mSampleRate;
145 nsTArray<float> mBuffer;
146 };
147
148 class WaveShaperNodeEngine final : public AudioNodeEngine {
149 public:
WaveShaperNodeEngine(AudioNode * aNode)150 explicit WaveShaperNodeEngine(AudioNode* aNode)
151 : AudioNodeEngine(aNode), mType(OverSampleType::None) {}
152
153 enum Parameters { TYPE };
154
SetRawArrayData(nsTArray<float> && aCurve)155 void SetRawArrayData(nsTArray<float>&& aCurve) override {
156 mCurve = std::move(aCurve);
157 }
158
SetInt32Parameter(uint32_t aIndex,int32_t aValue)159 void SetInt32Parameter(uint32_t aIndex, int32_t aValue) override {
160 switch (aIndex) {
161 case TYPE:
162 mType = static_cast<OverSampleType>(aValue);
163 break;
164 default:
165 NS_ERROR("Bad WaveShaperNode Int32Parameter");
166 }
167 }
168
169 template <uint32_t blocks>
ProcessCurve(const float * aInputBuffer,float * aOutputBuffer)170 void ProcessCurve(const float* aInputBuffer, float* aOutputBuffer) {
171 for (uint32_t j = 0; j < WEBAUDIO_BLOCK_SIZE * blocks; ++j) {
172 // Index into the curve array based on the amplitude of the
173 // incoming signal by using an amplitude range of [-1, 1] and
174 // performing a linear interpolation of the neighbor values.
175 float index = (mCurve.Length() - 1) * (aInputBuffer[j] + 1.0f) / 2.0f;
176 if (index < 0.0f) {
177 aOutputBuffer[j] = mCurve[0];
178 } else {
179 int32_t indexLower = index;
180 if (static_cast<uint32_t>(indexLower) >= mCurve.Length() - 1) {
181 aOutputBuffer[j] = mCurve[mCurve.Length() - 1];
182 } else {
183 uint32_t indexHigher = indexLower + 1;
184 float interpolationFactor = index - indexLower;
185 aOutputBuffer[j] = (1.0f - interpolationFactor) * mCurve[indexLower] +
186 interpolationFactor * mCurve[indexHigher];
187 }
188 }
189 }
190 }
191
ProcessBlock(AudioNodeTrack * aTrack,GraphTime aFrom,const AudioBlock & aInput,AudioBlock * aOutput,bool * aFinished)192 void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
193 const AudioBlock& aInput, AudioBlock* aOutput,
194 bool* aFinished) override {
195 uint32_t channelCount = aInput.ChannelCount();
196 if (!mCurve.Length()) {
197 // Optimize the case where we don't have a curve buffer
198 *aOutput = aInput;
199 return;
200 }
201
202 // If the input is null, check to see if non-null output will be produced
203 bool nullInput = false;
204 if (channelCount == 0) {
205 float index = (mCurve.Length() - 1) * 0.5;
206 uint32_t indexLower = index;
207 uint32_t indexHigher = indexLower + 1;
208 float interpolationFactor = index - indexLower;
209 if ((1.0f - interpolationFactor) * mCurve[indexLower] +
210 interpolationFactor * mCurve[indexHigher] ==
211 0.0) {
212 *aOutput = aInput;
213 return;
214 }
215 nullInput = true;
216 channelCount = 1;
217 }
218
219 aOutput->AllocateChannels(channelCount);
220 for (uint32_t i = 0; i < channelCount; ++i) {
221 const float* inputSamples;
222 float scaledInput[WEBAUDIO_BLOCK_SIZE + 4];
223 float* alignedScaledInput = ALIGNED16(scaledInput);
224 ASSERT_ALIGNED16(alignedScaledInput);
225 if (!nullInput) {
226 if (aInput.mVolume != 1.0f) {
227 AudioBlockCopyChannelWithScale(
228 static_cast<const float*>(aInput.mChannelData[i]), aInput.mVolume,
229 alignedScaledInput);
230 inputSamples = alignedScaledInput;
231 } else {
232 inputSamples = static_cast<const float*>(aInput.mChannelData[i]);
233 }
234 } else {
235 PodZero(alignedScaledInput, WEBAUDIO_BLOCK_SIZE);
236 inputSamples = alignedScaledInput;
237 }
238 float* outputBuffer = aOutput->ChannelFloatsForWrite(i);
239 float* sampleBuffer;
240
241 switch (mType) {
242 case OverSampleType::None:
243 mResampler.Reset(channelCount, aTrack->mSampleRate,
244 OverSampleType::None);
245 ProcessCurve<1>(inputSamples, outputBuffer);
246 break;
247 case OverSampleType::_2x:
248 mResampler.Reset(channelCount, aTrack->mSampleRate,
249 OverSampleType::_2x);
250 sampleBuffer = mResampler.UpSample(i, inputSamples, 2);
251 ProcessCurve<2>(sampleBuffer, sampleBuffer);
252 mResampler.DownSample(i, outputBuffer, 2);
253 break;
254 case OverSampleType::_4x:
255 mResampler.Reset(channelCount, aTrack->mSampleRate,
256 OverSampleType::_4x);
257 sampleBuffer = mResampler.UpSample(i, inputSamples, 4);
258 ProcessCurve<4>(sampleBuffer, sampleBuffer);
259 mResampler.DownSample(i, outputBuffer, 4);
260 break;
261 default:
262 MOZ_ASSERT_UNREACHABLE("We should never reach here");
263 }
264 }
265 }
266
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const267 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
268 size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
269 amount += mCurve.ShallowSizeOfExcludingThis(aMallocSizeOf);
270 amount += mResampler.SizeOfExcludingThis(aMallocSizeOf);
271 return amount;
272 }
273
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const274 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
275 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
276 }
277
278 private:
279 nsTArray<float> mCurve;
280 OverSampleType mType;
281 Resampler mResampler;
282 };
283
WaveShaperNode(AudioContext * aContext)284 WaveShaperNode::WaveShaperNode(AudioContext* aContext)
285 : AudioNode(aContext, 2, ChannelCountMode::Max,
286 ChannelInterpretation::Speakers),
287 mType(OverSampleType::None) {
288 WaveShaperNodeEngine* engine = new WaveShaperNodeEngine(this);
289 mTrack = AudioNodeTrack::Create(
290 aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
291 }
292
293 /* static */
Create(AudioContext & aAudioContext,const WaveShaperOptions & aOptions,ErrorResult & aRv)294 already_AddRefed<WaveShaperNode> WaveShaperNode::Create(
295 AudioContext& aAudioContext, const WaveShaperOptions& aOptions,
296 ErrorResult& aRv) {
297 RefPtr<WaveShaperNode> audioNode = new WaveShaperNode(&aAudioContext);
298
299 audioNode->Initialize(aOptions, aRv);
300 if (NS_WARN_IF(aRv.Failed())) {
301 return nullptr;
302 }
303
304 if (aOptions.mCurve.WasPassed()) {
305 audioNode->SetCurveInternal(aOptions.mCurve.Value(), aRv);
306 if (NS_WARN_IF(aRv.Failed())) {
307 return nullptr;
308 }
309 }
310
311 audioNode->SetOversample(aOptions.mOversample);
312 return audioNode.forget();
313 }
314
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)315 JSObject* WaveShaperNode::WrapObject(JSContext* aCx,
316 JS::Handle<JSObject*> aGivenProto) {
317 return WaveShaperNode_Binding::Wrap(aCx, this, aGivenProto);
318 }
319
SetCurve(const Nullable<Float32Array> & aCurve,ErrorResult & aRv)320 void WaveShaperNode::SetCurve(const Nullable<Float32Array>& aCurve,
321 ErrorResult& aRv) {
322 // Let's purge the cached value for the curve attribute.
323 WaveShaperNode_Binding::ClearCachedCurveValue(this);
324
325 if (aCurve.IsNull()) {
326 CleanCurveInternal();
327 return;
328 }
329
330 const Float32Array& floats = aCurve.Value();
331 floats.ComputeState();
332
333 nsTArray<float> curve;
334 uint32_t argLength = floats.Length();
335 if (!curve.SetLength(argLength, fallible)) {
336 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
337 return;
338 }
339
340 PodCopy(curve.Elements(), floats.Data(), argLength);
341 SetCurveInternal(curve, aRv);
342 }
343
SetCurveInternal(const nsTArray<float> & aCurve,ErrorResult & aRv)344 void WaveShaperNode::SetCurveInternal(const nsTArray<float>& aCurve,
345 ErrorResult& aRv) {
346 if (aCurve.Length() < 2) {
347 aRv.ThrowInvalidStateError("Must have at least two entries");
348 return;
349 }
350
351 mCurve = aCurve.Clone();
352 SendCurveToTrack();
353 }
354
CleanCurveInternal()355 void WaveShaperNode::CleanCurveInternal() {
356 mCurve.Clear();
357 SendCurveToTrack();
358 }
359
SendCurveToTrack()360 void WaveShaperNode::SendCurveToTrack() {
361 AudioNodeTrack* ns = mTrack;
362 MOZ_ASSERT(ns, "Why don't we have a track here?");
363
364 nsTArray<float> copyCurve(mCurve.Clone());
365 ns->SetRawArrayData(std::move(copyCurve));
366 }
367
GetCurve(JSContext * aCx,JS::MutableHandle<JSObject * > aRetval)368 void WaveShaperNode::GetCurve(JSContext* aCx,
369 JS::MutableHandle<JSObject*> aRetval) {
370 // Let's return a null value if the list is empty.
371 if (mCurve.IsEmpty()) {
372 aRetval.set(nullptr);
373 return;
374 }
375
376 MOZ_ASSERT(mCurve.Length() >= 2);
377 aRetval.set(
378 Float32Array::Create(aCx, this, mCurve.Length(), mCurve.Elements()));
379 }
380
SetOversample(OverSampleType aType)381 void WaveShaperNode::SetOversample(OverSampleType aType) {
382 mType = aType;
383 SendInt32ParameterToTrack(WaveShaperNodeEngine::TYPE,
384 static_cast<int32_t>(aType));
385 }
386
387 } // namespace mozilla::dom
388