Bug 1007778 - Add a devtools API to make some Web Audio nodes pass through their inputs; r=roc,smaug
authorEhsan Akhgari <ehsan@mozilla.com>
Mon, 18 Aug 2014 20:12:50 -0400
changeset 200216 2ad221f9aca912d68192bafc55ad5a99a0fbbde2
parent 200215 e2dd99b59ec139bbd572407dc34fc185541521a7
child 200217 db50d91e58950a3f57f0f6c57001cf43c3d639a6
push id47840
push usereakhgari@mozilla.com
push dateTue, 19 Aug 2014 00:12:59 +0000
treeherdermozilla-inbound@2ad221f9aca9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc, smaug
bugs1007778
milestone34.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 1007778 - Add a devtools API to make some Web Audio nodes pass through their inputs; r=roc,smaug
content/media/AudioNodeStream.cpp
content/media/AudioNodeStream.h
content/media/webaudio/AudioNode.cpp
content/media/webaudio/AudioNode.h
content/media/webaudio/test/mochitest.ini
content/media/webaudio/test/test_analyserNodeOutput.html
content/media/webaudio/test/test_analyserNodePassThrough.html
content/media/webaudio/test/test_biquadFilterNodePassThrough.html
content/media/webaudio/test/test_convolverNodePassThrough.html
content/media/webaudio/test/test_delayNodePassThrough.html
content/media/webaudio/test/test_dynamicsCompressorNodePassThrough.html
content/media/webaudio/test/test_gainNodePassThrough.html
content/media/webaudio/test/test_pannerNodePassThrough.html
content/media/webaudio/test/test_waveShaperPassThrough.html
dom/webidl/AnalyserNode.webidl
dom/webidl/AudioNode.webidl
dom/webidl/BiquadFilterNode.webidl
dom/webidl/ConvolverNode.webidl
dom/webidl/DelayNode.webidl
dom/webidl/DynamicsCompressorNode.webidl
dom/webidl/GainNode.webidl
dom/webidl/PannerNode.webidl
dom/webidl/ScriptProcessorNode.webidl
dom/webidl/WaveShaperNode.webidl
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -257,16 +257,34 @@ AudioNodeStream::SetChannelMixingParamet
 
   MOZ_ASSERT(this);
   GraphImpl()->AppendMessage(new Message(this, aNumberOfChannels,
                                          aChannelCountMode,
                                          aChannelInterpretation));
 }
 
 void
