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