Bug 864164 - Part 1: Send the AudioBufferSourceNode loop parameter changes to the stream; r=padenot
authorEhsan Akhgari <ehsan@mozilla.com>
Mon, 22 Apr 2013 00:22:33 -0400
changeset 129522 6ac63545817c72c74000acf91f59c18b0bf1694f
parent 129521 ec44739db921d9060558817d5a93722cbb07dd0b
child 129523 8130599b8886553d86d5f24bfb87ba088f713d9c
push id24580
push useremorley@mozilla.com
push dateTue, 23 Apr 2013 11:09:54 +0000
treeherdermozilla-central@aa620f3fc2f7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs864164
milestone23.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 864164 - Part 1: Send the AudioBufferSourceNode loop parameter changes to the stream; r=padenot
content/media/webaudio/AudioBufferSourceNode.cpp
content/media/webaudio/AudioBufferSourceNode.h
content/media/webaudio/test/Makefile.in
content/media/webaudio/test/test_audioBufferSourceNodeLoop.html
content/media/webaudio/test/test_audioBufferSourceNodeLoopStartEnd.html
--- a/content/media/webaudio/AudioBufferSourceNode.cpp
+++ b/content/media/webaudio/AudioBufferSourceNode.cpp
@@ -53,35 +53,20 @@ public:
 
   ~AudioBufferSourceNodeEngine()
   {
     if (mResampler) {
       speex_resampler_destroy(mResampler);
     }
   }
 
-  // START, OFFSET and DURATION are always set by start() (along with setting
-  // mBuffer to something non-null).
-  // STOP is set by stop().
-  enum Parameters {
-    SAMPLE_RATE,
-    START,
-    STOP,
-    OFFSET,
-    DURATION,
-    LOOP,
-    LOOPSTART,
-    LOOPEND,
-    PLAYBACKRATE,
-    DOPPLERSHIFT
-  };
   virtual void SetTimelineParameter(uint32_t aIndex, const dom::AudioParamTimeline& aValue)
   {
     switch (aIndex) {
-    case PLAYBACKRATE:
+    case AudioBufferSourceNode::PLAYBACKRATE:
       mPlaybackRateTimeline = aValue;
       // If we have a simple value that is 1.0 (i.e. intrinsic speed), and our
       // input buffer is already at the ideal audio rate, and we have a
       // resampler, we can release it.
       if (mResampler && mPlaybackRateTimeline.HasSimpleValue() &&
           mPlaybackRateTimeline.GetValue() == 1.0 &&
           mSampleRate == IdealAudioRate()) {
         speex_resampler_destroy(mResampler);
@@ -91,41 +76,41 @@ public:
       break;
     default:
       NS_ERROR("Bad GainNodeEngine TimelineParameter");
     }
   }
   virtual void SetStreamTimeParameter(uint32_t aIndex, TrackTicks aParam)
   {
     switch (aIndex) {
-    case START: mStart = aParam; break;
-    case STOP: mStop = aParam; break;
+    case AudioBufferSourceNode::START: mStart = aParam; break;
+    case AudioBufferSourceNode::STOP: mStop = aParam; break;
     default:
       NS_ERROR("Bad AudioBufferSourceNodeEngine StreamTimeParameter");
     }
   }
   virtual void SetDoubleParameter(uint32_t aIndex, double aParam)
   {
     switch (aIndex) {
-      case DOPPLERSHIFT:
+      case AudioBufferSourceNode::DOPPLERSHIFT:
         mDopplerShift = aParam;
         break;
       default:
         NS_ERROR("Bad AudioBufferSourceNodeEngine double parameter.");
     };
   }
   virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam)
   {
     switch (aIndex) {
-    case SAMPLE_RATE: mSampleRate = aParam; break;
-    case OFFSET: mOffset = aParam; break;
-    case DURATION: mDuration = aParam; break;
-    case LOOP: mLoop = !!aParam; break;
-    case LOOPSTART: mLoopStart = aParam; break;
-    case LOOPEND: mLoopEnd = aParam; break;
+    case AudioBufferSourceNode::SAMPLE_RATE: mSampleRate = aParam; break;
+    case AudioBufferSourceNode::OFFSET: mOffset = aParam; break;
+    case AudioBufferSourceNode::DURATION: mDuration = aParam; break;
+    case AudioBufferSourceNode::LOOP: mLoop = !!aParam; break;
+    case AudioBufferSourceNode::LOOPSTART: mLoopStart = aParam; break;
+    case AudioBufferSourceNode::LOOPEND: mLoopEnd = aParam; break;
     default:
       NS_ERROR("Bad AudioBufferSourceNodeEngine Int32Parameter");
     }
   }
   virtual void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
   {
     mBuffer = aBuffer;
   }
