Bug 865251 - Implement WaveShaperNode; r=roc
authorEhsan Akhgari <ehsan@mozilla.com>
Tue, 14 May 2013 00:12:30 -0400
changeset 143328 813a1f7e66ef142412e418342e097d33febd1db6
parent 143327 3a56d9a0b092036c2153bb8d3e1178d269fdd14c
child 143329 fcae0b9251bae2efbd079561bfe9595e4ef61298
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs865251
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 865251 - Implement WaveShaperNode; r=roc
content/media/AudioNodeEngine.h
content/media/AudioNodeStream.cpp
content/media/AudioNodeStream.h
content/media/webaudio/AudioContext.cpp
content/media/webaudio/AudioContext.h
content/media/webaudio/Makefile.in
content/media/webaudio/WaveShaperNode.cpp
content/media/webaudio/WaveShaperNode.h
content/media/webaudio/moz.build
content/media/webaudio/test/Makefile.in
content/media/webaudio/test/test_waveShaper.html
content/media/webaudio/test/test_waveShaperNoCurve.html
content/media/webaudio/test/test_waveShaperZeroLengthCurve.html
dom/webidl/AudioContext.webidl
dom/webidl/WaveShaperNode.webidl
dom/webidl/WebIDL.mk
--- a/content/media/AudioNodeEngine.h
+++ b/content/media/AudioNodeEngine.h
@@ -183,16 +183,21 @@ public:
                                        const dom::ThreeDPoint& aValue)
   {
     NS_ERROR("Invalid SetThreeDPointParameter index");
   }
   virtual void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
   {
     NS_ERROR("SetBuffer called on engine that doesn't support it");
   }
+  // This consumes the contents of aData.  aData will be emptied after this returns.
+  virtual void SetRawArrayData(nsTArray<float>& aData)
+  {
+    NS_ERROR("SetRawArrayData called on an engine that doesn't support it");
+  }
 
   /**
    * Produce the next block of audio samples, given input samples aInput
    * (the mixed data for input 0).
    * aInput is guaranteed to have float sample format (if it has samples at all)
    * and to have been resampled to IdealAudioRate(), and to have exactly
    * WEBAUDIO_BLOCK_SIZE samples.
    * *aFinished is set to false by the caller. If the callee sets it to true,
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -155,16 +155,38 @@ AudioNodeStream::SetBuffer(already_AddRe
     nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
   };
 
   MOZ_ASSERT(this);
   GraphImpl()->AppendMessage(new Message(this, aBuffer));
 }
 
 void
+AudioNodeStream::SetRawArrayData(nsTArray<float>& aData)
+{
+  class Message : public ControlMessage {
+  public:
+    Message(AudioNodeStream* aStream,
+            nsTArray<float>& aData)
+      : ControlMessage(aStream)
+    {
+      mData.SwapElements(aData);
+    }
+    virtual void Run()
+    {
+      static_cast<AudioNodeStream*>(mStream)->Engine()->SetRawArrayData(mData);
+    }
+    nsTArray<float> mData;
+  };
+
+  MOZ_ASSERT(this);
+  GraphImpl()->AppendMessage(new Message(this, aData));
+}
+
+void
 AudioNodeStream::SetChannelMixingParameters(uint32_t aNumberOfChannels,
                                             ChannelCountMode aChannelCountMode,
                                             ChannelInterpretation aChannelInterpretation)
 {
   class Message : public ControlMessage {
   public:
     Message(AudioNodeStream* aStream,
             uint32_t aNumberOfChannels,
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -70,16 +70,18 @@ public:
    */
   void SetStreamTimeParameter(uint32_t aIndex, MediaStream* aRelativeToStream,
                               double aStreamTime);
   void SetDoubleParameter(uint32_t aIndex, double aValue);
   void SetInt32Parameter(uint32_t aIndex, int32_t aValue);
   void SetTimelineParameter(uint32_t aIndex, const dom::AudioParamTimeline& aValue);
   void SetThreeDPointParameter(uint32_t aIndex, const dom::ThreeDPoint& aValue);
   void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer);
