Backed out 4 changesets (bug 1106649, bug 916285, bug 997870) for test_periodicWave.html failures.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 05 Jan 2015 14:22:29 -0500
changeset 222055 72d7ae169b094ee14e59053a8ad1bdfc8e9e423e
parent 222054 4ed5726c356bed3a613c6c567264cdebe89433db
child 222079 fd223e4af53dd3fa313a7bc04a869a95374f689a
child 222131 78098039e9c13ac28b792d1ac3e086f278de09fa
push id28055
push userkwierso@gmail.com
push dateTue, 06 Jan 2015 00:19:38 +0000
treeherdermozilla-central@72d7ae169b09 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1106649, 916285, 997870
milestone37.0a1
backs out43e2d930ba6f044213d2446bbfcbb1c256a50660
011c2f2f58992100ebe4c6ca4433d58117de7cf4
b7303f56216b6f000055f434437d86df496b169f
a2b0a16b88981a2f807a704f5eb8ab35b4df2435
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
Backed out 4 changesets (bug 1106649, bug 916285, bug 997870) for test_periodicWave.html failures. Backed out changeset 43e2d930ba6f (bug 916285) Backed out changeset 011c2f2f5899 (bug 916285) Backed out changeset b7303f56216b (bug 997870) Backed out changeset a2b0a16b8898 (bug 1106649) CLOSED TREE
dom/media/webaudio/OscillatorNode.cpp
dom/media/webaudio/blink/PeriodicWave.cpp
dom/media/webaudio/test/mochitest.ini
dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html
--- a/dom/media/webaudio/OscillatorNode.cpp
+++ b/dom/media/webaudio/OscillatorNode.cpp
@@ -18,30 +18,70 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(Oscil
                                    mPeriodicWave, mFrequency, mDetune)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(OscillatorNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(OscillatorNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(OscillatorNode, AudioNode)
 
+static const float sLeakTriangle = 0.995f;
+static const float sLeak = 0.999f;
+
+class DCBlocker
+{
+public:
+  // These are sane defauts when the initial mPhase is zero
+  explicit DCBlocker(float aLastInput = 0.0f,
+            float aLastOutput = 0.0f,
+            float aPole = 0.995)
+    :mLastInput(aLastInput),
+     mLastOutput(aLastOutput),
+     mPole(aPole)
+  {
+    MOZ_ASSERT(aPole > 0);
+  }
+
+  inline float Process(float aInput)
+  {
+    float out;
+
+    out = mLastOutput * mPole + aInput - mLastInput;
+    mLastOutput = out;
+    mLastInput = aInput;
+
+    return out;
+  }
+private:
+  float mLastInput;
+  float mLastOutput;
+  float mPole;
+};
+
+
 class OscillatorNodeEngine : public AudioNodeEngine
 {
 public:
   OscillatorNodeEngine(AudioNode* aNode, AudioDestinationNode* aDestination)
     : AudioNodeEngine(aNode)
     , mSource(nullptr)
     , mDestination(static_cast<AudioNodeStream*> (aDestination->Stream()))
     , mStart(-1)
     , mStop(STREAM_TIME_MAX)
     // Keep the default values in sync with OscillatorNode::OscillatorNode.
     , mFrequency(440.f)
     , mDetune(0.f)
     , mType(OscillatorType::Sine)
     , mPhase(0.)
+    // 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;
@@ -87,37 +127,51 @@ public:
   }
 
   virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam) MOZ_OVERRIDE
   {
     switch (aIndex) {
       case TYPE:
         // Set the new type.
         mType = static_cast<OscillatorType>(aParam);
-        if (mType == OscillatorType::Sine) {
+        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:
-            mPeriodicWave = WebCore::PeriodicWave::createSquare(mSource->SampleRate());
+            mPhase = 0.0;
+            // Initial integration condition is -0.5, because our
+            // square has 50% duty cycle.
+            mSquare = -0.5;
             break;
           case OscillatorType::Triangle:
-            mPeriodicWave = WebCore::PeriodicWave::createTriangle(mSource->SampleRate());
+            // Initial mPhase and related integration condition so the
+            // triangle is in the middle of the first upward slope.
+            // XXX actually do the maths and put the right number here.
+            mPhase = (float)(M_PI / 2);
+            mSquare = 0.5;
+            mTriangle = 0.0;
             break;
           case OscillatorType::Sawtooth:
-            mPeriodicWave = WebCore::PeriodicWave::createSawtooth(mSource->SampleRate());
+            // Initial mPhase so the oscillator starts at the
+            // middle of the ramp, per spec.
+            mPhase = (float)(M_PI / 2);
+            // mSaw = 0 when mPhase = pi/2.
+            mSaw = 0.0;
             break;
           case OscillatorType::Custom:
+            // Custom waveforms don't use BLIT.
             break;
           default:
             NS_ERROR("Bad OscillatorNodeEngine type parameter.");
         }
         // End type switch.
         break;
       case PERIODICWAVE:
         MOZ_ASSERT(aParam >= 0, "negative custom array length");
@@ -136,54 +190,68 @@ public:
     MOZ_ASSERT(mCustom->GetChannels() == 2,
                "PeriodicWave should have sent two channels");
     mPeriodicWave = WebCore::PeriodicWave::create(mSource->SampleRate(),
     mCustom->GetData(0), mCustom->GetData(1), mCustomLength);
   }
 
   void IncrementPhase()
   {
-    const float twoPiFloat = float(2 * M_PI);
     mPhase += mPhaseIncrement;
-    if (mPhase > twoPiFloat) {
-      mPhase -= twoPiFloat;
-    } else if (mPhase < -twoPiFloat) {
-      mPhase += twoPiFloat;
+    if (mPhase > mPhaseWrap) {
+      mPhase -= mPhaseWrap;
     }
   }
 
+  // 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 UpdateParametersIfNeeded(StreamTime 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 (!ParametersMayNeedUpdate()) {
+    if (simpleFrequency && simpleDetune && !mRecomputeParameters) {
       return;
     }
 
-    bool simpleFrequency = mFrequency.HasSimpleValue();
-    bool simpleDetune = mDetune.HasSimpleValue();
-
     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.);
-    float signalPeriod = mSource->SampleRate() / mFinalFrequency;
     mRecomputeParameters = false;
 
-    mPhaseIncrement = 2 * M_PI / signalPeriod;
+    // 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;
+    // Even number of harmonics for bipolar blit, odd otherwise.
+    mNumberOfHarmonics = UsesBipolarBLIT() ? 2 * floor(0.5 * mSignalPeriod)
+                                           : 2 * floor(0.5 * mSignalPeriod) + 1;
+    mPhaseIncrement = mType == OscillatorType::Sine ? 2 * M_PI / mSignalPeriod
+                                                    : M_PI / mSignalPeriod;
+    mAmplitudeAtZero = mNumberOfHarmonics / mSignalPeriod;
   }
 
   void FillBounds(float* output, StreamTime ticks,
                   uint32_t& start, uint32_t& end)
   {
     MOZ_ASSERT(output);
     static_assert(StreamTime(WEBAUDIO_BLOCK_SIZE) < UINT_MAX,
         "WEBAUDIO_BLOCK_SIZE overflows interator bounds.");
@@ -198,32 +266,108 @@ public:
     if (ticks + end > mStop) {
       end = mStop - ticks;
       for (uint32_t i = end; i < WEBAUDIO_BLOCK_SIZE; ++i) {
         output[i] = 0.0;
       }
     }
   }
 
+  float BipolarBLIT()
+  {
+    float blit;
+    float denom = sin(mPhase);
+
+    if (fabs(denom) < std::numeric_limits<float>::epsilon()) {
+      if (mPhase < 0.1f || mPhase > 2 * M_PI - 0.1f) {
+        blit = mAmplitudeAtZero;
+      } else {
+        blit = -mAmplitudeAtZero;
+      }
+    } else {
+      blit = sin(mNumberOfHarmonics * mPhase);
+      blit /= mSignalPeriod * denom;
+    }
+    return blit;
+  }
+
+  float UnipolarBLIT()
+  {
+    float blit;
+    float denom = sin(mPhase);
+
+    if (fabs(denom) <= std::numeric_limits<float>::epsilon()) {
+      blit = mAmplitudeAtZero;
+    } else {
+      blit = sin(mNumberOfHarmonics * mPhase);
+      blit /= mSignalPeriod * denom;
+    }
+
+    return blit;
+  }
+
   void ComputeSine(float * aOutput, StreamTime ticks, uint32_t aStart, uint32_t aEnd)
   {
     for (uint32_t i = aStart; i < aEnd; ++i) {
       UpdateParametersIfNeeded(ticks, i);
 
       aOutput[i] = sin(mPhase);
 
       IncrementPhase();
     }
   }
 
-  bool ParametersMayNeedUpdate()
+  void ComputeSquare(float * aOutput, StreamTime ticks, uint32_t aStart, uint32_t aEnd)
+  {
+    for (uint32_t i = aStart; i < aEnd; ++i) {
+      UpdateParametersIfNeeded(ticks, i);
+      // Integration to get us a square. It turns out we can have a
+      // pure integrator here.
+      mSquare = mSquare * sLeak + 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, StreamTime ticks, uint32_t aStart, uint32_t aEnd)
   {
-    return mDetune.HasSimpleValue() ||
-           mFrequency.HasSimpleValue() ||
-           mRecomputeParameters;
+    float dcoffset;
+    for (uint32_t i = aStart; i < aEnd; ++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 = mSaw * sLeak + (UnipolarBLIT() - dcoffset);
+      // reverse the saw so we are spec compliant
+      aOutput[i] = -mSaw * 1.5;
+
+      IncrementPhase();
+    }
+  }
+
+  void ComputeTriangle(float * aOutput, StreamTime ticks, uint32_t aStart, uint32_t aEnd)
+  {
+    for (uint32_t i = aStart; i < aEnd; ++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 * sLeakTriangle + mSquare + C6;
+      // DC Block, and scale back to [-1.0; 1.0]
+      aOutput[i] = mDCBlocker.Process(mTriangle) / (mSignalPeriod/2) * 1.5;
+
+      IncrementPhase();
+    }
   }
 
   void ComputeCustom(float* aOutput,
                      StreamTime ticks,
                      uint32_t aStart,
                      uint32_t aEnd)
   {
     MOZ_ASSERT(mPeriodicWave, "No custom waveform data");
@@ -233,34 +377,25 @@ public:
     uint32_t indexMask = periodicWaveSize - 1;
     MOZ_ASSERT(periodicWaveSize && (periodicWaveSize & indexMask) == 0,
                "periodicWaveSize must be power of 2");
     float* higherWaveData = nullptr;
     float* lowerWaveData = nullptr;
     float tableInterpolationFactor;
     // Phase increment at frequency of 1 Hz.
     // mPhase runs [0,periodicWaveSize) here instead of [0,2*M_PI).
-    float basePhaseIncrement = mPeriodicWave->rateScale();
+    float basePhaseIncrement =
+      static_cast<float>(periodicWaveSize) / mSource->SampleRate();
 
-    bool parametersMayNeedUpdate = ParametersMayNeedUpdate();
-    if (!parametersMayNeedUpdate) {
+    for (uint32_t i = aStart; i < aEnd; ++i) {
+      UpdateParametersIfNeeded(ticks, i);
       mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
                                                      lowerWaveData,
                                                      higherWaveData,
                                                      tableInterpolationFactor);
-    }
-
-    for (uint32_t i = aStart; i < aEnd; ++i) {
-      if (parametersMayNeedUpdate) {
-        mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
-                                                       lowerWaveData,
-                                                       higherWaveData,
-                                                       tableInterpolationFactor);
-        UpdateParametersIfNeeded(ticks, i);
-      }
       // Bilinear interpolation between adjacent samples in each table.
       float floorPhase = floorf(mPhase);
       uint32_t j1 = floorPhase;
       j1 &= indexMask;
       uint32_t j2 = j1 + 1;
       j2 &= indexMask;
 
       float sampleInterpolationFactor = mPhase - floorPhase;
@@ -317,18 +452,24 @@ public:
     FillBounds(output, ticks, start, end);
 
     // Synthesize the correct waveform.
     switch(mType) {
       case OscillatorType::Sine:
         ComputeSine(output, ticks, start, end);
         break;
       case OscillatorType::Square:
+        ComputeSquare(output, ticks, start, end);
+        break;
       case OscillatorType::Triangle:
+        ComputeTriangle(output, ticks, start, end);
+        break;
       case OscillatorType::Sawtooth:
+        ComputeSawtooth(output, ticks, start, end);
+        break;
       case OscillatorType::Custom:
         ComputeCustom(output, ticks, start, end);
         break;
       default:
         ComputeSilence(aOutput);
     };
 
   }
@@ -354,26 +495,34 @@ public:
     return amount;
   }
 
   virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   {
     return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
+  DCBlocker mDCBlocker;
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   StreamTime mStart;
   StreamTime mStop;
   AudioParamTimeline mFrequency;
   AudioParamTimeline mDetune;
   OscillatorType mType;
   float mPhase;
   float mFinalFrequency;
+  uint32_t mNumberOfHarmonics;
+  float mSignalPeriod;
+  float mAmplitudeAtZero;
   float mPhaseIncrement;
+  float mSquare;
+  float mTriangle;
+  float mSaw;
+  float mPhaseWrap;
   bool mRecomputeParameters;
   nsRefPtr<ThreadSharedFloatArrayBufferList> mCustom;
   uint32_t mCustomLength;
   nsAutoPtr<WebCore::PeriodicWave> mPeriodicWave;
 };
 
 OscillatorNode::OscillatorNode(AudioContext* aContext)
   : AudioNode(aContext,
--- a/dom/media/webaudio/blink/PeriodicWave.cpp
+++ b/dom/media/webaudio/blink/PeriodicWave.cpp
@@ -286,22 +286,18 @@ void PeriodicWave::generateBasicWaveform
             // Sawtooth-shaped waveform with the first half ramping from
             // zero to maximum and the second half from minimum to zero.
             a = 0;
             b = -invOmega * cos(0.5 * omega);
             break;
         case OscillatorType::Triangle:
             // Triangle-shaped waveform going from its maximum value to
             // its minimum value then back to the maximum value.
-            a = 0;
-            if (n & 1) {
-              b = 2 * (2 / (n * piFloat) * 2 / (n * piFloat)) * ((((n - 1) >> 1) & 1) ? -1 : 1);
-            } else {
-              b = 0;
-            }
+            a = (4 - 4 * cos(0.5 * omega)) / (n * n * piFloat * piFloat);
+            b = 0;
             break;
         default:
             NS_NOTREACHED("invalid oscillator type");
             a = 0;
             b = 0;
             break;
         }
 
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -121,17 +121,16 @@ skip-if = android_version == '10' # bug 
 [test_mozaudiochannel.html]
 skip-if = (toolkit == 'gonk' && !debug)
 [test_nodeToParamConnection.html]
 [test_OfflineAudioContext.html]
 [test_offlineDestinationChannelCountLess.html]
 [test_offlineDestinationChannelCountMore.html]
 [test_oscillatorNode.html]
 [test_oscillatorNode2.html]
-[test_oscillatorNodeNegativeFrequency.html]
 [test_oscillatorNodePassThrough.html]
 [test_oscillatorNodeStart.html]
 [test_oscillatorTypeChange.html]
 [test_pannerNode.html]
 [test_pannerNode_equalPower.html]
 [test_pannerNodeAbove.html]
 [test_pannerNodeChannelCount.html]
 [test_pannerNodeHRTFSymmetry.html]
deleted file mode 100644
--- a/dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html
+++ /dev/null
@@ -1,50 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test the OscillatorNode when the frequency is negative</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() {
-
-  var types = ["sine",
-               "square",
-               "sawtooth",
-               "triangle"];
-
-  var finished = 0;
-  function finish() {
-    if (++finished == types.length) {
-      SimpleTest.finish();
-    }
-  }
-
-  types.forEach(function(t) {
-    var context = new OfflineAudioContext(1, 256, 44100);
-    var osc = context.createOscillator();
-
-    osc.frequency.value = -440;
-    osc.type = t;
-
-    osc.connect(context.destination);
-    osc.start();
-    context.startRendering().then(function(buffer) {
-      var samples = buffer.getChannelData(0);
-      // This samples the wave form in the middle of the first period, the value
-      // should be negative.
-      ok(samples[Math.floor(44100 / 440 / 4)] < 0., "Phase should be inverted when using a " + t + " waveform");
-      finish();
-    });
-  });
-});
-
-</script>
-</pre>
-</body>
-</html>