Bug 865251 - Implement WaveShaperNode; r=roc

This commit is contained in:
Ehsan Akhgari 2013-05-14 00:12:30 -04:00
parent 8e2172bf4e
commit 5cba21896b
16 changed files with 400 additions and 0 deletions

View File

@ -188,6 +188,11 @@ public:
{
NS_ERROR("SetBuffer called on engine that doesn't support it");
}
// This consumes the contents of aData. aData will be emptied after this returns.
virtual void SetRawArrayData(nsTArray<float>& aData)
{
NS_ERROR("SetRawArrayData called on an engine that doesn't support it");
}
/**
* Produce the next block of audio samples, given input samples aInput

View File

@ -159,6 +159,28 @@ AudioNodeStream::SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aB
GraphImpl()->AppendMessage(new Message(this, aBuffer));
}
void
AudioNodeStream::SetRawArrayData(nsTArray<float>& aData)
{
class Message : public ControlMessage {
public:
Message(AudioNodeStream* aStream,
nsTArray<float>& aData)
: ControlMessage(aStream)
{
mData.SwapElements(aData);
}
virtual void Run()
{
static_cast<AudioNodeStream*>(mStream)->Engine()->SetRawArrayData(mData);
}
nsTArray<float> mData;
};
MOZ_ASSERT(this);
GraphImpl()->AppendMessage(new Message(this, aData));
}
void
AudioNodeStream::SetChannelMixingParameters(uint32_t aNumberOfChannels,
ChannelCountMode aChannelCountMode,

View File

@ -75,6 +75,8 @@ public:
void SetTimelineParameter(uint32_t aIndex, const dom::AudioParamTimeline& aValue);
void SetThreeDPointParameter(uint32_t aIndex, const dom::ThreeDPoint& aValue);
void SetBuffer(already_AddRefed<ThreadSharedFloatArrayBufferList> aBuffer);
// This consumes the contents of aData. aData will be emptied after this returns.
void SetRawArrayData(nsTArray<float>& aData);
void SetChannelMixingParameters(uint32_t aNumberOfChannels,
dom::ChannelCountMode aChannelCountMoe,
dom::ChannelInterpretation aChannelInterpretation);

View File

@ -22,6 +22,7 @@
#include "ScriptProcessorNode.h"
#include "ChannelMergerNode.h"
#include "ChannelSplitterNode.h"
#include "WaveShaperNode.h"
#include "nsNetUtil.h"
// Note that this number is an arbitrary large value to protect against OOM
@ -195,6 +196,13 @@ AudioContext::CreateGain()
return gainNode.forget();
}
already_AddRefed<WaveShaperNode>
AudioContext::CreateWaveShaper()
{
nsRefPtr<WaveShaperNode> waveShaperNode = new WaveShaperNode(this);
return waveShaperNode.forget();
}
already_AddRefed<DelayNode>
AudioContext::CreateDelay(double aMaxDelayTime, ErrorResult& aRv)
{

View File

@ -52,6 +52,7 @@ class GainNode;
class GlobalObject;
class PannerNode;
class ScriptProcessorNode;
class WaveShaperNode;
class AudioContext MOZ_FINAL : public nsDOMEventTargetHelper,
public EnableWebAudioCheck
@ -126,6 +127,9 @@ public:
already_AddRefed<GainNode>
CreateGain();
already_AddRefed<WaveShaperNode>
CreateWaveShaper();
already_AddRefed<GainNode>
CreateGainNode()
{

View File

@ -36,6 +36,7 @@ CPPSRCS := \
PannerNode.cpp \
ScriptProcessorNode.cpp \
ThreeDPoint.cpp \
WaveShaperNode.cpp \
WebAudioUtils.cpp \
$(NULL)

View File

@ -0,0 +1,140 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "WaveShaperNode.h"
#include "mozilla/dom/WaveShaperNodeBinding.h"
#include "AudioNode.h"
#include "AudioNodeEngine.h"
#include "AudioNodeStream.h"
#include "mozilla/PodOperations.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(WaveShaperNode, AudioNode)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
tmp->ClearCurve();
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(WaveShaperNode, AudioNode)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(WaveShaperNode)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCurve)
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(WaveShaperNode)
NS_INTERFACE_MAP_END_INHERITING(AudioNode)
NS_IMPL_ADDREF_INHERITED(WaveShaperNode, AudioNode)
NS_IMPL_RELEASE_INHERITED(WaveShaperNode, AudioNode)
class WaveShaperNodeEngine : public AudioNodeEngine
{
public:
explicit WaveShaperNodeEngine(AudioNode* aNode)
: AudioNodeEngine(aNode)
{
}
virtual void SetRawArrayData(nsTArray<float>& aCurve) MOZ_OVERRIDE
{
mCurve.SwapElements(aCurve);
}
virtual void ProduceAudioBlock(AudioNodeStream* aStream,
const AudioChunk& aInput,
AudioChunk* aOutput,
bool* aFinished)
{
uint32_t channelCount = aInput.mChannelData.Length();
if (!mCurve.Length() || !channelCount) {
// Optimize the case where we don't have a curve buffer,
// or the input is null.
*aOutput = aInput;
return;
}
AllocateAudioBlock(channelCount, aOutput);
for (uint32_t i = 0; i < channelCount; ++i) {
const float* inputBuffer = static_cast<const float*>(aInput.mChannelData[i]);
float* outputBuffer = const_cast<float*> (static_cast<const float*>(aOutput->mChannelData[i]));
for (uint32_t j = 0; j < WEBAUDIO_BLOCK_SIZE; ++j) {
// Index into the curve array based on the amplitude of the
// incoming signal by clamping the amplitude to [-1, 1] and
// performing a linear interpolation of the neighbor values.
float index = std::max(0.0f, std::min(float(mCurve.Length() - 1),
mCurve.Length() * (inputBuffer[j] + 1) / 2));
uint32_t indexLower = uint32_t(index);
uint32_t indexHigher = uint32_t(index + 1.0f);
if (indexHigher == mCurve.Length()) {
outputBuffer[j] = mCurve[indexLower];
} else {
float interpolationFactor = index - indexLower;
outputBuffer[j] = (1.0f - interpolationFactor) * mCurve[indexLower] +
interpolationFactor * mCurve[indexHigher];
}
}
}
}
private:
nsTArray<float> mCurve;
};
WaveShaperNode::WaveShaperNode(AudioContext* aContext)
: AudioNode(aContext,
2,
ChannelCountMode::Max,
ChannelInterpretation::Speakers)
, mCurve(nullptr)
{
NS_HOLD_JS_OBJECTS(this, WaveShaperNode);
WaveShaperNodeEngine* engine = new WaveShaperNodeEngine(this);
mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
}
WaveShaperNode::~WaveShaperNode()
{
ClearCurve();
}
void
WaveShaperNode::ClearCurve()
{
mCurve = nullptr;
NS_DROP_JS_OBJECTS(this, WaveShaperNode);
}
JSObject*
WaveShaperNode::WrapObject(JSContext *aCx, JS::Handle<JSObject*> aScope)
{
return WaveShaperNodeBinding::Wrap(aCx, aScope, this);
}
void
WaveShaperNode::SetCurve(const Float32Array* aCurve)
{
nsTArray<float> curve;
if (aCurve) {
mCurve = aCurve->Obj();
curve.SetLength(aCurve->Length());
PodCopy(curve.Elements(), aCurve->Data(), aCurve->Length());
} else {
mCurve = nullptr;
}
AudioNodeStream* ns = static_cast<AudioNodeStream*>(mStream.get());
MOZ_ASSERT(ns, "Why don't we have a stream here?");
ns->SetRawArrayData(curve);
}
}
}

View File

@ -0,0 +1,46 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef WaveShaperNode_h_
#define WaveShaperNode_h_
#include "AudioNode.h"
#include "AudioParam.h"
namespace mozilla {
namespace dom {
class AudioContext;
class WaveShaperNode : public AudioNode
{
public:
explicit WaveShaperNode(AudioContext *aContext);
virtual ~WaveShaperNode();
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(WaveShaperNode, AudioNode)
virtual JSObject* WrapObject(JSContext *aCx,
JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
JSObject* GetCurve(JSContext* aCx) const
{
return mCurve;
}
void SetCurve(const Float32Array* aData);
private:
void ClearCurve();
private:
JSObject* mCurve;
};
}
}
#endif

View File

@ -36,5 +36,6 @@ EXPORTS.mozilla.dom += [
'GainNode.h',
'PannerNode.h',
'ScriptProcessorNode.h',
'WaveShaperNode.h',
]

View File

@ -61,6 +61,9 @@ MOCHITEST_FILES := \
test_scriptProcessorNode.html \
test_scriptProcessorNodeChannelCount.html \
test_singleSourceDest.html \
test_waveShaper.html \
test_waveShaperNoCurve.html \
test_waveShaperZeroLengthCurve.html \
ting.ogg \
ting-expected.wav \
ting-dualchannel44.1.ogg \

View File

@ -0,0 +1,60 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test WaveShaperNode with no curve</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">
var gTest = {
length: 4096,
numberOfChannels: 1,
createGraph: function(context) {
var source = context.createBufferSource();
source.buffer = this.buffer;
var shaper = context.createWaveShaper();
shaper.curve = this.curve;
source.connect(shaper);
source.start(0);
return shaper;
},
createExpectedBuffers: function(context) {
this.buffer = context.createBuffer(1, 4096, context.sampleRate);
for (var i = 1; i < 4095; ++i) {
this.buffer.getChannelData(0)[i] = 2 * (i / 4096) - 1;
}
// Two out of range values
this.buffer.getChannelData(0)[0] = -2;
this.buffer.getChannelData(0)[4095] = 2;
this.curve = new Float32Array(2048);
for (var i = 0; i < 2048; ++i) {
this.curve[i] = Math.sin(100 * Math.PI * (i + 1) / context.sampleRate);
}
var expectedBuffer = context.createBuffer(1, 4096, context.sampleRate);
for (var i = 1; i < 4095; ++i) {
var input = this.buffer.getChannelData(0)[i];
var index = Math.floor(this.curve.length * (input + 1) / 2);
index = Math.max(0, Math.min(this.curve.length - 1, index));
expectedBuffer.getChannelData(0)[i] = this.curve[index];
}
expectedBuffer.getChannelData(0)[0] = this.curve[0];
expectedBuffer.getChannelData(0)[4095] = this.curve[2047];
return expectedBuffer;
},
};
runTest();
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,43 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test WaveShaperNode with no curve</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">
var gTest = {
length: 2048,
numberOfChannels: 1,
createGraph: function(context) {
var source = context.createBufferSource();
source.buffer = this.buffer;
var shaper = context.createWaveShaper();
is(shaper.curve, null, "The shaper curve must be null by default");
source.connect(shaper);
source.start(0);
return shaper;
},
createExpectedBuffers: function(context) {
var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
for (var i = 0; i < 2048; ++i) {
expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
}
this.buffer = expectedBuffer;
return expectedBuffer;
},
};
runTest();
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,43 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test WaveShaperNode with no curve</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">
var gTest = {
length: 2048,
numberOfChannels: 1,
createGraph: function(context) {
var source = context.createBufferSource();
source.buffer = this.buffer;
var shaper = context.createWaveShaper();
shaper.curve = new Float32Array(0);
source.connect(shaper);
source.start(0);
return shaper;
},
createExpectedBuffers: function(context) {
var expectedBuffer = context.createBuffer(1, 2048, context.sampleRate);
for (var i = 0; i < 2048; ++i) {
expectedBuffer.getChannelData(0)[i] = Math.sin(440 * 2 * Math.PI * i / context.sampleRate);
}
this.buffer = expectedBuffer;
return expectedBuffer;
},
};
runTest();
</script>
</pre>
</body>
</html>

View File

@ -49,6 +49,8 @@ interface AudioContext : EventTarget {
[Creator]
BiquadFilterNode createBiquadFilter();
[Creator]
WaveShaperNode createWaveShaper();
[Creator]
PannerNode createPanner();
[Creator, Throws]

View File

@ -0,0 +1,19 @@
/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/.
*
* The origin of this IDL file is
* https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html
*
* Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
* liability, trademark and document use rules apply.
*/
[PrefControlled]
interface WaveShaperNode : AudioNode {
attribute Float32Array? curve;
};

View File

@ -315,6 +315,7 @@ webidl_files = \
URLUtils.webidl \
USSDReceivedEvent.webidl \
VideoStreamTrack.webidl \
WaveShaperNode.webidl \
Window.webidl \
XMLDocument.webidl \
XMLHttpRequest.webidl \