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