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