Bug 1106649 - Use band-limited wave tables to implement basic waveforms. r=karlt
☠☠ backed out by 9d72b8315f65 ☠ ☠
authorPaul Adenot <paul@paul.cx>
Mon, 01 Dec 2014 16:09:56 -0800
changeset 247836 3ae2161027a62bad6a2bbf8c2017aa5ad5ba9806
parent 247835 8f85d78f6890826e89686aa451d7c2db60666cff
child 247837 187fa323cec69d9406514e11ebe1c1f3b9a18661
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt
bugs1106649
milestone37.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 1106649 - Use band-limited wave tables to implement basic waveforms. r=karlt
dom/media/webaudio/OscillatorNode.cpp
dom/media/webaudio/blink/PeriodicWave.cpp
--- a/dom/media/webaudio/OscillatorNode.cpp
+++ b/dom/media/webaudio/OscillatorNode.cpp
@@ -18,70 +18,30 @@ 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;
@@ -127,51 +87,37 @@ public:
   }
 
   virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam)
   {
     switch (aIndex) {
       case TYPE:
         // Set the new type.
         mType = static_cast<OscillatorType>(aParam);
-        if (mType != OscillatorType::Custom) {
+        if (mType == OscillatorType::Sine) {
           // 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;
-            // Initial integration condition is -0.5, because our
-            // square has 50% duty cycle.
-            mSquare = -0.5;
+            mPeriodicWave = WebCore::PeriodicWave::createSquare(mSource->SampleRate());
             break;
           case OscillatorType::Triangle:
-            // 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;
+            mPeriodicWave = WebCore::PeriodicWave::createTriangle(mSource->SampleRate());
             break;
           case OscillatorType::Sawtooth:
-            // 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;
+            mPeriodicWave = WebCore::PeriodicWave::createSawtooth(mSource->SampleRate());
             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");
@@ -196,22 +142,16 @@ public:
   void IncrementPhase()
   {
     mPhase += mPhaseIncrement;
     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
@@ -226,32 +166,22 @@ public:
       frequency = mFrequency.GetValueAtTime(ticks, count);
     }
     if (simpleDetune) {
       detune = mDetune.GetValue();
     } else {
       detune = mDetune.GetValueAtTime(ticks, count);
     }
 
+    float signalPeriod = mSource->SampleRate() / mFinalFrequency;
     mFinalFrequency = frequency * pow(2., detune / 1200.);
     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;
-    // 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;
+    mPhaseWrap = 2 * M_PI;
+    mPhaseIncrement = 2 * M_PI / signalPeriod;
   }
 
   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.");
@@ -266,110 +196,27 @@ 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();
     }
   }
 
-  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)
-  {
-    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");
 
     uint32_t periodicWaveSize = mPeriodicWave->periodicWaveSize();
@@ -377,18 +224,17 @@ 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 =
-      static_cast<float>(periodicWaveSize) / mSource->SampleRate();
+    float basePhaseIncrement = mPeriodicWave->rateScale();
 
     for (uint32_t i = aStart; i < aEnd; ++i) {
       UpdateParametersIfNeeded(ticks, i);
       mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
                                                      lowerWaveData,
                                                      higherWaveData,
                                                      tableInterpolationFactor);
       // Bilinear interpolation between adjacent samples in each table.
@@ -452,24 +298,18 @@ 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);
     };
 
   }
@@ -495,33 +335,26 @@ 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)
--- a/dom/media/webaudio/blink/PeriodicWave.cpp
+++ b/dom/media/webaudio/blink/PeriodicWave.cpp
@@ -286,18 +286,22 @@ 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 = (4 - 4 * cos(0.5 * omega)) / (n * n * piFloat * piFloat);
-            b = 0;
+            a = 0;
+            if (n & 1) {
+              b = 2 * (2 / (n * piFloat) * 2 / (n * piFloat)) * ((((n - 1) >> 1) & 1) ? -1 : 1);
+            } else {
+              b = 0;
+            }
             break;
         default:
             NS_NOTREACHED("invalid oscillator type");
             a = 0;
             b = 0;
             break;
         }