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