dom/media/webaudio/WaveShaperNode.cpp
author Olli Pettay <Olli.Pettay@helsinki.fi>
Tue, 03 Jan 2017 21:47:55 +0200
changeset 375160 910c551b4d72866775aa6ff9a4dd024b266a06c9
parent 373312 8895dafe20fea0a2b339a8eeb556bcf4e3ac08ef
child 429603 71882fd33c91d06876715d6ac8b37847c8d14726
permissions -rw-r--r--
Bug 1326507, remove NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS, r=mccr8

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "WaveShaperNode.h"
#include "mozilla/dom/WaveShaperNodeBinding.h"
#include "AlignmentUtils.h"
#include "AudioNode.h"
#include "AudioNodeEngine.h"
#include "AudioNodeStream.h"
#include "mozilla/PodOperations.h"

namespace mozilla {
namespace dom {

NS_IMPL_CYCLE_COLLECTION_CLASS(WaveShaperNode)

NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WaveShaperNode, AudioNode)
  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END

NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WaveShaperNode, AudioNode)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END

NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WaveShaperNode)
  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END

NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WaveShaperNode)
NS_INTERFACE_MAP_END_INHERITING(AudioNode)

NS_IMPL_ADDREF_INHERITED(WaveShaperNode, AudioNode)
NS_IMPL_RELEASE_INHERITED(WaveShaperNode, AudioNode)

static uint32_t ValueOf(OverSampleType aType)
{
  switch (aType) {
  case OverSampleType::None: return 1;
  case OverSampleType::_2x:  return 2;
  case OverSampleType::_4x:  return 4;
  default:
    NS_NOTREACHED("We should never reach here");
    return 1;
  }
}

class Resampler final
{
public:
  Resampler()
    : mType(OverSampleType::None)
    , mUpSampler(nullptr)
    , mDownSampler(nullptr)
    , mChannels(0)
    , mSampleRate(0)
  {
  }

  ~Resampler()
  {
    Destroy();
  }

  void Reset(uint32_t aChannels, TrackRate aSampleRate, OverSampleType aType)
  {
    if (aChannels == mChannels &&
        aSampleRate == mSampleRate &&
        aType == mType) {
      return;
    }

    mChannels = aChannels;
    mSampleRate = aSampleRate;
    mType = aType;

    Destroy();

    if (aType == OverSampleType::None) {
      mBuffer.Clear();
      return;
    }

    mUpSampler = speex_resampler_init(aChannels,
                                      aSampleRate,
                                      aSampleRate * ValueOf(aType),
                                      SPEEX_RESAMPLER_QUALITY_MIN,
                                      nullptr);
    mDownSampler = speex_resampler_init(aChannels,
                                        aSampleRate * ValueOf(aType),
                                        aSampleRate,
                                        SPEEX_RESAMPLER_QUALITY_MIN,
                                        nullptr);
    mBuffer.SetLength(WEBAUDIO_BLOCK_SIZE*ValueOf(aType));
  }

  float* UpSample(uint32_t aChannel, const float* aInputData, uint32_t aBlocks)
  {
    uint32_t inSamples = WEBAUDIO_BLOCK_SIZE;
    uint32_t outSamples = WEBAUDIO_BLOCK_SIZE*aBlocks;
    float* outputData = mBuffer.Elements();

    MOZ_ASSERT(mBuffer.Length() == outSamples);

    WebAudioUtils::SpeexResamplerProcess(mUpSampler, aChannel,
                                         aInputData, &inSamples,
                                         outputData, &outSamples);

    MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE && outSamples == WEBAUDIO_BLOCK_SIZE*aBlocks);

    return outputData;
  }

  void DownSample(uint32_t aChannel, float* aOutputData, uint32_t aBlocks)
  {
    uint32_t inSamples = WEBAUDIO_BLOCK_SIZE*aBlocks;
    uint32_t outSamples = WEBAUDIO_BLOCK_SIZE;
    const float* inputData = mBuffer.Elements();

    MOZ_ASSERT(mBuffer.Length() == inSamples);

    WebAudioUtils::SpeexResamplerProcess(mDownSampler, aChannel,
                                         inputData, &inSamples,
                                         aOutputData, &outSamples);

    MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE*aBlocks && outSamples == WEBAUDIO_BLOCK_SIZE);
  }

  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
  {
    size_t amount = 0;
    // Future: properly measure speex memory
    amount += aMallocSizeOf(mUpSampler);
    amount += aMallocSizeOf(mDownSampler);
    amount += mBuffer.ShallowSizeOfExcludingThis(aMallocSizeOf);
    return amount;
  }

