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 "AudioNodeStream.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->Stream()),
33 mStart(-1),
34 mStop(STREAM_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
SetSourceStream(AudioNodeStream * aSource)49 void SetSourceStream(AudioNodeStream* 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
SetStreamTimeParameter(uint32_t aIndex,StreamTime aParam)78 void SetStreamTimeParameter(uint32_t aIndex, StreamTime 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 StreamTimeParameter");
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->SampleRate(), 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(StreamTime ticks,size_t count)151 bool UpdateParametersIfNeeded(StreamTime 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 * pow(2., detune / 1200.);
175 float signalPeriod = mSource->SampleRate() / 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,StreamTime ticks,uint32_t & start,uint32_t & end)187 void FillBounds(float* output, StreamTime ticks, uint32_t& start,
188 uint32_t& end) {
189 MOZ_ASSERT(output);
190 static_assert(StreamTime(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,StreamTime ticks,uint32_t aStart,uint32_t aEnd)208 void ComputeSine(float* aOutput, StreamTime 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,StreamTime ticks,uint32_t aStart,uint32_t aEnd)226 void ComputeCustom(float* aOutput, StreamTime 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(AudioNodeStream * aStream,GraphTime aFrom,const AudioBlock & aInput,AudioBlock * aOutput,bool * aFinished)285 void ProcessBlock(AudioNodeStream* aStream, GraphTime aFrom,
286 const AudioBlock& aInput, AudioBlock* aOutput,
287 bool* aFinished) override {
288 MOZ_ASSERT(mSource == aStream, "Invalid source stream");
289
290 StreamTime ticks = mDestination->GraphTimeToStreamTime(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 AudioNodeStream* MOZ_NON_OWNING_REF mSource;
355 RefPtr<AudioNodeStream> mDestination;
356 StreamTime mStart;
357 StreamTime 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 mFrequency(new AudioParam(
375 this, OscillatorNodeEngine::FREQUENCY, "frequency", 440.0f,
376 -(aContext->SampleRate() / 2), aContext->SampleRate() / 2)),
377 mDetune(
378 new AudioParam(this, OscillatorNodeEngine::DETUNE, "detune", 0.0f)),
379 mStartCalled(false) {
380 OscillatorNodeEngine* engine =
381 new OscillatorNodeEngine(this, aContext->Destination());
382 mStream = AudioNodeStream::Create(aContext, engine,
383 AudioNodeStream::NEED_MAIN_THREAD_FINISHED,
384 aContext->Graph());
385 engine->SetSourceStream(mStream);
386 mStream->AddMainThreadListener(this);
387 }
388
Create(AudioContext & aAudioContext,const OscillatorOptions & aOptions,ErrorResult & aRv)389 /* static */ already_AddRefed<OscillatorNode> OscillatorNode::Create(
390 AudioContext& aAudioContext, const OscillatorOptions& aOptions,
391 ErrorResult& aRv) {
392 if (aAudioContext.CheckClosed(aRv)) {
393 return nullptr;
394 }
395
396 RefPtr<OscillatorNode> audioNode = new OscillatorNode(&aAudioContext);
397
398 audioNode->Initialize(aOptions, aRv);
399 if (NS_WARN_IF(aRv.Failed())) {
400 return nullptr;
401 }
402
403 audioNode->SetType(aOptions.mType, aRv);
404 if (NS_WARN_IF(aRv.Failed())) {
405 return nullptr;
406 }
407
408 audioNode->Frequency()->SetValue(aOptions.mFrequency);
409 audioNode->Detune()->SetValue(aOptions.mDetune);
410
411 if (aOptions.mPeriodicWave.WasPassed()) {
412 audioNode->SetPeriodicWave(aOptions.mPeriodicWave.Value());
413 }
414
415 return audioNode.forget();
416 }
417
SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const418 size_t OscillatorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
419 size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
420
421 // For now only report if we know for sure that it's not shared.
422 if (mPeriodicWave) {
423 amount += mPeriodicWave->SizeOfIncludingThisIfNotShared(aMallocSizeOf);
424 }
425 amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
426 amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
427 return amount;
428 }
429
SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const430 size_t OscillatorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
431 return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
432 }
433
WrapObject(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)434 JSObject* OscillatorNode::WrapObject(JSContext* aCx,
435 JS::Handle<JSObject*> aGivenProto) {
436 return OscillatorNodeBinding::Wrap(aCx, this, aGivenProto);
437 }
438
DestroyMediaStream()439 void OscillatorNode::DestroyMediaStream() {
440 if (mStream) {
441 mStream->RemoveMainThreadListener(this);
442 }
443 AudioNode::DestroyMediaStream();
444 }
445
SendTypeToStream()446 void OscillatorNode::SendTypeToStream() {
447 if (!mStream) {
448 return;
449 }
450 if (mType == OscillatorType::Custom) {
451 // The engine assumes we'll send the custom data before updating the type.
452 SendPeriodicWaveToStream();
453 }
454 SendInt32ParameterToStream(OscillatorNodeEngine::TYPE,
455 static_cast<int32_t>(mType));
456 }
457
SendPeriodicWaveToStream()458 void OscillatorNode::SendPeriodicWaveToStream() {
459 NS_ASSERTION(mType == OscillatorType::Custom,
460 "Sending custom waveform to engine thread with non-custom type");
461 MOZ_ASSERT(mStream, "Missing node stream.");
462 MOZ_ASSERT(mPeriodicWave, "Send called without PeriodicWave object.");
463 SendInt32ParameterToStream(OscillatorNodeEngine::DISABLE_NORMALIZATION,
464 mPeriodicWave->DisableNormalization());
465 AudioChunk data = mPeriodicWave->GetThreadSharedBuffer();
466 mStream->SetBuffer(Move(data));
467 }
468
Start(double aWhen,ErrorResult & aRv)469 void OscillatorNode::Start(double aWhen, ErrorResult& aRv) {
470 if (!WebAudioUtils::IsTimeValid(aWhen)) {
471 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
472 return;
473 }
474
475 if (mStartCalled) {
476 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
477 return;
478 }
479 mStartCalled = true;
480
481 if (!mStream) {
482 // Nothing to play, or we're already dead for some reason
483 return;
484 }
485
486 // TODO: Perhaps we need to do more here.
487 mStream->SetStreamTimeParameter(OscillatorNodeEngine::START, Context(),
488 aWhen);
489
490 MarkActive();
491 }
492
Stop(double aWhen,ErrorResult & aRv)493 void OscillatorNode::Stop(double aWhen, ErrorResult& aRv) {
494 if (!WebAudioUtils::IsTimeValid(aWhen)) {
495 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
496 return;
497 }
498
499 if (!mStartCalled) {
500 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
501 return;
502 }
503
504 if (!mStream || !Context()) {
505 // We've already stopped and had our stream shut down
506 return;
507 }
508
509 // TODO: Perhaps we need to do more here.
510 mStream->SetStreamTimeParameter(OscillatorNodeEngine::STOP, Context(),
511 std::max(0.0, aWhen));
512 }
513
NotifyMainThreadStreamFinished()514 void OscillatorNode::NotifyMainThreadStreamFinished() {
515 MOZ_ASSERT(mStream->IsFinished());
516
517 class EndedEventDispatcher final : public Runnable {
518 public:
519 explicit EndedEventDispatcher(OscillatorNode* aNode)
520 : mozilla::Runnable("EndedEventDispatcher"), mNode(aNode) {}
521 NS_IMETHOD Run() override {
522 // If it's not safe to run scripts right now, schedule this to run later
523 if (!nsContentUtils::IsSafeToRunScript()) {
524 nsContentUtils::AddScriptRunner(this);
525 return NS_OK;
526 }
527
528 mNode->DispatchTrustedEvent(NS_LITERAL_STRING("ended"));
529 // Release stream resources.
530 mNode->DestroyMediaStream();
531 return NS_OK;
532 }
533
534 private:
535 RefPtr<OscillatorNode> mNode;
536 };
537
538 Context()->Dispatch(do_AddRef(new EndedEventDispatcher(this)));
539
540 // Drop the playing reference
541 // Warning: The below line might delete this.
542 MarkInactive();
543 }
544
545 } // namespace dom
546 } // namespace mozilla
547