Bug 1106649 - Use band-limited wave tables to implement basic waveforms. r=karlt
authorPaul Adenot <paul@paul.cx>
Mon, 01 Dec 2014 16:09:56 -0800
changeset 222763 ffab1d2bd4ce20632fe12e66429ac0f43c2ebac5
parent 222762 b60d1a95bfa3aa59b85a5434a20a755975972560
child 222764 894be64b069b8223f41af5154621fa8f0762c04e
push id10716
push userkwierso@gmail.com
push dateFri, 09 Jan 2015 01:17:28 +0000
treeherderfx-team@0f98d51a4a49 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskarlt
bugs1106649
milestone37.0a1
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) MOZ_OVERRIDE
   {
     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;
         }