@@ -463,50 +448,29 @@ AudioBufferSourceNode::Start(JSContext* 
   double offset = std::max(0.0, aOffset);
   double endOffset = aDuration.WasPassed() ?
       std::min(aOffset + aDuration.Value(), length) : length;
 
   if (offset >= endOffset) {
     return;
   }
 
-  // Don't compute and set the loop parameters unnecessarily
-  if (mLoop) {
-    double actualLoopStart, actualLoopEnd;
-    if (((mLoopStart != 0.0) || (mLoopEnd != 0.0)) &&
-        mLoopStart >= 0.0 && mLoopEnd > 0.0 &&
-        mLoopStart < mLoopEnd) {
-      actualLoopStart = (mLoopStart > length) ? 0.0 : mLoopStart;
-      actualLoopEnd = std::min(mLoopEnd, length);
-    } else {
-      actualLoopStart = 0.0;
-      actualLoopEnd = length;
-    }
-    int32_t loopStartTicks = NS_lround(actualLoopStart * rate);
-    int32_t loopEndTicks = NS_lround(actualLoopEnd * rate);
-    ns->SetInt32Parameter(AudioBufferSourceNodeEngine::LOOP, 1);
-    ns->SetInt32Parameter(AudioBufferSourceNodeEngine::LOOPSTART, loopStartTicks);
-    ns->SetInt32Parameter(AudioBufferSourceNodeEngine::LOOPEND, loopEndTicks);
-  }
-
   ns->SetBuffer(data.forget());
   // Don't set parameter unnecessarily
   if (aWhen > 0.0) {
-    ns->SetStreamTimeParameter(AudioBufferSourceNodeEngine::START,
-                               Context()->DestinationStream(),
-                               aWhen);
+    ns->SetStreamTimeParameter(START, Context()->DestinationStream(), aWhen);
   }
   int32_t offsetTicks = NS_lround(offset*rate);
   // Don't set parameter unnecessarily
   if (offsetTicks > 0) {
-    ns->SetInt32Parameter(AudioBufferSourceNodeEngine::OFFSET, offsetTicks);
+    ns->SetInt32Parameter(OFFSET, offsetTicks);
   }
-  ns->SetInt32Parameter(AudioBufferSourceNodeEngine::DURATION,
+  ns->SetInt32Parameter(DURATION,
       NS_lround(endOffset*rate) - offsetTicks);
-  ns->SetInt32Parameter(AudioBufferSourceNodeEngine::SAMPLE_RATE, rate);
+  ns->SetInt32Parameter(SAMPLE_RATE, rate);
 
   MOZ_ASSERT(!mPlayingRef, "We can only accept a successful start() call once");
   mPlayingRef.Take(this);
 }
 
 void
 AudioBufferSourceNode::Stop(double aWhen, ErrorResult& aRv)
 {
@@ -516,18 +480,17 @@ AudioBufferSourceNode::Stop(double aWhen
   }
 
   AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
   if (!ns) {
     // We've already stopped and had our stream shut down
     return;
   }
 
-  ns->SetStreamTimeParameter(AudioBufferSourceNodeEngine::STOP,
-                             Context()->DestinationStream(),
+  ns->SetStreamTimeParameter(STOP, Context()->DestinationStream(),
                              std::max(0.0, aWhen));
 }
 
 void
 AudioBufferSourceNode::NotifyMainThreadStateChanged()
 {
   if (mStream->IsFinished()) {
     // Drop the playing reference
@@ -535,19 +498,45 @@ AudioBufferSourceNode::NotifyMainThreadS
     mPlayingRef.Drop(this);
   }
 }
 
 void
 AudioBufferSourceNode::SendPlaybackRateToStream(AudioNode* aNode)
 {
   AudioBufferSourceNode* This = static_cast<AudioBufferSourceNode*>(aNode);
-  SendTimelineParameterToStream(This, AudioBufferSourceNodeEngine::PLAYBACKRATE, *This->mPlaybackRate);
+  SendTimelineParameterToStream(This, PLAYBACKRATE, *This->mPlaybackRate);
 }
 
 void
 AudioBufferSourceNode::SendDopplerShiftToStream(double aDopplerShift)
 {
-  SendDoubleParameterToStream(AudioBufferSourceNodeEngine::DOPPLERSHIFT, aDopplerShift);
+  SendDoubleParameterToStream(DOPPLERSHIFT, aDopplerShift);
+}
+
+void
+AudioBufferSourceNode::SendLoopParametersToStream()
+{
+  SendInt32ParameterToStream(LOOP, mLoop ? 1 : 0);
+
+  // Don't compute and set the loop parameters unnecessarily
+  if (mLoop && mBuffer) {
+    float rate = mBuffer->SampleRate();
+    double length = (double(mBuffer->Length()) / mBuffer->SampleRate());
+    double actualLoopStart, actualLoopEnd;
+    if (((mLoopStart != 0.0) || (mLoopEnd != 0.0)) &&
+        mLoopStart >= 0.0 && mLoopEnd > 0.0 &&
+        mLoopStart < mLoopEnd) {
+      actualLoopStart = (mLoopStart > length) ? 0.0 : mLoopStart;
+      actualLoopEnd = std::min(mLoopEnd, length);
+    } else {
+      actualLoopStart = 0.0;
+      actualLoopEnd = length;
+    }
+    int32_t loopStartTicks = NS_lround(actualLoopStart * rate);
+    int32_t loopEndTicks = NS_lround(actualLoopEnd * rate);
+    SendInt32ParameterToStream(LOOPSTART, loopStartTicks);
+    SendInt32ParameterToStream(LOOPEND, loopEndTicks);
+  }
 }
 
 }
 }
