Bug 865256 - Part 4: Implement custom waveforms. r=ehsan,padenot

Copy the periodicWave data into a shareable buffer.

Add a SetBuffer call to send the buffer data to the
OscillatorNodeEngine. Call into blink's PeriodicWave
implementation to generate bandlimited tables bracketing
the desired frequency and interpolate the output data
from them.

Change the PeriodicWave constructor to only take one
length, since both arrays must be the same size.

Change OscillatorNode's SetType to throw INVALID_STATE
instead of NOT_IMPLEMENTED if js tries to assign
type = 'custom' directly.
This commit is contained in:
Ralph Giles 2013-08-28 15:39:26 -07:00
Родитель 2ea123dcde
Коммит a4447ac147
6 изменённых файлов: 177 добавлений и 41 удалений

Просмотреть файл

@ -396,8 +396,11 @@ AudioContext::CreatePeriodicWave(const Float32Array& aRealData,
}
nsRefPtr<PeriodicWave> periodicWave =
new PeriodicWave(this, aRealData.Data(), aRealData.Length(),
aImagData.Data(), aImagData.Length());
new PeriodicWave(this, aRealData.Data(), aImagData.Data(),
aImagData.Length(), aRv);
if (aRv.Failed()) {
return nullptr;
}
return periodicWave.forget();
}

Просмотреть файл

@ -9,6 +9,7 @@
#include "AudioNodeStream.h"
#include "AudioDestinationNode.h"
#include "WebAudioUtils.h"
#include "blink/PeriodicWave.h"
namespace mozilla {
namespace dom {
@ -93,6 +94,7 @@ public:
, mSaw(0.0)
, mPhaseWrap(0.0)
, mRecomputeFrequency(true)
, mCustomLength(0)
{
}
@ -142,36 +144,68 @@ public:
virtual void SetInt32Parameter(uint32_t aIndex, int32_t aParam)
{
mType = static_cast<OscillatorType>(aParam);
// Set the new type, and update integrators with the new initial conditions.
switch (mType) {
case OscillatorType::Sine:
mPhase = 0.0;
switch (aIndex) {
case TYPE:
// Set the new type.
mType = static_cast<OscillatorType>(aParam);
if (mType != OscillatorType::Custom) {
// Forget any previous custom data.
mCustomLength = 0;
mCustom = nullptr;
mPeriodicWave = nullptr;
}
// 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;
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;
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;
break;
case OscillatorType::Custom:
// Custom waveforms don't use BLIT.
break;
default:
NS_ERROR("Bad OscillatorNodeEngine type parameter.");
}
// End type switch.
break;
case OscillatorType::Square:
mPhase = 0.0;
// Initial integration condition is -0.5, because our square has 50%
// duty cycle.
mSquare = -0.5;
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;
break;
case OscillatorType::Sawtooth:
/* initial mPhase so the oscillator start at the middle
* of the ramp, per spec */
mPhase = (float)(M_PI / 2);
/* mSaw = 0 when mPhase = pi/2 */
mSaw = 0.0;
case PERIODICWAVE:
MOZ_ASSERT(aParam >= 0, "negative custom array length");
mCustomLength = static_cast<uint32_t>(aParam);
break;
default:
NS_ERROR("Bad OscillatorNodeEngine Int32Parameter.");
};
}
// End index switch.
}
virtual void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer)
{
MOZ_ASSERT(mCustomLength, "Custom buffer sent before length");
mCustom = aBuffer;
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()
@ -346,6 +380,46 @@ public:
}
}
void ComputeCustom(float* aOutput,
TrackTicks ticks,
uint32_t aStart,
uint32_t aEnd)
{
MOZ_ASSERT(mPeriodicWave, "No custom waveform data");
uint32_t periodicWaveSize = mPeriodicWave->periodicWaveSize();
float* higherWaveData = nullptr;
float* lowerWaveData = nullptr;
float tableInterpolationFactor;
float rate = 1.0 / mSource->SampleRate();
for (uint32_t i = aStart; i < aEnd; ++i) {
UpdateFrequencyIfNeeded(ticks, i);
mPeriodicWave->waveDataForFundamentalFrequency(mFinalFrequency,
lowerWaveData,
higherWaveData,
tableInterpolationFactor);
// mPhase runs 0..periodicWaveSize here instead of 0..2*M_PI.
mPhase += periodicWaveSize * mFinalFrequency * rate;
if (mPhase >= periodicWaveSize) {
mPhase -= periodicWaveSize;
}
// Bilinear interpolation between adjacent samples in each table.
uint32_t j1 = floor(mPhase);
uint32_t j2 = j1 + 1;
if (j2 >= periodicWaveSize) {
j2 -= periodicWaveSize;
}
float sampleInterpolationFactor = mPhase - j1;
float lower = sampleInterpolationFactor * lowerWaveData[j1] +
(1 - sampleInterpolationFactor) * lowerWaveData[j2];
float higher = sampleInterpolationFactor * higherWaveData[j1] +
(1 - sampleInterpolationFactor) * higherWaveData[j2];
aOutput[i] = tableInterpolationFactor * lower +
(1 - tableInterpolationFactor) * higher;
}
}
void ComputeSilence(AudioChunk *aOutput)
{
aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
@ -397,6 +471,9 @@ public:
case OscillatorType::Sawtooth:
ComputeSawtooth(output, ticks, start, end);
break;
case OscillatorType::Custom:
ComputeCustom(output, ticks, start, end);
break;
default:
ComputeSilence(aOutput);
};
@ -422,6 +499,9 @@ public:
float mSaw;
float mPhaseWrap;
bool mRecomputeFrequency;
nsRefPtr<ThreadSharedFloatArrayBufferList> mCustom;
uint32_t mCustomLength;
nsAutoPtr<WebCore::PeriodicWave> mPeriodicWave;
};
OscillatorNode::OscillatorNode(AudioContext* aContext)
@ -472,10 +552,25 @@ OscillatorNode::SendDetuneToStream(AudioNode* aNode)
void
OscillatorNode::SendTypeToStream()
{
SendInt32ParameterToStream(OscillatorNodeEngine::TYPE, static_cast<int32_t>(mType));
if (mType == OscillatorType::Custom) {
// TODO: Send the custom wave table somehow
// The engine assumes we'll send the custom data before updating the type.
SendPeriodicWaveToStream();
}
SendInt32ParameterToStream(OscillatorNodeEngine::TYPE, static_cast<int32_t>(mType));
}
void OscillatorNode::SendPeriodicWaveToStream()
{
NS_ASSERTION(mType == OscillatorType::Custom,
"Sending custom waveform to engine thread with non-custom type");
AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
MOZ_ASSERT(ns, "Missing node stream.");
MOZ_ASSERT(mPeriodicWave, "Send called without PeriodicWave object.");
SendInt32ParameterToStream(OscillatorNodeEngine::PERIODICWAVE,
mPeriodicWave->DataLength());
nsRefPtr<ThreadSharedFloatArrayBufferList> data =
mPeriodicWave->GetThreadSharedBuffer();
ns->SetBuffer(data.forget());
}
void

