b=923106 recompute frequency dependent parameters when OscillatorType changes r=padenot
authorKarl Tomlinson <karlt+@karlt.net>
Tue, 15 Oct 2013 13:10:02 +1300
changeset 164553 7fc486a6f6450879f7b53e989d9d888f530f2f2e
parent 164552 bdbcdd02794e2c3948fb874c26e648e208def75a
child 164554 2140cb2f2fcd2ef1fa42346f6fdb1845cd470b40
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspadenot
bugs923106
milestone27.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
b=923106 recompute frequency dependent parameters when OscillatorType changes r=padenot mSquare, mTriangle, and mSaw are not initialized in the OscillatorNodeEngine constructor, just like they are not initialized when switching to OscillatorType::Sine. These parameters are initialized if and when switching to the OscillatorTypes that use them. mFinalFrequency, mNumberOfHarmonics, mSignalPeriod, mAmplitudeAtZero, mPhaseIncrement, mPhaseWrap are not initialized in the OscillatorNodeEngine constructor, just like they are not initialized immediately on switching OscillatorType. These parameters are initialized in UpdateParametersIfNeeded(), conditional on mRecomputeParameters.
content/media/webaudio/OscillatorNode.cpp
content/media/webaudio/test/mochitest.ini
content/media/webaudio/test/test_oscillatorTypeChange.html
--- a/content/media/webaudio/OscillatorNode.cpp
+++ b/content/media/webaudio/OscillatorNode.cpp
@@ -65,26 +65,23 @@ public:
     , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
     , mStart(-1)
     , mStop(TRACK_TICKS_MAX)
     // Keep the default values in sync with OscillatorNode::OscillatorNode.
     , mFrequency(440.f)
     , mDetune(0.f)
     , mType(OscillatorType::Sine)
     , mPhase(0.)
-    , mFinalFrequency(0.0)
-    , mNumberOfHarmonics(0)
-    , mSignalPeriod(0.0)
-    , mAmplitudeAtZero(0.0)
-    , mPhaseIncrement(0.0)
-    , mSquare(0.0)
-    , mTriangle(0.0)
-    , mSaw(0.0)
-    , mPhaseWrap(0.0)
-    , mRecomputeFrequency(true)
+    // mSquare, mTriangle, and mSaw are not used for default type "sine".
+    // They are initialized if and when switching to the OscillatorTypes that
+    // use them.
+    // mFinalFrequency, mNumberOfHarmonics, mSignalPeriod, mAmplitudeAtZero,
+    // mPhaseIncrement, and mPhaseWrap are initialized in
+    // UpdateParametersIfNeeded() when mRecomputeParameters is set.
+    , mRecomputeParameters(true)
     , mCustomLength(0)
   {
   }
 
   void SetSourceStream(AudioNodeStream* aSource)
   {
     mSource = aSource;
   }