+AudioNodeStream::SetPassThrough(bool aPassThrough)
+{
+  class Message : public ControlMessage {
+  public:
+    Message(AudioNodeStream* aStream, bool aPassThrough)
+      : ControlMessage(aStream), mPassThrough(aPassThrough) {}
+    virtual void Run()
+    {
+      static_cast<AudioNodeStream*>(mStream)->mPassThrough = mPassThrough;
+    }
+    bool mPassThrough;
+  };
+
+  MOZ_ASSERT(this);
+  GraphImpl()->AppendMessage(new Message(this, aPassThrough));
+}
+
+void
 AudioNodeStream::SetChannelMixingParametersImpl(uint32_t aNumberOfChannels,
                                                 ChannelCountMode aChannelCountMode,
                                                 ChannelInterpretation aChannelInterpretation)
 {
   // Make sure that we're not clobbering any significant bits by fitting these
   // values in 16 bits.
   MOZ_ASSERT(int(aChannelCountMode) < INT16_MAX);
   MOZ_ASSERT(int(aChannelInterpretation) < INT16_MAX);
@@ -448,20 +466,25 @@ AudioNodeStream::ProcessInput(GraphTime 
     // We need to generate at least one input
     uint16_t maxInputs = std::max(uint16_t(1), mEngine->InputCount());
     OutputChunks inputChunks;
     inputChunks.SetLength(maxInputs);
     for (uint16_t i = 0; i < maxInputs; ++i) {
       ObtainInputBlock(inputChunks[i], i);
     }
     bool finished = false;
-    if (maxInputs <= 1 && mEngine->OutputCount() <= 1) {
-      mEngine->ProcessBlock(this, inputChunks[0], &mLastChunks[0], &finished);
+    if (mPassThrough) {
+      MOZ_ASSERT(outputCount == 1, "For now, we only support nodes that have one output port");
+      mLastChunks[0] = inputChunks[0];
     } else {
-      mEngine->ProcessBlocksOnPorts(this, inputChunks, mLastChunks, &finished);
+      if (maxInputs <= 1 && mEngine->OutputCount() <= 1) {
+        mEngine->ProcessBlock(this, inputChunks[0], &mLastChunks[0], &finished);
+      } else {
+        mEngine->ProcessBlocksOnPorts(this, inputChunks, mLastChunks, &finished);
+      }
     }
     for (uint16_t i = 0; i < outputCount; ++i) {
       NS_ASSERTION(mLastChunks[i].GetDuration() == WEBAUDIO_BLOCK_SIZE,
                    "Invalid WebAudio chunk size");
     }
     if (finished) {
       mMarkAsFinishedAfterThisBlock = true;
     }
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -49,17 +49,18 @@ public:
                   MediaStreamGraph::AudioNodeStreamKind aKind,
                   TrackRate aSampleRate)
     : ProcessedMediaStream(nullptr),
       mEngine(aEngine),
       mSampleRate(aSampleRate),
       mKind(aKind),
       mNumberOfInputChannels(2),
       mMarkAsFinishedAfterThisBlock(false),
-      mAudioParamStream(false)
+      mAudioParamStream(false),
+      mPassThrough(false)
   {
     MOZ_ASSERT(NS_IsMainThread());
     mChannelCountMode = ChannelCountMode::Max;
     mChannelInterpretation = ChannelInterpretation::Speakers;
     // AudioNodes are always producing data
     mHasCurrentData = true;
     MOZ_COUNT_CTOR(AudioNodeStream);
   }
@@ -80,16 +81,17 @@ public:
   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,
                                   ChannelCountMode aChannelCountMoe,
                                   ChannelInterpretation aChannelInterpretation);
+  void SetPassThrough(bool aPassThrough);
   ChannelInterpretation GetChannelInterpretation()
   {
     return mChannelInterpretation;
   }
 
   void SetAudioParamHelperStream()
   {
     MOZ_ASSERT(!mAudioParamStream, "Can only do this once");
@@ -187,13 +189,15 @@ protected:
   // The mixing modes
   ChannelCountMode mChannelCountMode;
   ChannelInterpretation mChannelInterpretation;
   // Whether the stream should be marked as finished as soon
   // as the current time range has been computed block by block.
   bool mMarkAsFinishedAfterThisBlock;
   // Whether the stream is an AudioParamHelper stream.
   bool mAudioParamStream;
+  // Whether the stream just passes its input through.
+  bool mPassThrough;
 };
 
 }
 
 #endif /* MOZILLA_AUDIONODESTREAM_H_ */
--- a/content/media/webaudio/AudioNode.cpp
+++ b/content/media/webaudio/AudioNode.cpp
@@ -60,16 +60,17 @@ AudioNode::AudioNode(AudioContext* aCont
                      ChannelCountMode aChannelCountMode,
                      ChannelInterpretation aChannelInterpretation)
   : DOMEventTargetHelper(aContext->GetParentObject())
   , mContext(aContext)
   , mChannelCount(aChannelCount)
   , mChannelCountMode(aChannelCountMode)
   , mChannelInterpretation(aChannelInterpretation)
   , mId(gId++)
+  , mPassThrough(false)
 #ifdef DEBUG
   , mDemiseNotified(false)
 #endif
 {
   MOZ_ASSERT(aContext);
   DOMEventTargetHelper::BindToOwner(aContext->GetParentObject());
   SetIsDOMBinding();
   aContext->UpdateNodeCount(1);
@@ -411,10 +412,27 @@ AudioNode::DestroyMediaStream()
 }
 
 void
 AudioNode::RemoveOutputParam(AudioParam* aParam)
 {
   mOutputParams.RemoveElement(aParam);
 }
 