Просмотреть файл

@ -77,9 +77,10 @@ public:
// Shut up the compiler warning
break;
}
if (aType == OscillatorType::Custom) {
aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
// ::Custom can only be set by setPeriodicWave().
// https://github.com/WebAudio/web-audio-api/issues/105 for exception.
aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
return;
}
mType = aType;
@ -108,6 +109,7 @@ public:
void SetPeriodicWave(PeriodicWave& aPeriodicWave)
{
mPeriodicWave = &aPeriodicWave;
// SendTypeToStream will call SendPeriodicWaveToStream for us.
mType = OscillatorType::Custom;
SendTypeToStream();
}
@ -120,6 +122,7 @@ private:
static void SendFrequencyToStream(AudioNode* aNode);
static void SendDetuneToStream(AudioNode* aNode);
void SendTypeToStream();
void SendPeriodicWaveToStream();
private:
OscillatorType mType;

Просмотреть файл

@ -18,13 +18,30 @@ NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(PeriodicWave, Release)
PeriodicWave::PeriodicWave(AudioContext* aContext,
const float* aRealData,
uint32_t aRealDataLength,
const float* aImagData,
uint32_t aImagDataLength)
const uint32_t aLength,
ErrorResult& aRv)
: mContext(aContext)
{
MOZ_ASSERT(aContext);
SetIsDOMBinding();
// Caller should have checked this and thrown.
MOZ_ASSERT(aLength > 0);
MOZ_ASSERT(aLength <= 4096);
mLength = aLength;
// Copy coefficient data. The two arrays share an allocation.
mCoefficients = new ThreadSharedFloatArrayBufferList(2);
float* buffer = static_cast<float*>(malloc(aLength*sizeof(float)*2));
if (buffer == nullptr) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
PodCopy(buffer, aRealData, aLength);
mCoefficients->SetData(0, buffer, buffer);
PodCopy(buffer+aLength, aImagData, aLength);
mCoefficients->SetData(1, nullptr, buffer+aLength);
}
JSObject*
@ -33,6 +50,6 @@ PeriodicWave::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
return PeriodicWaveBinding::Wrap(aCx, aScope, this);
}
}
}
} // namespace dom
} // namespace mozilla

Просмотреть файл

@ -12,6 +12,7 @@
#include "mozilla/Attributes.h"
#include "EnableWebAudioCheck.h"
#include "AudioContext.h"
#include "AudioNodeEngine.h"
#include "nsAutoPtr.h"
namespace mozilla {
@ -24,9 +25,9 @@ class PeriodicWave MOZ_FINAL : public nsWrapperCache,
public:
PeriodicWave(AudioContext* aContext,
const float* aRealData,
uint32_t aRealDataLength,
const float* aImagData,
uint32_t aImagDataLength);
const uint32_t aLength,
ErrorResult& aRv);
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PeriodicWave)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(PeriodicWave)
@ -39,12 +40,23 @@ public:
virtual JSObject* WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
uint32_t DataLength() const
{
return mLength;
}
ThreadSharedFloatArrayBufferList* GetThreadSharedBuffer() const
{
return mCoefficients;
}
private:
nsRefPtr<AudioContext> mContext;
nsRefPtr<ThreadSharedFloatArrayBufferList> mCoefficients;
uint32_t mLength;
};
}
}
#endif

Просмотреть файл

@ -22,10 +22,10 @@ addLoadEvent(function() {
is(osc.type, "sine", "Correct default type");
expectException(function() {
osc.type = "custom";
}, DOMException.NOT_SUPPORTED_ERR);
}, DOMException.INVALID_STATE_ERR);
expectException(function() {
osc.type = osc.CUSTOM;
}, DOMException.NOT_SUPPORTED_ERR);
}, DOMException.INVALID_STATE_ERR);
is(osc.type, "sine", "Cannot set the type to custom");
is(osc.frequency.value, 440, "Correct default frequency value");
is(osc.detune.value, 0, "Correct default detine value");
@ -43,6 +43,12 @@ addLoadEvent(function() {
osc.type = types[i];
}
// Verify setPeriodicWave()
var real = new Float32Array([1.0, 0.5, 0.25, 0.125]);
var imag = new Float32Array([1.0, 0.7, -1.0, 0.5]);
osc.setPeriodicWave(context.createPeriodicWave(real, imag));
is(osc.type, "custom", "Failed to set custom waveform");
SimpleTest.finish();
});