--- a/content/media/webaudio/AudioBufferSourceNode.h
+++ b/content/media/webaudio/AudioBufferSourceNode.h
@@ -88,39 +88,62 @@ public:
   }
   bool Loop() const
   {
     return mLoop;
   }
   void SetLoop(bool aLoop)
   {
     mLoop = aLoop;
+    SendLoopParametersToStream();
   }
   double LoopStart() const
   {
     return mLoopStart;
   }
   void SetLoopStart(double aStart)
   {
     mLoopStart = aStart;
+    SendLoopParametersToStream();
   }
   double LoopEnd() const
   {
     return mLoopEnd;
   }
   void SetLoopEnd(double aEnd)
   {
     mLoopEnd = aEnd;
+    SendLoopParametersToStream();
   }
   void SendDopplerShiftToStream(double aDopplerShift);
 
   virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
 
 private:
+  friend class AudioBufferSourceNodeEngine;
+  // START, OFFSET and DURATION are always set by start() (along with setting
+  // mBuffer to something non-null).
+  // STOP is set by stop().
+  enum EngineParameters {
+    SAMPLE_RATE,
+    START,
+    STOP,
+    OFFSET,
+    DURATION,
+    LOOP,
+    LOOPSTART,
+    LOOPEND,
+    PLAYBACKRATE,
+    DOPPLERSHIFT
+  };
+
+  void SendLoopParametersToStream();
   static void SendPlaybackRateToStream(AudioNode* aNode);
+
+private:
   double mLoopStart;
   double mLoopEnd;
   nsRefPtr<AudioBuffer> mBuffer;
   nsRefPtr<AudioParam> mPlaybackRate;
   PannerNode* mPannerNode;
   SelfReference<AudioBufferSourceNode> mPlayingRef; // a reference to self while playing
   bool mLoop;
   bool mStartCalled;
