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