+  // This consumes the contents of aData.  aData will be emptied after this returns.
+  void SetRawArrayData(nsTArray<float>& aData);
   void SetChannelMixingParameters(uint32_t aNumberOfChannels,
                                   dom::ChannelCountMode aChannelCountMoe,
                                   dom::ChannelInterpretation aChannelInterpretation);
   void SetAudioParamHelperStream()
   {
     MOZ_ASSERT(!mAudioParamStream, "Can only do this once");
     mAudioParamStream = true;
   }
--- a/content/media/webaudio/AudioContext.cpp
+++ b/content/media/webaudio/AudioContext.cpp
@@ -17,16 +17,17 @@
 #include "DelayNode.h"
 #include "PannerNode.h"
 #include "AudioListener.h"
 #include "DynamicsCompressorNode.h"
 #include "BiquadFilterNode.h"
 #include "ScriptProcessorNode.h"
 #include "ChannelMergerNode.h"
 #include "ChannelSplitterNode.h"
+#include "WaveShaperNode.h"
 #include "nsNetUtil.h"
 
 // Note that this number is an arbitrary large value to protect against OOM
 // attacks.
 const unsigned MAX_SCRIPT_PROCESSOR_CHANNELS = 10000;
 const unsigned MAX_CHANNEL_SPLITTER_OUTPUTS = UINT16_MAX;
 const unsigned MAX_CHANNEL_MERGER_INPUTS = UINT16_MAX;
 
@@ -190,16 +191,23 @@ AudioContext::CreateAnalyser()
 
 already_AddRefed<GainNode>
 AudioContext::CreateGain()
 {
   nsRefPtr<GainNode> gainNode = new GainNode(this);
   return gainNode.forget();
 }
 