private:
  void Destroy()
  {
    if (mUpSampler) {
      speex_resampler_destroy(mUpSampler);
      mUpSampler = nullptr;
    }
    if (mDownSampler) {
      speex_resampler_destroy(mDownSampler);
      mDownSampler = nullptr;
    }
  }

private:
  OverSampleType mType;
  SpeexResamplerState* mUpSampler;
  SpeexResamplerState* mDownSampler;
  uint32_t mChannels;
  TrackRate mSampleRate;
  nsTArray<float> mBuffer;
};

class WaveShaperNodeEngine final : public AudioNodeEngine
{
public:
  explicit WaveShaperNodeEngine(AudioNode* aNode)
    : AudioNodeEngine(aNode)
    , mType(OverSampleType::None)
  {
  }

  enum Parameteres {
    TYPE
  };

  void SetRawArrayData(nsTArray<float>& aCurve) override
  {
    mCurve.SwapElements(aCurve);
  }

  void SetInt32Parameter(uint32_t aIndex, int32_t aValue) override
  {
    switch (aIndex) {
    case TYPE:
      mType = static_cast<OverSampleType>(aValue);
      break;
    default:
      NS_ERROR("Bad WaveShaperNode Int32Parameter");
    }
  }

  template <uint32_t blocks>
  void ProcessCurve(const float* aInputBuffer, float* aOutputBuffer)
  {
    for (uint32_t j = 0; j < WEBAUDIO_BLOCK_SIZE*blocks; ++j) {
      // Index into the curve array based on the amplitude of the
      // incoming signal by using an amplitude range of [-1, 1] and
      // performing a linear interpolation of the neighbor values.
      float index = (mCurve.Length() - 1) * (aInputBuffer[j] + 1.0f) / 2.0f;
      if (index < 0.0f) {
        aOutputBuffer[j] = mCurve[0];
      } else {
        int32_t indexLower = index;
        if (static_cast<uint32_t>(indexLower) >= mCurve.Length() - 1) {
          aOutputBuffer[j] = mCurve[mCurve.Length() - 1];
        } else {
          uint32_t indexHigher = indexLower + 1;
          float interpolationFactor = index - indexLower;
          aOutputBuffer[j] = (1.0f - interpolationFactor) * mCurve[indexLower] +
                                     interpolationFactor  * mCurve[indexHigher];
        }
      }
    }
  }

  void ProcessBlock(AudioNodeStream* aStream,
                    GraphTime aFrom,
                    const AudioBlock& aInput,
                    AudioBlock* aOutput,
                    bool* aFinished) override
  {
    uint32_t channelCount = aInput.ChannelCount();
    if (!mCurve.Length()) {
      // Optimize the case where we don't have a curve buffer
      *aOutput = aInput;
      return;
    }

    // If the input is null, check to see if non-null output will be produced
    bool nullInput = false;
    if (channelCount == 0) {
      float index = (mCurve.Length() - 1) * 0.5;
      uint32_t indexLower = index;
      uint32_t indexHigher = indexLower + 1;
      float interpolationFactor = index - indexLower;
      if ((1.0f - interpolationFactor) * mCurve[indexLower] +
          interpolationFactor * mCurve[indexHigher] == 0.0) {
        *aOutput = aInput;
        return;
      } else {
        nullInput = true;
        channelCount = 1;
      }
    }

    aOutput->AllocateChannels(channelCount);
    for (uint32_t i = 0; i < channelCount; ++i) {
      const float* inputSamples;
      float scaledInput[WEBAUDIO_BLOCK_SIZE + 4];
      float* alignedScaledInput = ALIGNED16(scaledInput);
      ASSERT_ALIGNED16(alignedScaledInput);
      if (!nullInput) {
        if (aInput.mVolume != 1.0f) {
          AudioBlockCopyChannelWithScale(
              static_cast<const float*>(aInput.mChannelData[i]),
                                        aInput.mVolume,
                                        alignedScaledInput);
          inputSamples = alignedScaledInput;
        } else {
          inputSamples = static_cast<const float*>(aInput.mChannelData[i]);
        }
      } else {
        PodZero(alignedScaledInput, WEBAUDIO_BLOCK_SIZE);
        inputSamples = alignedScaledInput;
      }
      float* outputBuffer = aOutput->ChannelFloatsForWrite(i);
      float* sampleBuffer;

      switch (mType) {
      case OverSampleType::None:
        mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::None);
        ProcessCurve<1>(inputSamples, outputBuffer);
        break;
      case OverSampleType::_2x:
        mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::_2x);
        sampleBuffer = mResampler.UpSample(i, inputSamples, 2);
        ProcessCurve<2>(sampleBuffer, sampleBuffer);
        mResampler.DownSample(i, outputBuffer, 2);
        break;
      case OverSampleType::_4x:
        mResampler.Reset(channelCount, aStream->SampleRate(), OverSampleType::_4x);
        sampleBuffer = mResampler.UpSample(i, inputSamples, 4);
        ProcessCurve<4>(sampleBuffer, sampleBuffer);
        mResampler.DownSample(i, outputBuffer, 4);
        break;
      default:
        NS_NOTREACHED("We should never reach here");
      }
    }
  }

  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
  {
    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
    amount += mCurve.ShallowSizeOfExcludingThis(aMallocSizeOf);
    amount += mResampler.SizeOfExcludingThis(aMallocSizeOf);
    return amount;
  }

  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override
  {
    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
  }

