зеркало из https://github.com/mozilla/gecko-dev.git
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
This commit is contained in:
Родитель
f62303f39a
Коммит
3d55a15384
|
@ -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<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.");
|
||||
|
@ -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<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) {
|
||||
|
@ -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<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;
|
||||
|
@ -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<ThreadSharedFloatArrayBufferList> mCustom;
|
||||
uint32_t mCustomLength;
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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>
|
Загрузка…
Ссылка в новой задаче