tor-browser

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

IIRFilterNode.cpp (9576B)


      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 "IIRFilterNode.h"
      8 
      9 #include "AlignmentUtils.h"
     10 #include "AudioDestinationNode.h"
     11 #include "AudioNodeEngine.h"
     12 #include "PlayingRefChangeHandler.h"
     13 #include "Tracing.h"
     14 #include "blink/IIRFilter.h"
     15 #include "nsGlobalWindowInner.h"
     16 #include "nsPrintfCString.h"
     17 
     18 namespace mozilla::dom {
     19 
     20 class IIRFilterNodeEngine final : public AudioNodeEngine {
     21 public:
     22  IIRFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
     23                      const AudioDoubleArray& aFeedforward,
     24                      const AudioDoubleArray& aFeedback, uint64_t aWindowID)
     25      : AudioNodeEngine(aNode),
     26        mDestination(aDestination->Track()),
     27        mFeedforward(aFeedforward.Clone()),
     28        mFeedback(aFeedback.Clone()),
     29        mWindowID(aWindowID) {}
     30 
     31  void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
     32                    const AudioBlock& aInput, AudioBlock* aOutput,
     33                    bool* aFinished) override {
     34    TRACE("IIRFilterNodeEngine::ProcessBlock");
     35    float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4];
     36    float* alignedInputBuffer = ALIGNED16(inputBuffer);
     37    ASSERT_ALIGNED16(alignedInputBuffer);
     38 
     39    if (aInput.IsNull()) {
     40      if (!mIIRFilters.IsEmpty()) {
     41        bool allZero = true;
     42        for (uint32_t i = 0; i < mIIRFilters.Length(); ++i) {
     43          allZero &= mIIRFilters[i]->buffersAreZero();
     44        }
     45 
     46        // all filter buffer values are zero, so the output will be zero
     47        // as well.
     48        if (allZero) {
     49          mIIRFilters.Clear();
     50          aTrack->ScheduleCheckForInactive();
     51 
     52          RefPtr<PlayingRefChangeHandler> refchanged =
     53              new PlayingRefChangeHandler(aTrack,
     54                                          PlayingRefChangeHandler::RELEASE);
     55          aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
     56 
     57          aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
     58          return;
     59        }
     60 
     61        PodZero(alignedInputBuffer, WEBAUDIO_BLOCK_SIZE);
     62      }
     63    } else if (mIIRFilters.Length() != aInput.ChannelCount()) {
     64      if (mIIRFilters.IsEmpty()) {
     65        RefPtr<PlayingRefChangeHandler> refchanged =
     66            new PlayingRefChangeHandler(aTrack,
     67                                        PlayingRefChangeHandler::ADDREF);
     68        aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
     69      } else {
     70        WebAudioUtils::LogToDeveloperConsole(
     71            mWindowID, "IIRFilterChannelCountChangeWarning");
     72      }
     73 
     74      // Adjust the number of filters based on the number of channels
     75      mIIRFilters.SetLength(aInput.ChannelCount());
     76      for (size_t i = 0; i < aInput.ChannelCount(); ++i) {
     77        mIIRFilters[i] =
     78            MakeUnique<blink::IIRFilter>(&mFeedforward, &mFeedback);
     79      }
     80    }
     81 
     82    uint32_t numberOfChannels = mIIRFilters.Length();
     83    aOutput->AllocateChannels(numberOfChannels);
     84 
     85    for (uint32_t i = 0; i < numberOfChannels; ++i) {
     86      const float* input;
     87      if (aInput.IsNull()) {
     88        input = alignedInputBuffer;
     89      } else {
     90        input = static_cast<const float*>(aInput.mChannelData[i]);
     91        if (aInput.mVolume != 1.0) {
     92          AudioBlockCopyChannelWithScale(input, aInput.mVolume,
     93                                         alignedInputBuffer);
     94          input = alignedInputBuffer;
     95        }
     96      }
     97 
     98      mIIRFilters[i]->process(input, aOutput->ChannelFloatsForWrite(i),
     99                              aInput.GetDuration());
    100    }
    101  }
    102 
    103  bool IsActive() const override { return !mIIRFilters.IsEmpty(); }
    104 
    105  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
    106    // Not owned:
    107    // - mDestination - probably not owned
    108    // - AudioParamTimelines - counted in the AudioNode
    109    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
    110    amount += mIIRFilters.ShallowSizeOfExcludingThis(aMallocSizeOf);
    111    return amount;
    112  }
    113 
    114  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
    115    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
    116  }
    117 
    118 private:
    119  RefPtr<AudioNodeTrack> mDestination;
    120  nsTArray<UniquePtr<blink::IIRFilter>> mIIRFilters;
    121  AudioDoubleArray mFeedforward;
    122  AudioDoubleArray mFeedback;
    123  uint64_t mWindowID;
    124 };
    125 
    126 IIRFilterNode::IIRFilterNode(AudioContext* aContext,
    127                             const Sequence<double>& aFeedforward,
    128                             const Sequence<double>& aFeedback)
    129    : AudioNode(aContext, 2, ChannelCountMode::Max,
    130                ChannelInterpretation::Speakers) {
    131  mFeedforward.SetLength(aFeedforward.Length());
    132  PodCopy(mFeedforward.Elements(), aFeedforward.Elements(),
    133          aFeedforward.Length());
    134  mFeedback.SetLength(aFeedback.Length());
    135  PodCopy(mFeedback.Elements(), aFeedback.Elements(), aFeedback.Length());
    136 
    137  // Scale coefficients -- we guarantee that mFeedback != 0 when creating
    138  // the IIRFilterNode.
    139  double scale = mFeedback[0];
    140  double* elements = mFeedforward.Elements();
    141  for (size_t i = 0; i < mFeedforward.Length(); ++i) {
    142    elements[i] /= scale;
    143  }
    144 
    145  elements = mFeedback.Elements();
    146  for (size_t i = 0; i < mFeedback.Length(); ++i) {
    147    elements[i] /= scale;
    148  }
    149 
    150  // We check that this is exactly equal to one later in blink/IIRFilter.cpp
    151  elements[0] = 1.0;
    152 
    153  uint64_t windowID = 0;
    154  if (nsGlobalWindowInner* win = aContext->GetOwnerWindow()) {
    155    windowID = win->WindowID();
    156  }
    157  IIRFilterNodeEngine* engine = new IIRFilterNodeEngine(
    158      this, aContext->Destination(), mFeedforward, mFeedback, windowID);
    159  mTrack = AudioNodeTrack::Create(
    160      aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
    161 }
    162 
    163 /* static */
    164 already_AddRefed<IIRFilterNode> IIRFilterNode::Create(
    165    AudioContext& aAudioContext, const IIRFilterOptions& aOptions,
    166    ErrorResult& aRv) {
    167  if (aOptions.mFeedforward.Length() == 0 ||
    168      aOptions.mFeedforward.Length() > 20) {
    169    aRv.ThrowNotSupportedError(
    170        nsPrintfCString("\"feedforward\" length %zu is not in the range [1,20]",
    171                        aOptions.mFeedforward.Length()));
    172    return nullptr;
    173  }
    174 
    175  if (aOptions.mFeedback.Length() == 0 || aOptions.mFeedback.Length() > 20) {
    176    aRv.ThrowNotSupportedError(
    177        nsPrintfCString("\"feedback\" length %zu is not in the range [1,20]",
    178                        aOptions.mFeedback.Length()));
    179    return nullptr;
    180  }
    181 
    182  bool feedforwardAllZeros = true;
    183  for (size_t i = 0; i < aOptions.mFeedforward.Length(); ++i) {
    184    if (aOptions.mFeedforward.Elements()[i] != 0.0) {
    185      feedforwardAllZeros = false;
    186      break;
    187    }
    188  }
    189 
    190  if (feedforwardAllZeros) {
    191    aRv.ThrowInvalidStateError(
    192        "\"feedforward\" must contain some nonzero values");
    193    return nullptr;
    194  }
    195 
    196  if (aOptions.mFeedback[0] == 0.0) {
    197    aRv.ThrowInvalidStateError("First value in \"feedback\" must be nonzero");
    198    return nullptr;
    199  }
    200 
    201  RefPtr<IIRFilterNode> audioNode = new IIRFilterNode(
    202      &aAudioContext, aOptions.mFeedforward, aOptions.mFeedback);
    203 
    204  audioNode->Initialize(aOptions, aRv);
    205  if (NS_WARN_IF(aRv.Failed())) {
    206    return nullptr;
    207  }
    208 
    209  return audioNode.forget();
    210 }
    211 
    212 size_t IIRFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
    213  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
    214  return amount;
    215 }
    216 
    217 size_t IIRFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
    218  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
    219 }
    220 
    221 JSObject* IIRFilterNode::WrapObject(JSContext* aCx,
    222                                    JS::Handle<JSObject*> aGivenProto) {
    223  return IIRFilterNode_Binding::Wrap(aCx, this, aGivenProto);
    224 }
    225 
    226 void IIRFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz,
    227                                         const Float32Array& aMagResponse,
    228                                         const Float32Array& aPhaseResponse) {
    229  aFrequencyHz.ProcessData([&](const Span<float>& aFrequencyData,
    230                               JS::AutoCheckCannotGC&&) {
    231    aMagResponse.ProcessData([&](const Span<float>& aMagData,
    232                                 JS::AutoCheckCannotGC&&) {
    233      aPhaseResponse.ProcessData([&](const Span<float>& aPhaseData,
    234                                     JS::AutoCheckCannotGC&&) {
    235        uint32_t length = std::min(
    236            {aFrequencyData.Length(), aMagData.Length(), aPhaseData.Length()});
    237        if (!length) {
    238          return;
    239        }
    240 
    241        auto frequencies = MakeUniqueForOverwriteFallible<float[]>(length);
    242        if (!frequencies) {
    243          return;
    244        }
    245 
    246        const double nyquist = Context()->SampleRate() * 0.5;
    247 
    248        // Normalize the frequencies
    249        std::transform(aFrequencyData.begin(), aFrequencyData.begin() + length,
    250                       frequencies.get(), [&](float aFrequency) {
    251                         if (aFrequency >= 0 && aFrequency <= nyquist) {
    252                           return static_cast<float>(aFrequency / nyquist);
    253                         }
    254 
    255                         return std::numeric_limits<float>::quiet_NaN();
    256                       });
    257 
    258        blink::IIRFilter filter(&mFeedforward, &mFeedback);
    259        filter.getFrequencyResponse(int(length), frequencies.get(),
    260                                    aMagData.Elements(), aPhaseData.Elements());
    261      });
    262    });
    263  });
    264 }
    265 
    266 }  // namespace mozilla::dom