mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 03:45:46 +00:00
Bug 865251 - Implement WaveShaperNode; r=roc
This commit is contained in:
parent
8e2172bf4e
commit
5cba21896b
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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()
|
||||
{
|
||||
|
@ -36,6 +36,7 @@ CPPSRCS := \
|
||||
PannerNode.cpp \
|
||||
ScriptProcessorNode.cpp \
|
||||
ThreeDPoint.cpp \
|
||||
WaveShaperNode.cpp \
|
||||
WebAudioUtils.cpp \
|
||||
$(NULL)
|
||||
|
||||
|
140
content/media/webaudio/WaveShaperNode.cpp
Normal file
140
content/media/webaudio/WaveShaperNode.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
46
content/media/webaudio/WaveShaperNode.h
Normal file
46
content/media/webaudio/WaveShaperNode.h
Normal 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
|
@ -36,5 +36,6 @@ EXPORTS.mozilla.dom += [
|
||||
'GainNode.h',
|
||||
'PannerNode.h',
|
||||
'ScriptProcessorNode.h',
|
||||
'WaveShaperNode.h',
|
||||
]
|
||||
|
||||
|
@ -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 \
|
||||
|
60
content/media/webaudio/test/test_waveShaper.html
Normal file
60
content/media/webaudio/test/test_waveShaper.html
Normal 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>
|
43
content/media/webaudio/test/test_waveShaperNoCurve.html
Normal file
43
content/media/webaudio/test/test_waveShaperNoCurve.html
Normal 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>
|
@ -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>
|
@ -49,6 +49,8 @@ interface AudioContext : EventTarget {
|
||||
[Creator]
|
||||
BiquadFilterNode createBiquadFilter();
|
||||
[Creator]
|
||||
WaveShaperNode createWaveShaper();
|
||||
[Creator]
|
||||
PannerNode createPanner();
|
||||
|
||||
[Creator, Throws]
|
||||
|
19
dom/webidl/WaveShaperNode.webidl
Normal file
19
dom/webidl/WaveShaperNode.webidl
Normal 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;
|
||||
|
||||
};
|
||||
|
@ -315,6 +315,7 @@ webidl_files = \
|
||||
URLUtils.webidl \
|
||||
USSDReceivedEvent.webidl \
|
||||
VideoStreamTrack.webidl \
|
||||
WaveShaperNode.webidl \
|
||||
Window.webidl \
|
||||
XMLDocument.webidl \
|
||||
XMLHttpRequest.webidl \
|
||||
|
Loading…
Reference in New Issue
Block a user