--- a/content/media/webaudio/test/Makefile.in
+++ b/content/media/webaudio/test/Makefile.in
@@ -18,16 +18,18 @@ MOCHITEST_FILES := \
   test_bug845960.html \
   test_bug856771.html \
   test_analyserNode.html \
   test_AudioBuffer.html \
   test_AudioContext.html \
   test_AudioListener.html \
   test_AudioParam.html \
   test_audioBufferSourceNode.html \
+  test_audioBufferSourceNodeLoop.html \
+  test_audioBufferSourceNodeLoopStartEnd.html \
   test_badConnect.html \
   test_biquadFilterNode.html \
   test_currentTime.html \
   test_delayNode.html \
   test_decodeAudioData.html \
   test_dynamicsCompressorNode.html \
   test_gainNode.html \
   test_pannerNode.html \
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_audioBufferSourceNodeLoop.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test AudioBufferSourceNode looping</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">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+  SpecialPowers.setBoolPref("media.webaudio.enabled", true);
+
+  var context = new AudioContext();
+  var buffer = context.createBuffer(1, 2048, context.sampleRate);
+  for (var i = 0; i < 2048; ++i) {
+    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+  }
+
+  var expectedBuffer = context.createBuffer(1, 2048 * 4, context.sampleRate);
+  for (var i = 0; i < 4; ++i) {
+    for (var j = 0; j < 2048; ++j) {
+      expectedBuffer.getChannelData(0)[i * 2048 + j] = buffer.getChannelData(0)[j];
+    }
+  }
+
+  var source = context.createBufferSource();
+  source.buffer = buffer;
+
+  var sp = context.createScriptProcessor(2048 * 4, 1);
+  source.start(0);
+  source.loop = true;
+  source.connect(sp);
+  sp.connect(context.destination);
+  sp.onaudioprocess = function(e) {
+    is(e.inputBuffer.numberOfChannels, 1, "input buffer must have only one channel");
+    compareBuffers(e.inputBuffer.getChannelData(0), expectedBuffer.getChannelData(0));
+
+    sp.onaudioprocess = null;
+
+    SpecialPowers.clearUserPref("media.webaudio.enabled");
+    SimpleTest.finish();
+  };
+});
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_audioBufferSourceNodeLoopStartEnd.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test AudioBufferSourceNode looping</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">
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+  SpecialPowers.setBoolPref("media.webaudio.enabled", true);
+
+  var context = new AudioContext();
+  var buffer = context.createBuffer(1, 2048, context.sampleRate);
+  for (var i = 0; i < 2048; ++i) {
+    buffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
+  }
+
+  var expectedBuffer = context.createBuffer(1, 2048 * 4, context.sampleRate);
+  for (var j = 0; j < 1536; ++j) {
+    expectedBuffer.getChannelData(0)[j] = buffer.getChannelData(0)[j];
+  }
+  for (var i = 0; i < 6; ++i) {
+    for (var j = 512; j < 1536; ++j) {
+      expectedBuffer.getChannelData(0)[1536 + i * 1024 + j - 512] = buffer.getChannelData(0)[j];
+    }
+  }
+  for (var j = 7680; j < 2048 * 4; ++j) {
+    expectedBuffer.getChannelData(0)[j] = buffer.getChannelData(0)[j - 7168];
+  }
+
+  var source = context.createBufferSource();
+  source.buffer = buffer;
+
+  var sp = context.createScriptProcessor(2048 * 4, 1);
+  source.start(0);
+  source.loop = true;
+  source.loopStart = buffer.duration * 0.25;
+  source.loopEnd = buffer.duration * 0.75;
+  source.connect(sp);
+  sp.connect(context.destination);
+  sp.onaudioprocess = function(e) {
+    is(e.inputBuffer.numberOfChannels, 1, "input buffer must have only one channel");
+    compareBuffers(e.inputBuffer.getChannelData(0), expectedBuffer.getChannelData(0));
+
+    sp.onaudioprocess = null;
+
+    SpecialPowers.clearUserPref("media.webaudio.enabled");
+    SimpleTest.finish();
+  };
+});
+
+</script>
+</pre>
+</body>
+</html>