+bool
+AudioNode::PassThrough() const
+{
+  MOZ_ASSERT(NumberOfInputs() == 1 && NumberOfOutputs() == 1);
+  return mPassThrough;
+}
+
+void
+AudioNode::SetPassThrough(bool aPassThrough)
+{
+  MOZ_ASSERT(NumberOfInputs() == 1 && NumberOfOutputs() == 1);
+  mPassThrough = aPassThrough;
+  AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
+  MOZ_ASSERT(ns, "How come we don't have a stream here?");
+  ns->SetPassThrough(mPassThrough);
+}
+
 }
 }
--- a/content/media/webaudio/AudioNode.h
+++ b/content/media/webaudio/AudioNode.h
@@ -128,16 +128,19 @@ public:
   // The following two virtual methods must be implemented by each node type
   // to provide their number of input and output ports. These numbers are
   // constant for the lifetime of the node. Both default to 1.
   virtual uint16_t NumberOfInputs() const { return 1; }
   virtual uint16_t NumberOfOutputs() const { return 1; }
 
   uint32_t Id() const { return mId; }
 
+  bool PassThrough() const;
+  void SetPassThrough(bool aPassThrough);
+
   uint32_t ChannelCount() const { return mChannelCount; }
   virtual void SetChannelCount(uint32_t aChannelCount, ErrorResult& aRv)
   {
     if (aChannelCount == 0 ||
         aChannelCount > WebAudioUtils::MaxChannelCount) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
@@ -262,16 +265,19 @@ private:
   // able to identify the exact matching entry, since mOutputParams doesn't
   // include the port identifiers and the same node could be connected on
   // multiple ports.
   nsTArray<nsRefPtr<AudioParam> > mOutputParams;
   uint32_t mChannelCount;
   ChannelCountMode mChannelCountMode;
   ChannelInterpretation mChannelInterpretation;
   const uint32_t mId;
+  // Whether the node just passes through its input.  This is a devtools API that
+  // only works for some node types.
+  bool mPassThrough;
 #ifdef DEBUG
   // In debug builds, check to make sure that the node demise notification has
   // been properly sent before the node is destroyed.
   bool mDemiseNotified;
 #endif
 };
 
 }
--- a/content/media/webaudio/test/mochitest.ini
+++ b/content/media/webaudio/test/mochitest.ini
@@ -19,16 +19,18 @@ support-files =
   ting-48k-2ch.ogg
   ting-44.1k-1ch.wav
   ting-44.1k-2ch.wav
   ting-48k-1ch.wav
   ting-48k-2ch.wav
   webaudio.js
 
 [test_analyserNode.html]
+[test_analyserNodeOutput.html]
+[test_analyserNodePassThrough.html]
 [test_AudioBuffer.html]
 [test_audioBufferSourceNode.html]
 [test_audioBufferSourceNodeEnded.html]
 [test_audioBufferSourceNodeLazyLoopParam.html]
 [test_audioBufferSourceNodeLoop.html]
 [test_audioBufferSourceNodeLoopStartEnd.html]
 [test_audioBufferSourceNodeLoopStartEndSame.html]
 [test_audioBufferSourceNodeNeutered.html]
@@ -44,16 +46,17 @@ skip-if = (toolkit == 'gonk' && !debug) 
 [test_audioParamLinearRamp.html]
 [test_audioParamSetCurveAtTime.html]
 [test_audioParamSetCurveAtTimeZeroDuration.html]
 [test_audioParamSetTargetAtTime.html]
 [test_audioParamSetValueAtTime.html]
 [test_audioParamTimelineDestinationOffset.html]
 [test_badConnect.html]
 [test_biquadFilterNode.html]
+[test_biquadFilterNodePassThrough.html]
 [test_biquadFilterNodeWithGain.html]
 [test_bug808374.html]
 [test_bug827541.html]
 [test_bug839753.html]
 [test_bug845960.html]
 [test_bug856771.html]
 [test_bug866570.html]
 [test_bug866737.html]
@@ -69,32 +72,36 @@ skip-if = (toolkit == 'gonk' && !debug) 
 [test_bug972678.html]
 [test_channelMergerNode.html]
 [test_channelMergerNodeWithVolume.html]
 [test_channelSplitterNode.html]
 [test_channelSplitterNodeWithVolume.html]
 [test_convolverNode.html]
 [test_convolverNode_mono_mono.html]
 [test_convolverNodeChannelCount.html]
