tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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