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 "OscillatorNode.h"
8 #include "AudioNodeEngine.h"
9 #include "AudioNodeTrack.h"
10 #include "AudioDestinationNode.h"
11 #include "nsContentUtils.h"
12 #include "WebAudioUtils.h"
13 #include "blink/PeriodicWave.h"
14
15 namespace mozilla::dom {
16
17 NS_IMPL_CYCLE_COLLECTION_INHERITED(OscillatorNode, AudioScheduledSourceNode,
18 mPeriodicWave, mFrequency, mDetune)
19
20 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(OscillatorNode)
21 NS_INTERFACE_MAP_END_INHERITING(AudioScheduledSourceNode)
22
23 NS_IMPL_ADDREF_INHERITED(OscillatorNode, AudioScheduledSourceNode)
24 NS_IMPL_RELEASE_INHERITED(OscillatorNode, AudioScheduledSourceNode)
25
26 class OscillatorNodeEngine final : public AudioNodeEngine {
27 public:
OscillatorNodeEngine(AudioNode * aNode,AudioDestinationNode * aDestination)28 OscillatorNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
29 : AudioNodeEngine(aNode),
30 mSource(nullptr),
31 mDestination(aDestination->Track()),
32 mStart(-1),
33 mStop(TRACK_TIME_MAX)
34 // Keep the default values in sync with OscillatorNode::OscillatorNode.
35 ,
36 mFrequency(440.f),
37 mDetune(0.f),
38 mType(OscillatorType::Sine),
39 mPhase(0.),
40 mFinalFrequency(0.),
41 mPhaseIncrement(0.),
42 mRecomputeParameters(true),
43 mCustomDisableNormalization(false) {
44 MOZ_ASSERT(NS_IsMainThread());
45 mBasicWaveFormCache = aDestination->Context()->GetBasicWaveFormCache();
46 }
47
SetSourceTrack(AudioNodeTrack * aSource)48 void SetSourceTrack(AudioNodeTrack* aSource) { mSource = aSource; }
49
50 enum Parameters {
51 FREQUENCY,
52 DETUNE,
53 TYPE,
54 DISABLE_NORMALIZATION,
55 START,
56 STOP,
57 };
RecvTimelineEvent(uint32_t aIndex,AudioTimelineEvent & aEvent)58 void RecvTimelineEvent(uint32_t aIndex, AudioTimelineEvent& aEvent) override {
59 mRecomputeParameters = true;
60
61 MOZ_ASSERT(mDestination);
62
63 WebAudioUtils::ConvertAudioTimelineEventToTicks(aEvent, mDestination);
64
65 switch (aIndex) {
66 case FREQUENCY:
67 mFrequency.InsertEvent<int64_t>(aEvent);
68 break;
69 case DETUNE:
70 mDetune.InsertEvent<int64_t>(aEvent);
71 break;
72 default:
73 NS_ERROR("Bad OscillatorNodeEngine TimelineParameter");
74 }
75 }
76
SetTrackTimeParameter(uint32_t aIndex,TrackTime aParam)77 void SetTrackTimeParameter(uint32_t aIndex, TrackTime aParam) override {
78 switch (aIndex) {
79 case START:
80 mStart = aParam;
81 mSource->SetActive();
82 break;
83 case STOP:
84 mStop = aParam;
85 break;
86 default:
87 NS_ERROR("Bad OscillatorNodeEngine TrackTimeParameter");
88 }
89 }
90
SetInt32Parameter(uint32_t aIndex,int32_t aParam)91 void SetInt32Parameter(uint32_t aIndex, int32_t aParam) override {
92 switch (aIndex) {
93 case TYPE:
94 // Set the new type.
95 mType = static_cast<OscillatorType>(aParam);
96 if (mType == OscillatorType::Sine) {
97 // Forget any previous custom data.
98 mCustomDisableNormalization = false;
99 mPeriodicWave = nullptr;
100 mRecomputeParameters = true;
101 }
102 switch (mType) {
103 case OscillatorType::Sine:
104 mPhase = 0.0;
105 break;
106 case OscillatorType::Square:
107 case OscillatorType::Triangle:
108 case OscillatorType::Sawtooth:
109 mPeriodicWave = mBasicWaveFormCache->GetBasicWaveForm(mType);
110 break;
111 case OscillatorType::Custom:
112 break;
113 default:
114 NS_ERROR("Bad OscillatorNodeEngine type parameter.");
115 }
116 // End type switch.
117 break;
118 case DISABLE_NORMALIZATION:
119 MOZ_ASSERT(aParam >= 0, "negative custom array length");
120 mCustomDisableNormalization = static_cast<uint32_t>(aParam);
121 break;
122 default:
123 NS_ERROR("Bad OscillatorNodeEngine Int32Parameter.");
124 }
125 // End index switch.
126 }
127
SetBuffer(AudioChunk && aBuffer)128 void SetBuffer(AudioChunk&& aBuffer) override {
129 MOZ_ASSERT(aBuffer.ChannelCount() == 2,
130 "PeriodicWave should have sent two channels");
131 MOZ_ASSERT(aBuffer.mVolume == 1.0f);
132 mPeriodicWave = WebCore::PeriodicWave::create(
133 mSource->mSampleRate, aBuffer.ChannelData<float>()[0],
134 aBuffer.ChannelData<float>()[1], aBuffer.mDuration,
135 mCustomDisableNormalization);
136 }
137
IncrementPhase()138 void IncrementPhase() {
139 const float twoPiFloat = float(2 * M_PI);
140 mPhase += mPhaseIncrement;
141 if (mPhase > twoPiFloat) {
142 mPhase -= twoPiFloat;
143 } else if (mPhase < -twoPiFloat) {
144 mPhase += twoPiFloat;
145 }
146 }
147
148 // Returns true if the final frequency (and thus the phase increment) changed,
149 // false otherwise. This allow some optimizations at callsite.
UpdateParametersIfNeeded(TrackTime ticks,size_t count)150 bool UpdateParametersIfNeeded(TrackTime ticks, size_t count) {
151 double frequency, detune;
152
153 // Shortcut if frequency-related AudioParam are not automated, and we
154 // already have computed the frequency information and related parameters.
155 if (!ParametersMayNeedUpdate()) {
156 return false;
157 }
158
159 bool simpleFrequency = mFrequency.HasSimpleValue();
160 bool simpleDetune = mDetune.HasSimpleValue();
161
162 if (simpleFrequency) {
163 frequency = mFrequency.GetValue();
164 } else {
165 frequency = mFrequency.GetValueAtTime(ticks, count);
166 }
167 if (simpleDetune) {
168 detune = mDetune.GetValue();
169 } else {
170 detune = mDetune.GetValueAtTime(ticks, count);
171 }
172
173 float finalFrequency = frequency * exp2(detune / 1200.);
174 float signalPeriod = mSource->mSampleRate / finalFrequency;
175 mRecomputeParameters = false;
176
177 mPhaseIncrement = 2 * M_PI / signalPeriod;
178
179 if (finalFrequency != mFinalFrequency) {
180 mFinalFrequency = finalFrequency;
181 return true;
182 }
183 return false;
184 }
185
FillBounds(float * output,TrackTime ticks,uint32_t & start,uint32_t & end)186 void FillBounds(float* output, TrackTime ticks, uint32_t& start,
187 uint32_t& end) {
188 MOZ_ASSERT(output);
189 static_assert(TrackTime(WEBAUDIO_BLOCK_SIZE) < UINT_MAX,
190 "WEBAUDIO_BLOCK_SIZE overflows interator bounds.");
191 start = 0;
192 if (ticks < mStart) {
193 start = mStart - ticks;
194 for (uint32_t i = 0; i < start; ++i) {
195 output[i] = 0.0;
196 }
197 }
198 end = WEBAUDIO_BLOCK_SIZE;
199 if (ticks + end > mStop) {
200 end = mStop - ticks;
201 for (uint32_t i = end; i < WEBAUDIO_BLOCK_SIZE; ++i) {
202 output[i] = 0.0;
203 }
204 }
205 }
206
ComputeSine(float * aOutput,TrackTime ticks,uint32_t aStart,uint32_t aEnd)207 void ComputeSine(float* aOutput, TrackTime ticks, uint32_t aStart,
208 uint32_t aEnd) {
209 for (uint32_t i = aStart; i < aEnd; ++i) {
210 // We ignore the return value, changing the frequency has no impact on
211 // performances here.
212 UpdateParametersIfNeeded(ticks, i);
213
214 aOutput[i] = sin(mPhase);
215
216 IncrementPhase();
217 }
218 }
219
ParametersMayNeedUpdate()220 bool ParametersMayNeedUpdate() {
221 return !mDetune.HasSimpleValue() || !mFrequency.HasSimpleValue() ||
222 mRecomputeParameters;
223 }
224
ComputeCustom(float * aOutput,TrackTime ticks,uint32_t aStart,uint32_t aEnd)225 void ComputeCustom(float* aOutput, TrackTime ticks, uint32_t aStart,
226 uint32_t aEnd) {
227 MOZ_ASSERT(mPeriodicWave, "No custom waveform data");
228
229 uint32_t periodicWaveSize = mPeriodicWave->periodicWaveSize();
230 // Mask to wrap wave data indices into the range [0,periodicWaveSize).
231 uint32_t indexMask = periodicWaveSize - 1;
232 MOZ_ASSERT(periodicWaveSize && (periodicWaveSize & indexMask) == 0,
233 "periodicWaveSize must be power of 2");
234 float* higherWaveData = nullptr;
235 float* lowerWaveData = nullptr;
236 float tableInterpolationFactor;
237 // Phase increment at frequency of 1 Hz.
238 // mPhase runs [0,periodicWaveSize) here instead of [0,2*M_PI).
239 float basePhaseIncrement = mPeriodicWave->rateScale();
240
241 bool needToFetchWaveData = UpdateParametersIfNeeded(ticks, aStart);
242
243 bool parametersMayNeedUpdate = ParametersMayNeedUpdate();
244 mPeriodicWave->waveDataForFundamentalFrequency(
245 mFinalFrequency, lowerWaveData, higherWaveData,
246 tableInterpolationFactor);
247
248 for (uint32_t i = aStart; i < aEnd; ++i) {
249 if (parametersMayNeedUpdate) {
250 if (needToFetchWaveData) {
251 mPeriodicWave->waveDataForFundamentalFrequency(
252 mFinalFrequency, lowerWaveData, higherWaveData,
253 tableInterpolationFactor);
254 }
255 needToFetchWaveData = UpdateParametersIfNeeded(ticks, i);
256 }
257 // Bilinear interpolation between adjacent samples in each table.
258 float floorPhase = floorf(mPhase);
259 int j1Signed = static_cast<int>(floorPhase);
260 uint32_t j1 = j1Signed & indexMask;
261 uint32_t j2 = j1 + 1;
262 j2 &= indexMask;
263
264 float sampleInterpolationFactor = mPhase - floorPhase;
265
266 float lower = (1.0f - sampleInterpolationFactor) * lowerWaveData[j1] +
267 sampleInterpolationFactor * lowerWaveData[j2];
268 float higher = (1.0f - sampleInterpolationFactor) * higherWaveData[j1] +
269 sampleInterpolationFactor * higherWaveData[j2];
270 aOutput[i] = (1.0f - tableInterpolationFactor) * lower +
271 tableInterpolationFactor * higher;
272
273 // Calculate next phase position from wrapped value j1 to avoid loss of
274 // precision at large values.
275 mPhase =
276 j1 + sampleInterpolationFactor + basePhaseIncrement * mFinalFrequency;
277 }
278 }
279
ComputeSilence(AudioBlock * aOutput)280 void ComputeSilence(AudioBlock* aOutput) {
281 aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
282 }
283
ProcessBlock(AudioNodeTrack * aTrack,GraphTime aFrom,const AudioBlock & aInput,AudioBlock * aOutput,bool * aFinished)284 void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
285 const AudioBlock& aInput, AudioBlock* aOutput,
286 bool* aFinished) override {
287 MOZ_ASSERT(mSource == aTrack, "Invalid source track");
288
289 TrackTime ticks = mDestination->GraphTimeToTrackTime(aFrom);
290 if (mStart == -1) {
291 ComputeSilence(aOutput);
292 return;
293 }
294
295 if (ticks + WEBAUDIO_BLOCK_SIZE <= mStart || ticks >= mStop) {
296 ComputeSilence(aOutput);
297
298 } else {
299 aOutput->AllocateChannels(1);
300 float* output = aOutput->ChannelFloatsForWrite(0);
301
302 uint32_t start, end;
303 FillBounds(output, ticks, start, end);
304
305 // Synthesize the correct waveform.
306 switch (mType) {
307 case OscillatorType::Sine:
308 ComputeSine(output, ticks, start, end);
309 break;
310 case OscillatorType::Square:
311 case OscillatorType::Triangle:
312 case OscillatorType::Sawtooth:
313 case OscillatorType::Custom:
314 ComputeCustom(output, ticks, start, end);
315 break;
316 default:
317 ComputeSilence(aOutput);
318 };
319 }
320
321 if (ticks + WEBAUDIO_BLOCK_SIZE >= mStop) {
322 // We've finished playing.
323 *aFinished = true;
324 }
325 }
326
IsActive() const327 bool IsActive() const override {
328 // start() has been called.
329 return mStart != -1;
330 }
331
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const332 size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
333 size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
334
335 // Not owned:
336 // - mSource
337 // - mDestination
338 // - mFrequency (internal ref owned by node)
339 // - mDetune (internal ref owned by node)
340
341 if (mPeriodicWave) {
342 amount += mPeriodicWave->sizeOfIncludingThis(aMallocSizeOf);
343 }
344
345 return amount;
346 }
347
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const348 size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
349 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
350 }
351
352 // mSource deletes this engine in its destructor
353 AudioNodeTrack* MOZ_NON_OWNING_REF mSource;
354 RefPtr<AudioNodeTrack> mDestination;
355 TrackTime mStart;
356 TrackTime mStop;
357 AudioParamTimeline mFrequency;
358 AudioParamTimeline mDetune;
359 OscillatorType mType;
360 float mPhase;
361 float mFinalFrequency;
362 float mPhaseIncrement;
363 bool mRecomputeParameters;
364 RefPtr<BasicWaveFormCache> mBasicWaveFormCache;
365 bool mCustomDisableNormalization;
366 RefPtr<WebCore::PeriodicWave> mPeriodicWave;
367 };
368
OscillatorNode(AudioContext * aContext)369 OscillatorNode::OscillatorNode(AudioContext* aContext)
370 : AudioScheduledSourceNode(aContext, 2, ChannelCountMode::Max,
371 ChannelInterpretation::Speakers),
372 mType(OscillatorType::Sine),
373 mStartCalled(false) {
374 mFrequency = CreateAudioParam(
375 OscillatorNodeEngine::FREQUENCY, u"frequency"_ns, 440.0f,
376 -(aContext->SampleRate() / 2), aContext->SampleRate() / 2);
377 mDetune = CreateAudioParam(OscillatorNodeEngine::DETUNE, u"detune"_ns, 0.0f);
378 OscillatorNodeEngine* engine =
379 new OscillatorNodeEngine(this, aContext->Destination());
380 mTrack = AudioNodeTrack::Create(aContext, engine,
381 AudioNodeTrack::NEED_MAIN_THREAD_ENDED,
382 aContext->Graph());
383 engine->SetSourceTrack(mTrack);
384 mTrack->AddMainThreadListener(this);
385 }
386
387 /* static */
Create(AudioContext & aAudioContext,const OscillatorOptions & aOptions,ErrorResult & aRv)388 already_AddRefed<OscillatorNode> OscillatorNode::Create(
389 AudioContext& aAudioContext, const OscillatorOptions& aOptions,
390 ErrorResult& aRv) {
391 RefPtr<OscillatorNode> audioNode = new OscillatorNode(&aAudioContext);
392
393 audioNode->Initialize(aOptions, aRv);
394 if (NS_WARN_IF(aRv.Failed())) {
395 return nullptr;
396 }
397
398 audioNode->Frequency()->SetInitialValue(aOptions.mFrequency);
399 audioNode->Detune()->SetInitialValue(aOptions.mDetune);
400
401 if (aOptions.mPeriodicWave.WasPassed()) {
402 audioNode->SetPeriodicWave(aOptions.mPeriodicWave.Value());
403 } else {
404 audioNode->SetType(aOptions.mType, aRv);
405 if (NS_WARN_IF(aRv.Failed())) {
406 return nullptr;
407 }
408 }
409
410 return audioNode.forget();
411 }
412
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const413 size_t OscillatorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
414 size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
415
416 // For now only report if we know for sure that it's not shared.
417 if (mPeriodicWave) {
418 amount += mPeriodicWave->SizeOfIncludingThisIfNotShared(aMallocSizeOf);
419 }
420 amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
421 amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
422 return amount;
423 }
424
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const425 size_t OscillatorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
426 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
427 }
428
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)429 JSObject* OscillatorNode::WrapObject(JSContext* aCx,
430 JS::Handle<JSObject*> aGivenProto) {
431 return OscillatorNode_Binding::Wrap(aCx, this, aGivenProto);
432 }
433
DestroyMediaTrack()434 void OscillatorNode::DestroyMediaTrack() {
435 if (mTrack) {
436 mTrack->RemoveMainThreadListener(this);
437 }
438 AudioNode::DestroyMediaTrack();
439 }
440
SendTypeToTrack()441 void OscillatorNode::SendTypeToTrack() {
442 if (!mTrack) {
443 return;
444 }
445 if (mType == OscillatorType::Custom) {
446 // The engine assumes we'll send the custom data before updating the type.
447 SendPeriodicWaveToTrack();
448 }
449 SendInt32ParameterToTrack(OscillatorNodeEngine::TYPE,
450 static_cast<int32_t>(mType));
451 }
452
SendPeriodicWaveToTrack()453 void OscillatorNode::SendPeriodicWaveToTrack() {
454 NS_ASSERTION(mType == OscillatorType::Custom,
455 "Sending custom waveform to engine thread with non-custom type");
456 MOZ_ASSERT(mTrack, "Missing node track.");
457 MOZ_ASSERT(mPeriodicWave, "Send called without PeriodicWave object.");
458 SendInt32ParameterToTrack(OscillatorNodeEngine::DISABLE_NORMALIZATION,
459 mPeriodicWave->DisableNormalization());
460 AudioChunk data = mPeriodicWave->GetThreadSharedBuffer();
461 mTrack->SetBuffer(std::move(data));
462 }
463
Start(double aWhen,ErrorResult & aRv)464 void OscillatorNode::Start(double aWhen, ErrorResult& aRv) {
465 if (!WebAudioUtils::IsTimeValid(aWhen)) {
466 aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("start time");
467 return;
468 }
469
470 if (mStartCalled) {
471 aRv.ThrowInvalidStateError("Can't call start() more than once");
472 return;
473 }
474 mStartCalled = true;
475
476 if (!mTrack) {
477 // Nothing to play, or we're already dead for some reason
478 return;
479 }
480
481 // TODO: Perhaps we need to do more here.
482 mTrack->SetTrackTimeParameter(OscillatorNodeEngine::START, Context(), aWhen);
483
484 MarkActive();
485 Context()->StartBlockedAudioContextIfAllowed();
486 }
487
Stop(double aWhen,ErrorResult & aRv)488 void OscillatorNode::Stop(double aWhen, ErrorResult& aRv) {
489 if (!WebAudioUtils::IsTimeValid(aWhen)) {
490 aRv.ThrowRangeError<MSG_VALUE_OUT_OF_RANGE>("stop time");
491 return;
492 }
493
494 if (!mStartCalled) {
495 aRv.ThrowInvalidStateError("Can't call stop() without calling start()");
496 return;
497 }
498
499 if (!mTrack || !Context()) {
500 // We've already stopped and had our track shut down
501 return;
502 }
503
504 // TODO: Perhaps we need to do more here.
505 mTrack->SetTrackTimeParameter(OscillatorNodeEngine::STOP, Context(),
506 std::max(0.0, aWhen));
507 }
508
NotifyMainThreadTrackEnded()509 void OscillatorNode::NotifyMainThreadTrackEnded() {
510 MOZ_ASSERT(mTrack->IsEnded());
511
512 class EndedEventDispatcher final : public Runnable {
513 public:
514 explicit EndedEventDispatcher(OscillatorNode* aNode)
515 : mozilla::Runnable("EndedEventDispatcher"), mNode(aNode) {}
516 NS_IMETHOD Run() override {
517 // If it's not safe to run scripts right now, schedule this to run later
518 if (!nsContentUtils::IsSafeToRunScript()) {
519 nsContentUtils::AddScriptRunner(this);
520 return NS_OK;
521 }
522
523 mNode->DispatchTrustedEvent(u"ended"_ns);
524 // Release track resources.
525 mNode->DestroyMediaTrack();
526 return NS_OK;
527 }
528
529 private:
530 RefPtr<OscillatorNode> mNode;
531 };
532
533 Context()->Dispatch(do_AddRef(new EndedEventDispatcher(this)));
534
535 // Drop the playing reference
536 // Warning: The below line might delete this.
537 MarkInactive();
538 }
539
540 } // namespace mozilla::dom
541