@@ -96,17 +93,17 @@ public:
     PERIODICWAVE,
     START,
     STOP,
   };
   void SetTimelineParameter(uint32_t aIndex,
                             const AudioParamTimeline& aValue,
                             TrackRate aSampleRate) MOZ_OVERRIDE
   {
-    mRecomputeFrequency = true;
+    mRecomputeParameters = true;
     switch (aIndex) {
     case FREQUENCY:
       MOZ_ASSERT(mSource && mDestination);
       mFrequency = aValue;
       WebAudioUtils::ConvertAudioParamToTicks(mFrequency, mSource, mDestination);
       break;
     case DETUNE:
       MOZ_ASSERT(mSource && mDestination);
@@ -134,16 +131,17 @@ public:
       case TYPE:
         // Set the new type.
         mType = static_cast<OscillatorType>(aParam);
         if (mType != OscillatorType::Custom) {
           // Forget any previous custom data.
           mCustomLength = 0;
           mCustom = nullptr;
           mPeriodicWave = nullptr;
+          mRecomputeParameters = true;
         }
         // Update BLIT integrators with the new initial conditions.
         switch (mType) {
           case OscillatorType::Sine:
             mPhase = 0.0;
             break;
           case OscillatorType::Square:
             mPhase = 0.0;
@@ -203,42 +201,42 @@ public:
   }
 
   // Square and triangle are using a bipolar band-limited impulse train, saw is
   // using a normal band-limited impulse train.
   bool UsesBipolarBLIT() {
     return mType == OscillatorType::Square || mType == OscillatorType::Triangle;
   }
 
-  void UpdateFrequencyIfNeeded(TrackTicks ticks, size_t count)
+  void UpdateParametersIfNeeded(TrackTicks ticks, size_t count)
   {
     double frequency, detune;
 
     bool simpleFrequency = mFrequency.HasSimpleValue();
     bool simpleDetune = mDetune.HasSimpleValue();
 
     // Shortcut if frequency-related AudioParam are not automated, and we
     // already have computed the frequency information and related parameters.
-    if (simpleFrequency && simpleDetune && !mRecomputeFrequency) {
+    if (simpleFrequency && simpleDetune && !mRecomputeParameters) {
       return;
     }
 
     if (simpleFrequency) {
       frequency = mFrequency.GetValue();
     } else {
       frequency = mFrequency.GetValueAtTime(ticks, count);
     }
     if (simpleDetune) {
       detune = mDetune.GetValue();
     } else {
       detune = mDetune.GetValueAtTime(ticks, count);
     }
 
     mFinalFrequency = frequency * pow(2., detune / 1200.);
-    mRecomputeFrequency = false;
+    mRecomputeParameters = false;
 
     // When using bipolar BLIT, we divide the signal period by two, because we
     // are using two BLIT out of phase.
     mSignalPeriod = UsesBipolarBLIT() ? 0.5 * mSource->SampleRate() / mFinalFrequency
                                       : mSource->SampleRate() / mFinalFrequency;
     // Wrap the phase accordingly:
     mPhaseWrap = UsesBipolarBLIT() || mType == OscillatorType::Sine ? 2 * M_PI
                                    : M_PI;
@@ -303,59 +301,59 @@ public:
     }
 
     return blit;
   }
 
   void ComputeSine(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd)
   {
     for (uint32_t i = aStart; i < aEnd; ++i) {
-      UpdateFrequencyIfNeeded(ticks, i);
+      UpdateParametersIfNeeded(ticks, i);
 
       aOutput[i] = sin(mPhase);
 
       IncrementPhase();
     }
   }
 
   void ComputeSquare(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd)
   {
     for (uint32_t i = aStart; i < aEnd; ++i) {
-      UpdateFrequencyIfNeeded(ticks, i);
+      UpdateParametersIfNeeded(ticks, i);
       // Integration to get us a square. It turns out we can have a
       // pure integrator here.
       mSquare += BipolarBLIT();
       aOutput[i] = mSquare;
       // maybe we want to apply a gain, the wg has not decided yet
       aOutput[i] *= 1.5;
       IncrementPhase();
     }
   }
 
   void ComputeSawtooth(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd)
   {
     float dcoffset;
     for (uint32_t i = aStart; i < aEnd; ++i) {
-      UpdateFrequencyIfNeeded(ticks, i);
+      UpdateParametersIfNeeded(ticks, i);
       // DC offset so the Saw does not ramp up to infinity when integrating.
       dcoffset = mFinalFrequency / mSource->SampleRate();
       // Integrate and offset so we get mAmplitudeAtZero sawtooth. We have a
       // very low frequency component somewhere here, but I'm not sure where.
       mSaw += UnipolarBLIT() - dcoffset;
       // reverse the saw so we are spec compliant
       aOutput[i] = -mSaw * 1.5;
 
       IncrementPhase();
     }
   }
 
   void ComputeTriangle(float * aOutput, TrackTicks ticks, uint32_t aStart, uint32_t aEnd)
   {
     for (uint32_t i = aStart; i < aEnd; ++i) {
-      UpdateFrequencyIfNeeded(ticks, i);
+      UpdateParametersIfNeeded(ticks, i);
       // Integrate to get a square
       mSquare += BipolarBLIT();
       // Leaky integrate to get a triangle. We get too much dc offset if we don't
       // leaky integrate here.
       // C6 = k0 / period
       // (period is samplingrate / frequency, k0 = (PI/2)/(2*PI)) = 0.25
       float C6 = 0.25 / (mSource->SampleRate() / mFinalFrequency);
       mTriangle = mTriangle * sLeak + mSquare + C6;
@@ -375,17 +373,17 @@ public:
 
     uint32_t periodicWaveSize = mPeriodicWave->periodicWaveSize();
     float* higherWaveData = nullptr;
     float* lowerWaveData = nullptr;
     float tableInterpolationFactor;
     float rate = 1.0 / mSource->SampleRate();
  
     for (uint32_t i = aStart; i < aEnd; ++i) {
-      UpdateFrequencyIfNeeded(ticks, i);
+      UpdateParametersIfNeeded(ticks, i);
       mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
                                                      lowerWaveData,
                                                      higherWaveData,
                                                      tableInterpolationFactor);
       // mPhase runs 0..periodicWaveSize here instead of 0..2*M_PI.
       mPhase += periodicWaveSize * mFinalFrequency * rate;
       if (mPhase >= periodicWaveSize) {
         mPhase -= periodicWaveSize;
@@ -479,17 +477,17 @@ public:
   uint32_t mNumberOfHarmonics;
   float mSignalPeriod;
   float mAmplitudeAtZero;
   float mPhaseIncrement;
   float mSquare;
   float mTriangle;
   float mSaw;
   float mPhaseWrap;
-  bool mRecomputeFrequency;
+  bool mRecomputeParameters;
   nsRefPtr<ThreadSharedFloatArrayBufferList> mCustom;
   uint32_t mCustomLength;
   nsAutoPtr<WebCore::PeriodicWave> mPeriodicWave;
 };
 
 OscillatorNode::OscillatorNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
--- a/content/media/webaudio/test/mochitest.ini
+++ b/content/media/webaudio/test/mochitest.ini
@@ -92,16 +92,17 @@ support-files =
 [test_mediaStreamAudioSourceNodeResampling.html]
 [test_mixingRules.html]
 [test_nodeToParamConnection.html]
 [test_offlineDestinationChannelCountLess.html]
 [test_offlineDestinationChannelCountMore.html]
 [test_oscillatorNode.html]
 [test_oscillatorNode2.html]
 [test_oscillatorNodeStart.html]
+[test_oscillatorTypeChange.html]
 [test_pannerNode.html]
 [test_pannerNodeAbove.html]
 [test_pannerNodeChannelCount.html]
 [test_pannerNode_equalPower.html]
 [test_periodicWave.html]
 [test_scriptProcessorNode.html]
 [test_scriptProcessorNodeChannelCount.html]
 [test_scriptProcessorNodeZeroInputOutput.html]
new file mode 100644
--- /dev/null
+++ b/content/media/webaudio/test/test_oscillatorTypeChange.html
@@ -0,0 +1,58 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test OscillatorNode type change after it has started and triangle phase</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();
+
+const bufferSize = 1024;
+
+function startTest() {
+  var ctx = new AudioContext();
+
+  var oscillator1 = ctx.createOscillator();
+  oscillator1.connect(ctx.destination);
+  oscillator1.start(0);
+
+  // Assuming the above Web Audio operations have already scheduled an event
+  // to run in stable state and start the graph thread, schedule a subsequent
+  // event to change the type of oscillator1.
+  SimpleTest.executeSoon(function() {
+    oscillator1.type = "triangle";
+
+    // Another triangle wave with -1 gain should cancel the first.  This is
+    // starting at the same time as the type change, assuming that the phase
+    // is reset on type change.  A negative frequency should achieve the same
+    // as the -1 gain but for bug 916285.
+    var oscillator2 = ctx.createOscillator();
+    oscillator2.type = "triangle";
+    oscillator2.start(0);
+
+    var processor = ctx.createScriptProcessor(bufferSize, 1, 0);
+    oscillator1.connect(processor);
+    var gain = ctx.createGain();
+    gain.gain.value = -1;
+    gain.connect(processor);
+    oscillator2.connect(gain);
+
+    processor.onaudioprocess = function(e) {
+      compareBuffers(e.inputBuffer.getChannelData(0),
+                     new Float32Array(bufferSize));
+      e.target.onaudioprocess = null;
+      SimpleTest.finish();
+    }
+  });
+};
+
+startTest();
+</script>
+</pre>
+</body>
+</html>