diff --git a/dom/media/webaudio/OscillatorNode.cpp b/dom/media/webaudio/OscillatorNode.cpp index e34d43aec456..4298973f499f 100644 --- a/dom/media/webaudio/OscillatorNode.cpp +++ b/dom/media/webaudio/OscillatorNode.cpp @@ -23,6 +23,40 @@ 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: @@ -37,6 +71,12 @@ public: , 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) { @@ -92,27 +132,41 @@ public: case TYPE: // Set the new type. mType = static_cast(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."); @@ -141,28 +195,31 @@ public: 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; - // Shortcut if frequency-related AudioParam are not automated, and we - // already have computed the frequency information and related parameters. - if (!ParametersMayNeedUpdate()) { - return; - } - 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 && !mRecomputeParameters) { + return; + } + if (simpleFrequency) { frequency = mFrequency.GetValue(); } else { @@ -175,10 +232,21 @@ public: } 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, @@ -203,6 +271,39 @@ public: } } + float BipolarBLIT() + { + float blit; + float denom = sin(mPhase); + + if (fabs(denom) < std::numeric_limits::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::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) { @@ -214,11 +315,54 @@ public: } } - bool ParametersMayNeedUpdate() + void ComputeSquare(float * aOutput, StreamTime ticks, uint32_t aStart, uint32_t aEnd) { - return mDetune.HasSimpleValue() || - mFrequency.HasSimpleValue() || - mRecomputeParameters; + 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, @@ -238,24 +382,15 @@ public: 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(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; @@ -322,8 +457,14 @@ public: 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; @@ -359,6 +500,7 @@ public: return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); } + DCBlocker mDCBlocker; AudioNodeStream* mSource; AudioNodeStream* mDestination; StreamTime mStart; @@ -368,7 +510,14 @@ public: 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 mCustom; uint32_t mCustomLength; diff --git a/dom/media/webaudio/blink/PeriodicWave.cpp b/dom/media/webaudio/blink/PeriodicWave.cpp index 334990e0f2ce..4ffc39d89d5b 100644 --- a/dom/media/webaudio/blink/PeriodicWave.cpp +++ b/dom/media/webaudio/blink/PeriodicWave.cpp @@ -291,12 +291,8 @@ void PeriodicWave::generateBasicWaveform(OscillatorType shape) 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"); diff --git a/dom/media/webaudio/test/mochitest.ini b/dom/media/webaudio/test/mochitest.ini index abe4b7fca50b..a0c942a6a838 100644 --- a/dom/media/webaudio/test/mochitest.ini +++ b/dom/media/webaudio/test/mochitest.ini @@ -126,7 +126,6 @@ skip-if = (toolkit == 'gonk' && !debug) [test_offlineDestinationChannelCountMore.html] [test_oscillatorNode.html] [test_oscillatorNode2.html] -[test_oscillatorNodeNegativeFrequency.html] [test_oscillatorNodePassThrough.html] [test_oscillatorNodeStart.html] [test_oscillatorTypeChange.html] diff --git a/dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html b/dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html deleted file mode 100644 index 8acc025da4ba..000000000000 --- a/dom/media/webaudio/test/test_oscillatorNodeNegativeFrequency.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - Test the OscillatorNode when the frequency is negative - - - - - -
-
-
- -