+[test_convolverNodePassThrough.html]
 [test_convolverNodeWithGain.html]
 [test_currentTime.html]
 [test_decodeMultichannel.html]
 [test_delayNode.html]
 [test_delayNodeAtMax.html]
 [test_delayNodeChannelChanges.html]
 [test_delayNodeCycles.html]
+[test_delayNodePassThrough.html]
 [test_delayNodeSmallMaxDelay.html]
 [test_delayNodeTailIncrease.html]
 [test_delayNodeTailWithDisconnect.html]
 [test_delayNodeTailWithGain.html]
 [test_delayNodeTailWithReconnect.html]
 [test_delayNodeWithGain.html]
 [test_dynamicsCompressorNode.html]
+[test_dynamicsCompressorNodePassThrough.html]
 [test_gainNode.html]
 [test_gainNodeInLoop.html]
+[test_gainNodePassThrough.html]
 [test_maxChannelCount.html]
 [test_mediaDecoding.html]
 [test_mediaElementAudioSourceNode.html]
 [test_mediaStreamAudioDestinationNode.html]
 [test_mediaStreamAudioSourceNode.html]
 [test_mediaStreamAudioSourceNodeCrossOrigin.html]
 [test_mediaStreamAudioSourceNodeResampling.html]
 [test_mixingRules.html]
@@ -120,9 +127,10 @@ skip-if = (toolkit == 'gonk' && !debug)
 [test_scriptProcessorNode_playbackTime1.html]
 [test_scriptProcessorNodeZeroInputOutput.html]
 [test_scriptProcessorNodeNotConnected.html]
 [test_singleSourceDest.html]
 [test_stereoPanningWithGain.html]
 [test_waveDecoder.html]
 [test_waveShaper.html]
 [test_waveShaperNoCurve.html]