private:
  nsTArray<float> mCurve;
  OverSampleType mType;
  Resampler mResampler;
};

WaveShaperNode::WaveShaperNode(AudioContext* aContext)
  : AudioNode(aContext,
              2,
              ChannelCountMode::Max,
              ChannelInterpretation::Speakers)
  , mType(OverSampleType::None)
{
  WaveShaperNodeEngine* engine = new WaveShaperNodeEngine(this);
  mStream = AudioNodeStream::Create(aContext, engine,
                                    AudioNodeStream::NO_STREAM_FLAGS,
                                    aContext->Graph());
}

/* static */ already_AddRefed<WaveShaperNode>
WaveShaperNode::Create(AudioContext& aAudioContext,
                       const WaveShaperOptions& aOptions,
                       ErrorResult& aRv)
{
  if (aAudioContext.CheckClosed(aRv)) {
    return nullptr;
  }

  RefPtr<WaveShaperNode> audioNode = new WaveShaperNode(&aAudioContext);

  audioNode->Initialize(aOptions, aRv);
  if (NS_WARN_IF(aRv.Failed())) {
    return nullptr;
  }

  if (aOptions.mCurve.WasPassed()) {
    audioNode->SetCurveInternal(aOptions.mCurve.Value(), aRv);
    if (NS_WARN_IF(aRv.Failed())) {
      return nullptr;
    }
  }

  audioNode->SetOversample(aOptions.mOversample);
  return audioNode.forget();
}

JSObject*
WaveShaperNode::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
{
  return WaveShaperNodeBinding::Wrap(aCx, this, aGivenProto);
}

void
WaveShaperNode::SetCurve(const Nullable<Float32Array>& aCurve, ErrorResult& aRv)
{
  // Let's purge the cached value for the curve attribute.
  WaveShaperNodeBinding::ClearCachedCurveValue(this);

  if (aCurve.IsNull()) {
    CleanCurveInternal();
    return;
  }

  const Float32Array& floats = aCurve.Value();

  floats.ComputeLengthAndData();
  if (floats.IsShared()) {
    // Throw if the object is mapping shared memory (must opt in).
    aRv.ThrowTypeError<MSG_TYPEDARRAY_IS_SHARED>(NS_LITERAL_STRING("Argument of WaveShaperNode.setCurve"));
    return;
  }

  nsTArray<float> curve;
  uint32_t argLength = floats.Length();
  if (!curve.SetLength(argLength, fallible)) {
    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
    return;
  }

  PodCopy(curve.Elements(), floats.Data(), argLength);
  SetCurveInternal(curve, aRv);
}

void
WaveShaperNode::SetCurveInternal(const nsTArray<float>& aCurve,
                                 ErrorResult& aRv)
{
  if (aCurve.Length() < 2) {
    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
    return;
  }

  mCurve = aCurve;
  SendCurveToStream();
}

void
WaveShaperNode::CleanCurveInternal()
{
  mCurve.Clear();
  SendCurveToStream();
}

void
WaveShaperNode::SendCurveToStream()
{
  AudioNodeStream* ns = mStream;
  MOZ_ASSERT(ns, "Why don't we have a stream here?");

  nsTArray<float> copyCurve(mCurve);
  ns->SetRawArrayData(copyCurve);
}

void
WaveShaperNode::GetCurve(JSContext* aCx,
                         JS::MutableHandle<JSObject*> aRetval)
{
  // Let's return a null value if the list is empty.
  if (mCurve.IsEmpty()) {
    aRetval.set(nullptr);
    return;
  }

  MOZ_ASSERT(mCurve.Length() >= 2);
  aRetval.set(Float32Array::Create(aCx, this, mCurve.Length(),
                                   mCurve.Elements()));
}

void
WaveShaperNode::SetOversample(OverSampleType aType)
{
  mType = aType;
  SendInt32ParameterToStream(WaveShaperNodeEngine::TYPE,
                             static_cast<int32_t>(aType));
}

} // namespace dom
} // namespace mozilla