+already_AddRefed<WaveShaperNode>
+AudioContext::CreateWaveShaper()
+{
+  nsRefPtr<WaveShaperNode> waveShaperNode = new WaveShaperNode(this);
+  return waveShaperNode.forget();
+}
+
 already_AddRefed<DelayNode>
 AudioContext::CreateDelay(double aMaxDelayTime, ErrorResult& aRv)
 {
   if (aMaxDelayTime > 0. && aMaxDelayTime < 180.) {
     nsRefPtr<DelayNode> delayNode = new DelayNode(this, aMaxDelayTime);
     return delayNode.forget();
   }
   aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
--- a/content/media/webaudio/AudioContext.h
+++ b/content/media/webaudio/AudioContext.h
@@ -47,16 +47,17 @@ class BiquadFilterNode;
 class ChannelMergerNode;
 class ChannelSplitterNode;
 class DelayNode;
 class DynamicsCompressorNode;
 class GainNode;
 class GlobalObject;
 class PannerNode;
 class ScriptProcessorNode;
+class WaveShaperNode;
 
 class AudioContext MOZ_FINAL : public nsDOMEventTargetHelper,
                                public EnableWebAudioCheck
 {
   explicit AudioContext(nsPIDOMWindow* aParentWindow);
   ~AudioContext();
 
 public:
@@ -121,16 +122,19 @@ public:
   }
 
   already_AddRefed<AnalyserNode>
   CreateAnalyser();
 
   already_AddRefed<GainNode>
   CreateGain();
 
+  already_AddRefed<WaveShaperNode>
+  CreateWaveShaper();
+
   already_AddRefed<GainNode>
   CreateGainNode()
   {
     return CreateGain();
   }
 
   already_AddRefed<DelayNode>
   CreateDelay(double aMaxDelayTime, ErrorResult& aRv);
--- a/content/media/webaudio/Makefile.in
+++ b/content/media/webaudio/Makefile.in
@@ -31,14 +31,15 @@ CPPSRCS := \
   DelayNode.cpp \
   DynamicsCompressorNode.cpp \
   EnableWebAudioCheck.cpp \
   GainNode.cpp \
   MediaBufferDecoder.cpp \
   PannerNode.cpp \
   ScriptProcessorNode.cpp \
   ThreeDPoint.cpp \
+  WaveShaperNode.cpp \
   WebAudioUtils.cpp \
   $(NULL)
 
 FORCE_STATIC_LIB := 1
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/WaveShaperNode.cpp
@@ -0,0 +1,140 @@
+/* -*- 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 "AudioNode.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeStream.h"
+#include "mozilla/PodOperations.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WaveShaperNode, AudioNode)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+  tmp->ClearCurve();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WaveShaperNode, AudioNode)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+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_JS_MEMBER_CALLBACK(mCurve)
+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)
+
+class WaveShaperNodeEngine : public AudioNodeEngine
+{
+public:
+  explicit WaveShaperNodeEngine(AudioNode* aNode)
+    : AudioNodeEngine(aNode)
+  {
+  }
+
+  virtual void SetRawArrayData(nsTArray<float>& aCurve) MOZ_OVERRIDE
+  {
+    mCurve.SwapElements(aCurve);
+  }
+
+  virtual void ProduceAudioBlock(AudioNodeStream* aStream,
+                                 const AudioChunk& aInput,
+                                 AudioChunk* aOutput,
+                                 bool* aFinished)
+  {
+    uint32_t channelCount = aInput.mChannelData.Length();
+    if (!mCurve.Length() || !channelCount) {
+      // Optimize the case where we don't have a curve buffer,
+      // or the input is null.
+      *aOutput = aInput;
+      return;
+    }
+
+    AllocateAudioBlock(channelCount, aOutput);
+    for (uint32_t i = 0; i < channelCount; ++i) {
+      const float* inputBuffer = static_cast<const float*>(aInput.mChannelData[i]);
+      float* outputBuffer = const_cast<float*> (static_cast<const float*>(aOutput->mChannelData[i]));
+      for (uint32_t j = 0; j < WEBAUDIO_BLOCK_SIZE; ++j) {
+        // Index into the curve array based on the amplitude of the
+        // incoming signal by clamping the amplitude to [-1, 1] and
+        // performing a linear interpolation of the neighbor values.
+        float index = std::max(0.0f, std::min(float(mCurve.Length() - 1),
+                                              mCurve.Length() * (inputBuffer[j] + 1) / 2));
+        uint32_t indexLower = uint32_t(index);
+        uint32_t indexHigher = uint32_t(index + 1.0f);
+        if (indexHigher == mCurve.Length()) {
+          outputBuffer[j] = mCurve[indexLower];
+        } else {
+          float interpolationFactor = index - indexLower;
+          outputBuffer[j] = (1.0f - interpolationFactor) * mCurve[indexLower] +
+                                    interpolationFactor  * mCurve[indexHigher];
+        }
+      }
+    }
+  }
+
+private:
+  nsTArray<float> mCurve;
+};
+
+WaveShaperNode::WaveShaperNode(AudioContext* aContext)
+  : AudioNode(aContext,
+              2,
+              ChannelCountMode::Max,
+              ChannelInterpretation::Speakers)
+  , mCurve(nullptr)
+{
+  NS_HOLD_JS_OBJECTS(this, WaveShaperNode);
+
+  WaveShaperNodeEngine* engine = new WaveShaperNodeEngine(this);
+  mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
+}
+
+WaveShaperNode::~WaveShaperNode()
+{
+  ClearCurve();
+}
+
+void
+WaveShaperNode::ClearCurve()
+{
+  mCurve = nullptr;
+  NS_DROP_JS_OBJECTS(this, WaveShaperNode);
+}
+
+JSObject*
+WaveShaperNode::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aScope)
+{
+  return WaveShaperNodeBinding::Wrap(aCx, aScope, this);
+}
+
+void
+WaveShaperNode::SetCurve(const Float32Array* aCurve)
+{
+  nsTArray<float> curve;
+  if (aCurve) {
+    mCurve = aCurve->Obj();
+
+    curve.SetLength(aCurve->Length());
+    PodCopy(curve.Elements(), aCurve->Data(), aCurve->Length());
+  } else {
+    mCurve = nullptr;
+  }
+
+  AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
+  MOZ_ASSERT(ns, "Why don't we have a stream here?");
+  ns->SetRawArrayData(curve);
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/WaveShaperNode.h
@@ -0,0 +1,46 @@
+/* -*- 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/. */
+
+#ifndef WaveShaperNode_h_
+#define WaveShaperNode_h_
+
+#include "AudioNode.h"
+#include "AudioParam.h"
+
+namespace mozilla {
+namespace dom {
+
+class AudioContext;
+
+class WaveShaperNode : public AudioNode
+{
+public:
+  explicit WaveShaperNode(AudioContext *aContext);
+  virtual ~WaveShaperNode();
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WaveShaperNode, AudioNode)
+
+  virtual JSObject* WrapObject(JSContext *aCx,
+                               JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  JSObject* GetCurve(JSContext* aCx) const
+  {
+    return mCurve;
+  }
+  void SetCurve(const Float32Array* aData);
+
+private:
+  void ClearCurve();
+
+private:
+  JSObject* mCurve;
+};
+
+}
+}
+
+#endif
--- a/content/media/webaudio/moz.build
+++ b/content/media/webaudio/moz.build
@@ -31,10 +31,11 @@ EXPORTS.mozilla.dom += [
     'ChannelMergerNode.h',
     'ChannelSplitterNode.h',
     'DelayNode.h',
     'DynamicsCompressorNode.h',
     'EnableWebAudioCheck.h',
     'GainNode.h',
     'PannerNode.h',
     'ScriptProcessorNode.h',
+    'WaveShaperNode.h',
 ]
 
--- a/content/media/webaudio/test/Makefile.in
+++ b/content/media/webaudio/test/Makefile.in
@@ -56,16 +56,19 @@ MOCHITEST_FILES := \
   test_gainNodeInLoop.html \
   test_mediaDecoding.html \
   test_mixingRules.html \
   test_nodeToParamConnection.html \
   test_pannerNode.html \
   test_scriptProcessorNode.html \
   test_scriptProcessorNodeChannelCount.html \
   test_singleSourceDest.html \
+  test_waveShaper.html \
+  test_waveShaperNoCurve.html \
+  test_waveShaperZeroLengthCurve.html \
   ting.ogg \
   ting-expected.wav \
   ting-dualchannel44.1.ogg \
   ting-dualchannel44.1-expected.wav \
   ting-dualchannel48.ogg \
   ting-dualchannel48-expected.wav \
   ting-mono-expected.wav \
   ting-mono-dualchannel44.1-expected.wav \
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_waveShaper.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test WaveShaperNode with no curve</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="webaudio.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+  length: 4096,
+  numberOfChannels: 1,
+  createGraph: function(context) {
+    var source = context.createBufferSource();
+    source.buffer = this.buffer;
+
+    var shaper = context.createWaveShaper();
+    shaper.curve = this.curve;
+
+    source.connect(shaper);
+
+    source.start(0);
+    return shaper;
+  },
+  createExpectedBuffers: function(context) {
+    this.buffer = context.createBuffer(1, 4096, context.sampleRate);
+    for (var i = 1; i < 4095; ++i) {
+      this.buffer.getChannelData(0)[i] = 2 * (i / 4096) - 1;
+    }
+    // Two out of range values
+    this.buffer.getChannelData(0)[0] = -2;
+    this.buffer.getChannelData(0)[4095] = 2;
+
+    this.curve = new Float32Array(2048);
+    for (var i = 0; i < 2048; ++i) {
+      this.curve[i] = Math.sin(100 * Math.PI * (i + 1) / context.sampleRate);
+    }
+
+    var expectedBuffer = context.createBuffer(1, 4096, context.sampleRate);
+    for (var i = 1; i < 4095; ++i) {
+      var input = this.buffer.getChannelData(0)[i];
+      var index = Math.floor(this.curve.length * (input + 1) / 2);
+      index = Math.max(0, Math.min(this.curve.length - 1, index));
+      expectedBuffer.getChannelData(0)[i] = this.curve[index];
+    }
+    expectedBuffer.getChannelData(0)[0] = this.curve[0];
+    expectedBuffer.getChannelData(0)[4095] = this.curve[2047];
+    return expectedBuffer;
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_waveShaperNoCurve.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test WaveShaperNode with no curve</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="webaudio.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+  length: 2048,
+  numberOfChannels: 1,
+  createGraph: function(context) {
+    var source = context.createBufferSource();
+    source.buffer = this.buffer;
+
+    var shaper = context.createWaveShaper();
+    is(shaper.curve, null, "The shaper curve must be null by default");
+
+    source.connect(shaper);
+
+    source.start(0);
+    return shaper;
+  },
+  createExpectedBuffers: function(context) {
+    var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+    }
+    this.buffer = expectedBuffer;
+    return expectedBuffer;
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_waveShaperZeroLengthCurve.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test WaveShaperNode with no curve</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="webaudio.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+  length: 2048,
+  numberOfChannels: 1,
+  createGraph: function(context) {
+    var source = context.createBufferSource();
+    source.buffer = this.buffer;
+
+    var shaper = context.createWaveShaper();
+    shaper.curve = new Float32Array(0);
+
+    source.connect(shaper);
+
+    source.start(0);
+    return shaper;
+  },
+  createExpectedBuffers: function(context) {
+    var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+    }
+    this.buffer = expectedBuffer;
+    return expectedBuffer;
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/AudioContext.webidl
+++ b/dom/webidl/AudioContext.webidl
@@ -44,16 +44,18 @@ interface AudioContext : EventTarget {
     AnalyserNode createAnalyser();
     [Creator]
     GainNode createGain();
     [Creator, Throws]
     DelayNode createDelay(optional double maxDelayTime = 1);
     [Creator]
     BiquadFilterNode createBiquadFilter();
     [Creator]
+    WaveShaperNode createWaveShaper();
+    [Creator]
     PannerNode createPanner();
 
     [Creator, Throws]
     ChannelSplitterNode createChannelSplitter(optional unsigned long numberOfOutputs = 6);
     [Creator, Throws]
     ChannelMergerNode createChannelMerger(optional unsigned long numberOfInputs = 6);
 
     [Creator]
new file mode 100644
--- /dev/null
+++ b/dom/webidl/WaveShaperNode.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/.
+ *
+ * The origin of this IDL file is
+ * https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
+ *
+ * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * liability, trademark and document use rules apply.
+ */
+
+[PrefControlled]
+interface WaveShaperNode : AudioNode {
+
+      attribute Float32Array? curve;
+
+};
+
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -310,16 +310,17 @@ webidl_files = \
   ValidityState.webidl \
   WebComponents.webidl \
   WebSocket.webidl \
   WheelEvent.webidl \
   UndoManager.webidl \
   URLUtils.webidl \
   USSDReceivedEvent.webidl \
   VideoStreamTrack.webidl \
+  WaveShaperNode.webidl \
   Window.webidl \
   XMLDocument.webidl \
   XMLHttpRequest.webidl \
   XMLHttpRequestEventTarget.webidl \
   XMLHttpRequestUpload.webidl \
   XMLSerializer.webidl \
   XMLStylesheetProcessingInstruction.webidl \
   XPathEvaluator.webidl \