+[test_waveShaperPassThrough.html]
 [test_waveShaperZeroLengthCurve.html]
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_analyserNodeOutput.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test AnalyserNode</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();
+
+    var analyser = context.createAnalyser();
+
+    source.buffer = this.buffer;
+
+    source.connect(analyser);
+
+    source.start(0);
+    return analyser;
+  },
+  createExpectedBuffers: function(context) {
+    this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+    }
+
+    return [this.buffer];
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_analyserNodePassThrough.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test AnalyserNode with passthrough</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();
+
+    var analyser = context.createAnalyser();
+
+    source.buffer = this.buffer;
+
+    source.connect(analyser);
+
+    var analyserWrapped = SpecialPowers.wrap(analyser);
+    ok("passThrough" in analyserWrapped, "AnalyserNode should support the passThrough API");
+    analyserWrapped.passThrough = true;
+
+    source.start(0);
+    return analyser;
+  },
+  createExpectedBuffers: function(context) {
+    this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+    }
+
+    return [this.buffer];
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_biquadFilterNodePassThrough.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test BiquadFilterNode with passthrough</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();
+
+    var filter = context.createBiquadFilter();
+
+    source.buffer = this.buffer;
+
+    source.connect(filter);
+
+    var filterWrapped = SpecialPowers.wrap(filter);
+    ok("passThrough" in filterWrapped, "BiquadFilterNode should support the passThrough API");
+    filterWrapped.passThrough = true;
+
+    source.start(0);
+    return filter;
+  },
+  createExpectedBuffers: function(context) {
+    this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+    }
+
+    return [this.buffer];
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_convolverNodePassThrough.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test ConvolverNode with passthrough</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();
+
+    var convolver = context.createConvolver();
+
+    source.buffer = this.buffer;
+    convolver.buffer = this.buffer;
+
+    source.connect(convolver);
+
+    var convolverWrapped = SpecialPowers.wrap(convolver);
+    ok("passThrough" in convolverWrapped, "ConvolverNode should support the passThrough API");
+    convolverWrapped.passThrough = true;
+
+    source.start(0);
+    return convolver;
+  },
+  createExpectedBuffers: function(context) {
+    this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+    }
+
+    return [this.buffer];
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_delayNodePassThrough.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test DelayNode</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script src="webaudio.js" type="text/javascript"></script>
+<script class="testbody" type="text/javascript">
+
+var gTest = {
+  length: 4096,
+  numberOfChannels: 1,
+  createGraph: function(context) {
+    var source = context.createBufferSource();
+
+    var delay = context.createDelay();
+
+    source.buffer = this.buffer;
+
+    source.connect(delay);
+
+    delay.delayTime.value = 0.5;
+
+    // Delay the source stream by 2048 frames
+    delay.delayTime.value = 2048 / context.sampleRate;
+
+    var delayWrapped = SpecialPowers.wrap(delay);
+    ok("passThrough" in delayWrapped, "DelayNode should support the passThrough API");
+    delayWrapped.passThrough = true;
+
+    source.start(0);
+    return delay;
+  },
+  createExpectedBuffers: function(context) {
+    this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+    }
+    var silence = context.createBuffer(1, 2048, context.sampleRate);
+
+    return [this.buffer, silence];
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_dynamicsCompressorNodePassThrough.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test DynamicsCompressorNode with passthrough</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();
+
+    var compressor = context.createDynamicsCompressor();
+
+    source.buffer = this.buffer;
+
+    source.connect(compressor);
+
+    var compressorWrapped = SpecialPowers.wrap(compressor);
+    ok("passThrough" in compressorWrapped, "DynamicsCompressorNode should support the passThrough API");
+    compressorWrapped.passThrough = true;
+
+    source.start(0);
+    return compressor;
+  },
+  createExpectedBuffers: function(context) {
+    this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+    }
+
+    return [this.buffer];
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_gainNodePassThrough.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test GainNode with passthrough</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();
+
+    var gain = context.createGain();
+
+    source.buffer = this.buffer;
+
+    source.connect(gain);
+
+    gain.gain.value = 0.5;
+
+    var gainWrapped = SpecialPowers.wrap(gain);
+    ok("passThrough" in gainWrapped, "GainNode should support the passThrough API");
+    gainWrapped.passThrough = true;
+
+    source.start(0);
+    return gain;
+  },
+  createExpectedBuffers: function(context) {
+    this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+    }
+
+    return [this.buffer];
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_pannerNodePassThrough.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test PannerNode with passthrough</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();
+
+    var panner = context.createPanner();
+
+    source.buffer = this.buffer;
+
+    source.connect(panner);
+
+    context.listener.setOrientation(0, 6.311749985202524e+307, 0, 0.1, 1000, 0);
+    context.listener.setOrientation(0, 0, -6.311749985202524e+307, 0, 0, 6.311749985202524e+307);
+    panner.setPosition(2, 0, 0);
+    panner.rolloffFactor = 0;
+    panner.panningModel = "equalpower";
+
+    var pannerWrapped = SpecialPowers.wrap(panner);
+    ok("passThrough" in pannerWrapped, "PannerNode should support the passThrough API");
+    pannerWrapped.passThrough = true;
+
+    source.start(0);
+    return panner;
+  },
+  createExpectedBuffers: function(context) {
+    this.buffer = context.createBuffer(1, 2048, context.sampleRate);
+    for (var i = 0; i < 2048; ++i) {
+      this.buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+    }
+
+    return [this.buffer];
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_waveShaperPassThrough.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test WaveShaperNode with passthrough</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;
+
+    var shaperWrapped = SpecialPowers.wrap(shaper);
+    ok("passThrough" in shaperWrapped, "WaveShaperNode should support the passThrough API");
+    shaperWrapped.passThrough = true;
+
+    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);
+    }
+
+    return [this.buffer];
+  },
+};
+
+runTest();
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/webidl/AnalyserNode.webidl
+++ b/dom/webidl/AnalyserNode.webidl
@@ -30,8 +30,11 @@ interface AnalyserNode : AudioNode {
     [SetterThrows, Pure]
     attribute double maxDecibels;
 
     [SetterThrows, Pure]
     attribute double smoothingTimeConstant;
 
 };
 
