tor-browser

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

BiquadFilterNode.cpp (12448B)


      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 "BiquadFilterNode.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "AlignmentUtils.h"
     12 #include "AudioDestinationNode.h"
     13 #include "AudioNodeEngine.h"
     14 #include "AudioNodeTrack.h"
     15 #include "AudioParamTimeline.h"
     16 #include "PlayingRefChangeHandler.h"
     17 #include "Tracing.h"
     18 #include "WebAudioUtils.h"
     19 #include "blink/Biquad.h"
     20 #include "mozilla/ErrorResult.h"
     21 #include "mozilla/UniquePtr.h"
     22 #include "nsGlobalWindowInner.h"
     23 
     24 namespace mozilla::dom {
     25 
     26 NS_IMPL_CYCLE_COLLECTION_INHERITED(BiquadFilterNode, AudioNode, mFrequency,
     27                                   mDetune, mQ, mGain)
     28 
     29 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(BiquadFilterNode)
     30 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
     31 
     32 NS_IMPL_ADDREF_INHERITED(BiquadFilterNode, AudioNode)
     33 NS_IMPL_RELEASE_INHERITED(BiquadFilterNode, AudioNode)
     34 
     35 static void SetParamsOnBiquad(WebCore::Biquad& aBiquad, float aSampleRate,
     36                              BiquadFilterType aType, double aFrequency,
     37                              double aQ, double aGain, double aDetune) {
     38  const double nyquist = aSampleRate * 0.5;
     39  double normalizedFrequency = aFrequency / nyquist;
     40 
     41  if (aDetune) {
     42    normalizedFrequency *= fdlibm_exp2(aDetune / 1200);
     43  }
     44 
     45  switch (aType) {
     46    case BiquadFilterType::Lowpass:
     47      aBiquad.setLowpassParams(normalizedFrequency, aQ);
     48      break;
     49    case BiquadFilterType::Highpass:
     50      aBiquad.setHighpassParams(normalizedFrequency, aQ);
     51      break;
     52    case BiquadFilterType::Bandpass:
     53      aBiquad.setBandpassParams(normalizedFrequency, aQ);
     54      break;
     55    case BiquadFilterType::Lowshelf:
     56      aBiquad.setLowShelfParams(normalizedFrequency, aGain);
     57      break;
     58    case BiquadFilterType::Highshelf:
     59      aBiquad.setHighShelfParams(normalizedFrequency, aGain);
     60      break;
     61    case BiquadFilterType::Peaking:
     62      aBiquad.setPeakingParams(normalizedFrequency, aQ, aGain);
     63      break;
     64    case BiquadFilterType::Notch:
     65      aBiquad.setNotchParams(normalizedFrequency, aQ);
     66      break;
     67    case BiquadFilterType::Allpass:
     68      aBiquad.setAllpassParams(normalizedFrequency, aQ);
     69      break;
     70    default:
     71      MOZ_ASSERT_UNREACHABLE("We should never see the alternate names here");
     72      break;
     73  }
     74 }
     75 
     76 class BiquadFilterNodeEngine final : public AudioNodeEngine {
     77 public:
     78  BiquadFilterNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination,
     79                         uint64_t aWindowID)
     80      : AudioNodeEngine(aNode),
     81        mDestination(aDestination->Track())
     82        // Keep the default values in sync with the default values in
     83        // BiquadFilterNode::BiquadFilterNode
     84        ,
     85        mType(BiquadFilterType::Lowpass),
     86        mFrequency(350.f),
     87        mDetune(0.f),
     88        mQ(1.f),
     89        mGain(0.f),
     90        mWindowID(aWindowID) {}
     91 
     92  enum Parameters { TYPE, FREQUENCY, DETUNE, Q, GAIN };
     93  void SetInt32Parameter(uint32_t aIndex, int32_t aValue) override {
     94    switch (aIndex) {
     95      case TYPE:
     96        mType = static_cast<BiquadFilterType>(aValue);
     97        break;
     98      default:
     99        NS_ERROR("Bad BiquadFilterNode Int32Parameter");
    100    }
    101  }
    102  void RecvTimelineEvent(uint32_t aIndex, AudioParamEvent& aEvent) override {
    103    MOZ_ASSERT(mDestination);
    104 
    105    aEvent.ConvertToTicks(mDestination);
    106 
    107    switch (aIndex) {
    108      case FREQUENCY:
    109        mFrequency.InsertEvent<int64_t>(aEvent);
    110        break;
    111      case DETUNE:
    112        mDetune.InsertEvent<int64_t>(aEvent);
    113        break;
    114      case Q:
    115        mQ.InsertEvent<int64_t>(aEvent);
    116        break;
    117      case GAIN:
    118        mGain.InsertEvent<int64_t>(aEvent);
    119        break;
    120      default:
    121        NS_ERROR("Bad BiquadFilterNodeEngine TimelineParameter");
    122    }
    123  }
    124 
    125  void ProcessBlock(AudioNodeTrack* aTrack, GraphTime aFrom,
    126                    const AudioBlock& aInput, AudioBlock* aOutput,
    127                    bool* aFinished) override {
    128    TRACE("BiquadFilterNode::ProcessBlock");
    129    float inputBuffer[WEBAUDIO_BLOCK_SIZE + 4];
    130    float* alignedInputBuffer = ALIGNED16(inputBuffer);
    131    ASSERT_ALIGNED16(alignedInputBuffer);
    132 
    133    if (aInput.IsNull()) {
    134      bool hasTail = false;
    135      for (uint32_t i = 0; i < mBiquads.Length(); ++i) {
    136        if (mBiquads[i].hasTail()) {
    137          hasTail = true;
    138          break;
    139        }
    140      }
    141      if (!hasTail) {
    142        if (!mBiquads.IsEmpty()) {
    143          mBiquads.Clear();
    144          aTrack->ScheduleCheckForInactive();
    145 
    146          RefPtr<PlayingRefChangeHandler> refchanged =
    147              new PlayingRefChangeHandler(aTrack,
    148                                          PlayingRefChangeHandler::RELEASE);
    149          aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
    150        }
    151 
    152        aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
    153        return;
    154      }
    155 
    156      PodArrayZero(inputBuffer);
    157 
    158    } else if (mBiquads.Length() != aInput.ChannelCount()) {
    159      if (mBiquads.IsEmpty()) {
    160        RefPtr<PlayingRefChangeHandler> refchanged =
    161            new PlayingRefChangeHandler(aTrack,
    162                                        PlayingRefChangeHandler::ADDREF);
    163        aTrack->Graph()->DispatchToMainThreadStableState(refchanged.forget());
    164      } else {  // Help people diagnose bug 924718
    165        WebAudioUtils::LogToDeveloperConsole(
    166            mWindowID, "BiquadFilterChannelCountChangeWarning");
    167      }
    168 
    169      // Adjust the number of biquads based on the number of channels
    170      mBiquads.SetLength(aInput.ChannelCount());
    171    }
    172 
    173    uint32_t numberOfChannels = mBiquads.Length();
    174    aOutput->AllocateChannels(numberOfChannels);
    175 
    176    TrackTime pos = mDestination->GraphTimeToTrackTime(aFrom);
    177 
    178    double freq = mFrequency.GetValueAtTime(pos);
    179    double q = mQ.GetValueAtTime(pos);
    180    double gain = mGain.GetValueAtTime(pos);
    181    double detune = mDetune.GetValueAtTime(pos);
    182 
    183    for (uint32_t i = 0; i < numberOfChannels; ++i) {
    184      const float* input;
    185      if (aInput.IsNull()) {
    186        input = alignedInputBuffer;
    187      } else {
    188        input = static_cast<const float*>(aInput.mChannelData[i]);
    189        if (aInput.mVolume != 1.0) {
    190          AudioBlockCopyChannelWithScale(input, aInput.mVolume,
    191                                         alignedInputBuffer);
    192          input = alignedInputBuffer;
    193        }
    194      }
    195      SetParamsOnBiquad(mBiquads[i], aTrack->mSampleRate, mType, freq, q, gain,
    196                        detune);
    197 
    198      mBiquads[i].process(input, aOutput->ChannelFloatsForWrite(i),
    199                          aInput.GetDuration());
    200    }
    201  }
    202 
    203  bool IsActive() const override { return !mBiquads.IsEmpty(); }
    204 
    205  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override {
    206    // Not owned:
    207    // - mDestination - probably not owned
    208    // - AudioParamTimelines - counted in the AudioNode
    209    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
    210    amount += mBiquads.ShallowSizeOfExcludingThis(aMallocSizeOf);
    211    return amount;
    212  }
    213 
    214  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override {
    215    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
    216  }
    217 
    218 private:
    219  RefPtr<AudioNodeTrack> mDestination;
    220  BiquadFilterType mType;
    221  AudioParamTimeline mFrequency;
    222  AudioParamTimeline mDetune;
    223  AudioParamTimeline mQ;
    224  AudioParamTimeline mGain;
    225  nsTArray<WebCore::Biquad> mBiquads;
    226  uint64_t mWindowID;
    227 };
    228 
    229 BiquadFilterNode::BiquadFilterNode(AudioContext* aContext)
    230    : AudioNode(aContext, 2, ChannelCountMode::Max,
    231                ChannelInterpretation::Speakers),
    232      mType(BiquadFilterType::Lowpass) {
    233  mFrequency = CreateAudioParam(
    234      BiquadFilterNodeEngine::FREQUENCY, u"frequency"_ns, 350.f,
    235      -(aContext->SampleRate() / 2), aContext->SampleRate() / 2);
    236  mDetune = CreateAudioParam(BiquadFilterNodeEngine::DETUNE, u"detune"_ns, 0.f);
    237  mQ = CreateAudioParam(BiquadFilterNodeEngine::Q, u"Q"_ns, 1.f);
    238  mGain = CreateAudioParam(BiquadFilterNodeEngine::GAIN, u"gain"_ns, 0.f);
    239 
    240  uint64_t windowID = 0;
    241  if (nsGlobalWindowInner* win = aContext->GetOwnerWindow()) {
    242    windowID = win->WindowID();
    243  }
    244  BiquadFilterNodeEngine* engine =
    245      new BiquadFilterNodeEngine(this, aContext->Destination(), windowID);
    246  mTrack = AudioNodeTrack::Create(
    247      aContext, engine, AudioNodeTrack::NO_TRACK_FLAGS, aContext->Graph());
    248 }
    249 
    250 /* static */
    251 already_AddRefed<BiquadFilterNode> BiquadFilterNode::Create(
    252    AudioContext& aAudioContext, const BiquadFilterOptions& aOptions,
    253    ErrorResult& aRv) {
    254  RefPtr<BiquadFilterNode> audioNode = new BiquadFilterNode(&aAudioContext);
    255 
    256  audioNode->Initialize(aOptions, aRv);
    257  if (NS_WARN_IF(aRv.Failed())) {
    258    return nullptr;
    259  }
    260 
    261  audioNode->SetType(aOptions.mType);
    262  audioNode->Q()->SetInitialValue(aOptions.mQ);
    263  audioNode->Detune()->SetInitialValue(aOptions.mDetune);
    264  audioNode->Frequency()->SetInitialValue(aOptions.mFrequency);
    265  audioNode->Gain()->SetInitialValue(aOptions.mGain);
    266 
    267  return audioNode.forget();
    268 }
    269 
    270 size_t BiquadFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
    271  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
    272 
    273  if (mFrequency) {
    274    amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
    275  }
    276 
    277  if (mDetune) {
    278    amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
    279  }
    280 
    281  if (mQ) {
    282    amount += mQ->SizeOfIncludingThis(aMallocSizeOf);
    283  }
    284 
    285  if (mGain) {
    286    amount += mGain->SizeOfIncludingThis(aMallocSizeOf);
    287  }
    288 
    289  return amount;
    290 }
    291 
    292 size_t BiquadFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
    293  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
    294 }
    295 
    296 JSObject* BiquadFilterNode::WrapObject(JSContext* aCx,
    297                                       JS::Handle<JSObject*> aGivenProto) {
    298  return BiquadFilterNode_Binding::Wrap(aCx, this, aGivenProto);
    299 }
    300 
    301 void BiquadFilterNode::SetType(BiquadFilterType aType) {
    302  mType = aType;
    303  SendInt32ParameterToTrack(BiquadFilterNodeEngine::TYPE,
    304                            static_cast<int32_t>(aType));
    305 }
    306 
    307 void BiquadFilterNode::GetFrequencyResponse(const Float32Array& aFrequencyHz,
    308                                            const Float32Array& aMagResponse,
    309                                            const Float32Array& aPhaseResponse,
    310                                            ErrorResult& aRv) {
    311  UniquePtr<float[]> frequencies;
    312  size_t length;
    313  const double currentTime = Context()->CurrentTime();
    314  aFrequencyHz.ProcessData([&](const Span<float>& aFrequencyData,
    315                               JS::AutoCheckCannotGC&&) {
    316    aMagResponse.ProcessData([&](const Span<float>& aMagData,
    317                                 JS::AutoCheckCannotGC&&) {
    318      aPhaseResponse.ProcessData([&](const Span<float>& aPhaseData,
    319                                     JS::AutoCheckCannotGC&&) {
    320        length = aFrequencyData.Length();
    321        if (length != aMagData.Length() || length != aPhaseData.Length()) {
    322          aRv.ThrowInvalidAccessError("Parameter lengths must match");
    323          return;
    324        }
    325 
    326        if (length == 0) {
    327          return;
    328        }
    329 
    330        frequencies = MakeUniqueForOverwriteFallible<float[]>(length);
    331        if (!frequencies) {
    332          aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    333          return;
    334        }
    335 
    336        const double nyquist = Context()->SampleRate() * 0.5;
    337        std::transform(aFrequencyData.begin(), aFrequencyData.end(),
    338                       frequencies.get(), [&](float aFrequency) {
    339                         if (aFrequency >= 0 && aFrequency <= nyquist) {
    340                           return static_cast<float>(aFrequency / nyquist);
    341                         }
    342 
    343                         return std::numeric_limits<float>::quiet_NaN();
    344                       });
    345 
    346        double freq = mFrequency->GetValueAtTime(currentTime);
    347        double q = mQ->GetValueAtTime(currentTime);
    348        double gain = mGain->GetValueAtTime(currentTime);
    349        double detune = mDetune->GetValueAtTime(currentTime);
    350 
    351        WebCore::Biquad biquad;
    352        SetParamsOnBiquad(biquad, Context()->SampleRate(), mType, freq, q, gain,
    353                          detune);
    354        biquad.getFrequencyResponse(int(length), frequencies.get(),
    355                                    aMagData.Elements(), aPhaseData.Elements());
    356      });
    357    });
    358  });
    359 }
    360 
    361 }  // namespace mozilla::dom