+// Mozilla extension
+AnalyserNode implements AudioNodePassThrough;
+
--- a/dom/webidl/AudioNode.webidl
+++ b/dom/webidl/AudioNode.webidl
@@ -43,9 +43,14 @@ interface AudioNode : EventTarget {
 
 };
 
 // Mozilla extension
 partial interface AudioNode {
   [ChromeOnly]
   readonly attribute unsigned long id;
 };
+[NoInterfaceObject]
+interface AudioNodePassThrough {
+  [ChromeOnly]
+  attribute boolean passThrough;
+};
 
--- a/dom/webidl/BiquadFilterNode.webidl
+++ b/dom/webidl/BiquadFilterNode.webidl
@@ -30,8 +30,11 @@ interface BiquadFilterNode : AudioNode {
     readonly attribute AudioParam gain; // in Decibels
 
     void getFrequencyResponse(Float32Array frequencyHz,
                               Float32Array magResponse,
                               Float32Array phaseResponse);
 
 };
 
+// Mozilla extension
+BiquadFilterNode implements AudioNodePassThrough;
+
--- a/dom/webidl/ConvolverNode.webidl
+++ b/dom/webidl/ConvolverNode.webidl
@@ -13,8 +13,11 @@
 interface ConvolverNode : AudioNode {
 
       [SetterThrows]
       attribute AudioBuffer? buffer;
       attribute boolean normalize;
 
 };
 
+// Mozilla extension
+ConvolverNode implements AudioNodePassThrough;
+
--- a/dom/webidl/DelayNode.webidl
+++ b/dom/webidl/DelayNode.webidl
@@ -11,8 +11,11 @@
  */
 
 interface DelayNode : AudioNode {
 
     readonly attribute AudioParam delayTime;
 
 };
 
+// Mozilla extension
+DelayNode implements AudioNodePassThrough;
+
--- a/dom/webidl/DynamicsCompressorNode.webidl
+++ b/dom/webidl/DynamicsCompressorNode.webidl
@@ -16,8 +16,11 @@ interface DynamicsCompressorNode : Audio
     readonly attribute AudioParam knee; // in Decibels
     readonly attribute AudioParam ratio; // unit-less
     readonly attribute AudioParam reduction; // in Decibels
     readonly attribute AudioParam attack; // in Seconds
     readonly attribute AudioParam release; // in Seconds
 
 };
 
+// Mozilla extension
+DynamicsCompressorNode implements AudioNodePassThrough;
+
--- a/dom/webidl/GainNode.webidl
+++ b/dom/webidl/GainNode.webidl
@@ -11,8 +11,11 @@
  */
 
 interface GainNode : AudioNode {
 
     readonly attribute AudioParam gain;
 
 };
 
+// Mozilla extension
+GainNode implements AudioNodePassThrough;
+
--- a/dom/webidl/PannerNode.webidl
+++ b/dom/webidl/PannerNode.webidl
@@ -39,8 +39,11 @@ interface PannerNode : AudioNode {
 
     // Directional sound cone 
     attribute double coneInnerAngle;
     attribute double coneOuterAngle;
     attribute double coneOuterGain;
 
 };
 
+// Mozilla extension
+PannerNode implements AudioNodePassThrough;
+
--- a/dom/webidl/ScriptProcessorNode.webidl
+++ b/dom/webidl/ScriptProcessorNode.webidl
@@ -13,8 +13,11 @@
 interface ScriptProcessorNode : AudioNode {
 
     attribute EventHandler onaudioprocess;
 
     readonly attribute long bufferSize;
 
 };
 
+// Mozilla extension
+ScriptProcessorNode implements AudioNodePassThrough;
+
--- a/dom/webidl/WaveShaperNode.webidl
+++ b/dom/webidl/WaveShaperNode.webidl
@@ -18,8 +18,11 @@ enum OverSampleType {
 
 interface WaveShaperNode : AudioNode {
 
       attribute Float32Array? curve;
       attribute OverSampleType oversample;
 
 };
 
+// Mozilla extension
+WaveShaperNode implements AudioNodePassThrough;
+