Bug 803392 - Initial implementation of AudioParam; r=bzbarsky

This patch implements most of the semantics of AudioParam in a
templatized base class called AudioEventTimeline.  This is done in order
to make it possible to test this code from C++ without the need of
linking to libxul.  Basically we take everything that could depend on
libxul or is not suitable for a genertic implementation and put it in a
traits like type as a template argument.  Then the test creates mock
objects that conform to the argument interfaces, and tests the logic of
AudioEventTimeline.
This commit is contained in:
Ehsan Akhgari 2012-10-16 21:19:06 -04:00
parent 972a83c14d
commit 2bcb7f9713
9 changed files with 960 additions and 0 deletions

View File

@ -0,0 +1,374 @@
/* -*- 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/. */
#pragma once
#include "mozilla/Attributes.h"
#include "mozilla/FloatingPoint.h"
#include "nsTArray.h"
#include "math.h"
namespace mozilla {
namespace dom {
/**
* This class will be instantiated with different template arguments for testing and
* production code.
*
* FloatArrayWrapper is a type which satisfies the following:
* - Is copy-constructible.
* - Implements a Data() method returning a float*, representing an array.
* - Implements a Length() method returning a uint32_t, representing the array length.
* - Implements an inited() method returning true for valid objects.
* ErrorResult is a type which satisfies the following:
* - Implements a Throw() method taking an nsresult argument, representing an error code.
*/
template <class FloatArrayWrapper, class ErrorResult>
class AudioEventTimeline
{
private:
struct Event {
enum Type MOZ_ENUM_TYPE(uint32_t) {
SetValue,
LinearRamp,
ExponentialRamp,
SetTarget,
SetValueCurve
};
Event(Type aType, float aTime, float aValue, float aTimeConstant = 0.0,
float aDuration = 0.0, FloatArrayWrapper aCurve = FloatArrayWrapper())
: mType(aType)
, mTime(aTime)
, mValue(aValue)
, mTimeConstant(aTimeConstant)
, mDuration(aDuration)
{
if (aCurve.inited()) {
mCurve = aCurve;
}
}
bool IsValid() const
{
return IsValid(mTime) &&
IsValid(mValue) &&
IsValid(mTimeConstant) &&
IsValid(mDuration);
}
Type mType;
float mTime;
float mValue;
float mTimeConstant;
float mDuration;
FloatArrayWrapper mCurve;
private:
static bool IsValid(float value)
{
return MOZ_DOUBLE_IS_FINITE(value);
}
};
public:
AudioEventTimeline(float aDefaultValue,
float aMinValue,
float aMaxValue)
: mValue(aDefaultValue)
, mDefaultValue(aDefaultValue)
, mMinValue(aMinValue)
, mMaxValue(aMaxValue)
{
MOZ_ASSERT(aDefaultValue >= aMinValue);
MOZ_ASSERT(aDefaultValue <= aMaxValue);
MOZ_ASSERT(aMinValue < aMaxValue);
}
float Value() const
{
// TODO: Return the current value based on the timeline of the AudioContext
return mValue;
}
void SetValue(float aValue)
{
// Silently don't change anything if there are any events
if (mEvents.IsEmpty()) {
mValue = aValue;
}
}
float ComputedValue() const
{
// TODO: implement
return 0;
}
float MinValue() const
{
return mMinValue;
}
float MaxValue() const
{
return mMaxValue;
}
float DefaultValue() const
{
return mDefaultValue;
}
void SetValueAtTime(float aValue, float aStartTime, ErrorResult& aRv)
{
InsertEvent(Event(Event::Type::SetValue, aStartTime, aValue), aRv);
}
void LinearRampToValueAtTime(float aValue, float aEndTime, ErrorResult& aRv)
{
InsertEvent(Event(Event::Type::LinearRamp, aEndTime, aValue), aRv);
}
void ExponentialRampToValueAtTime(float aValue, float aEndTime, ErrorResult& aRv)
{
InsertEvent(Event(Event::Type::ExponentialRamp, aEndTime, aValue), aRv);
}
void SetTargetAtTime(float aTarget, float aStartTime, float aTimeConstant, ErrorResult& aRv)
{
InsertEvent(Event(Event::Type::SetTarget, aStartTime, aTarget, aTimeConstant), aRv);
}
void SetValueCurveAtTime(const FloatArrayWrapper& aValues, float aStartTime, float aDuration, ErrorResult& aRv)
{
// TODO: implement
// InsertEvent(Event(Event::Type::SetValueCurve, aStartTime, 0.0f, 0.0f, aDuration, aValues), aRv);
}
void CancelScheduledValues(float aStartTime)
{
// TODO: implement
}
// This method computes the AudioParam value at a given time based on the event timeline
float GetValueAtTime(float aTime) const
{
const Event* previous = nullptr;
const Event* next = nullptr;
bool bailOut = false;
for (unsigned i = 0; !bailOut && i < mEvents.Length(); ++i) {
switch (mEvents[i].mType) {
case Event::Type::SetValue:
case Event::Type::SetTarget:
case Event::Type::LinearRamp:
case Event::Type::ExponentialRamp:
if (aTime == mEvents[i].mTime) {
// Find the last event with the same time
do {
++i;
} while (i < mEvents.Length() &&
aTime == mEvents[i].mTime);
return mEvents[i - 1].mValue;
}
previous = next;
next = &mEvents[i];
if (aTime < mEvents[i].mTime) {
bailOut = true;
}
break;
case Event::Type::SetValueCurve:
// TODO: implement
break;
default:
MOZ_ASSERT(false, "unreached");
}
}
// Handle the case where the time is past all of the events
if (!bailOut) {
previous = next;
next = nullptr;
}
// Just return the default value if we did not find anything
if (!previous && !next) {
return mValue;
}
// If the requested time is before all of the existing events
if (!previous) {
switch (next->mType) {
case Event::Type::SetValue:
case Event::Type::SetTarget:
// The requested time is before the first event
return mValue;
case Event::Type::LinearRamp:
// Use t=0 as T0 and v=defaultValue as V0
return LinearInterpolate(0.0f, mValue, next->mTime, next->mValue, aTime);
case Event::Type::ExponentialRamp:
// Use t=0 as T0 and v=defaultValue as V0
return ExponentialInterpolate(0.0f, mValue, next->mTime, next->mValue, aTime);
case Event::Type::SetValueCurve:
// TODO: implement
return 0.0f;
}
MOZ_ASSERT(false, "unreached");
}
// SetTarget nodes can be handled no matter what their next node is (if they have one)
if (previous->mType == Event::Type::SetTarget) {
// Follow the curve, without regard to the next node
return ExponentialApproach(previous->mTime, mValue, previous->mValue,
previous->mTimeConstant, aTime);
}
// If the requested time is after all of the existing events
if (!next) {
switch (previous->mType) {
case Event::Type::SetValue:
case Event::Type::LinearRamp:
case Event::Type::ExponentialRamp:
// The value will be constant after the last event
return previous->mValue;
case Event::Type::SetValueCurve:
// TODO: implement
return 0.0f;
case Event::Type::SetTarget:
MOZ_ASSERT(false, "unreached");
}
MOZ_ASSERT(false, "unreached");
}
// Finally, handle the case where we have both a previous and a next event
// First, handle the case where our range ends up in a ramp event
switch (next->mType) {
case Event::Type::LinearRamp:
return LinearInterpolate(previous->mTime, previous->mValue, next->mTime, next->mValue, aTime);
case Event::Type::ExponentialRamp:
return ExponentialInterpolate(previous->mTime, previous->mValue, next->mTime, next->mValue, aTime);
case Event::Type::SetValue:
case Event::Type::SetTarget:
case Event::Type::SetValueCurve:
break;
}
// Now handle all other cases
switch (previous->mType) {
case Event::Type::SetValue:
case Event::Type::LinearRamp:
case Event::Type::ExponentialRamp:
// If the next event type is neither linear or exponential ramp, the
// value is constant.
return previous->mValue;
case Event::Type::SetValueCurve:
// TODO: implement
return 0.0f;
case Event::Type::SetTarget:
MOZ_ASSERT(false, "unreached");
}
MOZ_ASSERT(false, "unreached");
return 0.0f;
}
// Return the number of events scheduled
uint32_t GetEventCount() const
{
return mEvents.Length();
}
static float LinearInterpolate(float t0, float v0, float t1, float v1, float t)
{
return v0 + (v1 - v0) * ((t - t0) / (t1 - t0));
}
static float ExponentialInterpolate(float t0, float v0, float t1, float v1, float t)
{
return v0 * powf(v1 / v0, (t - t0) / (t1 - t0));
}
static float ExponentialApproach(float t0, float v0, float v1, float timeConstant, float t)
{
return v1 + (v0 - v1) * expf(-(t - t0) / timeConstant);
}
private:
void InsertEvent(const Event& aEvent, ErrorResult& aRv)
{
if (!aEvent.IsValid()) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
// Make sure that non-curve events don't fall within the duration of a
// curve event.
for (unsigned i = 0; i < mEvents.Length(); ++i) {
if (mEvents[i].mType == Event::Type::SetValueCurve &&
mEvents[i].mTime <= aEvent.mTime &&
(mEvents[i].mTime + mEvents[i].mDuration) >= aEvent.mTime) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
}
// Make sure that curve events don't fall in a range which includes other
// events.
if (aEvent.mType == Event::Type::SetValueCurve) {
for (unsigned i = 0; i < mEvents.Length(); ++i) {
if (mEvents[i].mTime >= aEvent.mTime &&
mEvents[i].mTime <= (aEvent.mTime + aEvent.mDuration)) {
aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
return;
}
}
}
for (unsigned i = 0; i < mEvents.Length(); ++i) {
if (aEvent.mTime == mEvents[i].mTime) {
if (aEvent.mType == mEvents[i].mType) {
// If times and types are equal, replace the event
mEvents.ReplaceElementAt(i, aEvent);
} else {
// Otherwise, place the element after the last event of another type
do {
++i;
} while (i < mEvents.Length() &&
aEvent.mType != mEvents[i].mType &&
aEvent.mTime == mEvents[i].mTime);
mEvents.InsertElementAt(i, aEvent);
}
return;
}
// Otherwise, place the event right after the latest existing event
if (aEvent.mTime < mEvents[i].mTime) {
mEvents.InsertElementAt(i, aEvent);
return;
}
}
// If we couldn't find a place for the event, just append it to the list
mEvents.AppendElement(aEvent);
}
private:
// This is a sorted array of the events in the timeline. Queries of this
// data structure should probably be more frequent than modifications to it,
// and that is the reason why we're using a simple array as the data structure.
// We can optimize this in the future if the performance of the array ends up
// being a bottleneck.
nsTArray<Event> mEvents;
float mValue;
const float mDefaultValue;
const float mMinValue;
const float mMaxValue;
};
}
}

View File

@ -0,0 +1,54 @@
/* -*- 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 "AudioParam.h"
#include "nsContentUtils.h"
#include "nsIDOMWindow.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/AudioParamBinding.h"
namespace mozilla {
namespace dom {
NS_IMPL_CYCLE_COLLECTION_CLASS(AudioParam)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(AudioParam)
NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mContext)
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER_NATIVE
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(AudioParam)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mContext, AudioContext)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_NATIVE_BEGIN(AudioParam)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioParam, AddRef)
NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioParam, Release)
AudioParam::AudioParam(AudioContext* aContext,
float aDefaultValue,
float aMinValue,
float aMaxValue)
: AudioParamTimeline(aDefaultValue, aMinValue, aMaxValue)
, mContext(aContext)
{
SetIsDOMBinding();
}
AudioParam::~AudioParam()
{
}
JSObject*
AudioParam::WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap)
{
return AudioParamBinding::Wrap(aCx, aScope, this, aTriedToWrap);
}
}
}

View File

@ -0,0 +1,116 @@
/* -*- 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/. */
#pragma once
#include "AudioEventTimeline.h"
#include "nsWrapperCache.h"
#include "nsCycleCollectionParticipant.h"
#include "nsCOMPtr.h"
#include "EnableWebAudioCheck.h"
#include "nsAutoPtr.h"
#include "AudioContext.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/Util.h"
class JSContext;
class nsIDOMWindow;
namespace mozilla {
class ErrorResult;
namespace dom {
namespace detail {
// This class wraps a JS typed array so that AudioEventTimeline does not need
// to know about JSObjects directly.
class FloatArrayWrapper
{
public:
FloatArrayWrapper() // creates an uninitialized object
{
}
FloatArrayWrapper(const Float32Array& array)
{
mArray.construct(array);
}
FloatArrayWrapper(const FloatArrayWrapper& rhs)
{
MOZ_ASSERT(!rhs.mArray.empty());
mArray.construct(rhs.mArray.ref());
}
FloatArrayWrapper& operator=(const FloatArrayWrapper& rhs)
{
MOZ_ASSERT(!rhs.mArray.empty());
mArray.destroyIfConstructed();
mArray.construct(rhs.mArray.ref());
return *this;
}
// Expose the API used by AudioEventTimeline
float* Data() const
{
MOZ_ASSERT(inited());
return mArray.ref().Data();
}
uint32_t Length() const
{
MOZ_ASSERT(inited());
return mArray.ref().Length();
}
bool inited() const
{
return !mArray.empty();
}
private:
Maybe<Float32Array> mArray;
};
}
typedef AudioEventTimeline<detail::FloatArrayWrapper, ErrorResult> AudioParamTimeline;
class AudioParam MOZ_FINAL : public nsWrapperCache,
public EnableWebAudioCheck,
public AudioParamTimeline
{
public:
AudioParam(AudioContext* aContext,
float aDefaultValue,
float aMinValue,
float aMaxValue);
virtual ~AudioParam();
NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AudioParam)
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AudioParam)
AudioContext* GetParentObject() const
{
return mContext;
}
virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
bool* aTriedToWrap);
// We override SetValueCurveAtTime to convert the Float32Array to the wrapper
// object.
void SetValueCurveAtTime(JSContext* cx, const Float32Array& aValues, float aStartTime, float aDuration, ErrorResult& aRv)
{
AudioParamTimeline::SetValueCurveAtTime(detail::FloatArrayWrapper(aValues),
aStartTime, aDuration, aRv);
}
private:
nsRefPtr<AudioContext> mContext;
};
}
}

View File

@ -20,6 +20,7 @@ CPPSRCS := \
AudioContext.cpp \ AudioContext.cpp \
AudioDestinationNode.cpp \ AudioDestinationNode.cpp \
AudioNode.cpp \ AudioNode.cpp \
AudioParam.cpp \
AudioSourceNode.cpp \ AudioSourceNode.cpp \
EnableWebAudioCheck.cpp \ EnableWebAudioCheck.cpp \
$(NULL) $(NULL)
@ -30,11 +31,16 @@ EXPORTS_mozilla/dom := \
AudioBufferSourceNode.h \ AudioBufferSourceNode.h \
AudioDestinationNode.h \ AudioDestinationNode.h \
AudioNode.h \ AudioNode.h \
AudioParam.h \
AudioSourceNode.h \ AudioSourceNode.h \
$(NULL) $(NULL)
PARALLEL_DIRS := test PARALLEL_DIRS := test
ifdef ENABLE_TESTS
TOOL_DIRS += compiledtest
endif
FORCE_STATIC_LIB := 1 FORCE_STATIC_LIB := 1
include $(topsrcdir)/config/rules.mk include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,19 @@
# 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/.
DEPTH := @DEPTH@
topsrcdir := @top_srcdir@
srcdir := @srcdir@
VPATH := @srcdir@
relativesrcdir := @relativesrcdir@
include $(DEPTH)/config/autoconf.mk
LOCAL_INCLUDES := -I$(srcdir)/..
CPP_UNIT_TESTS := \
TestAudioEventTimeline.cpp \
$(NULL)
include $(topsrcdir)/config/rules.mk

View File

@ -0,0 +1,343 @@
/* -*- 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 "AudioEventTimeline.h"
#include "TestHarness.h"
#include <sstream>
#include <limits>
using namespace mozilla::dom;
using std::numeric_limits;
// Some simple testing primitives
void ok(bool val, const char* msg)
{
if (val) {
passed(msg);
} else {
fail(msg);
}
}
namespace std {
template <class T>
basic_ostream<T, char_traits<T> >&
operator<<(basic_ostream<T, char_traits<T> >& os, nsresult rv)
{
os << static_cast<uint32_t>(rv);
return os;
}
}
template <class T, class U>
void is(const T& a, const U& b, const char* msg)
{
std::stringstream ss;
ss << msg << ", Got: " << a << ", expected: " << b;
ok(a == b, ss.str().c_str());
}
template <>
void is(const float& a, const float& b, const char* msg)
{
// stupidly high, since we mostly care about the correctness of the algorithm
const float kEpsilon = 0.00001f;
std::stringstream ss;
ss << msg << ", Got: " << a << ", expected: " << b;
ok(fabsf(a - b) < kEpsilon, ss.str().c_str());
}
class FloatArrayMock
{
public:
// This implementation is not used for now, so let's just return dummy values.
float* Data() const
{
return nullptr;
}
uint32_t Length() const
{
return 0;
}
bool inited() const
{
return true;
}
};
class ErrorResultMock
{
public:
ErrorResultMock()
: mRv(NS_OK)
{
}
void Throw(nsresult aRv)
{
mRv = aRv;
}
operator nsresult() const
{
return mRv;
}
private:
nsresult mRv;
};
typedef AudioEventTimeline<FloatArrayMock, ErrorResultMock> Timeline;
void TestSpecExample()
{
// First, run the basic tests
Timeline timeline(10.0f, .1f, 20.0f);
is(timeline.DefaultValue(), 10.0f, "Correct default value returned");
is(timeline.MinValue(), .1f, "Correct min value returned");
is(timeline.MaxValue(), 20.0f, "Correct max value returned");
ErrorResultMock rv;
// This test is copied from the example in the Web Audio spec
const float t0 = 0.0f,
t1 = 0.1f,
t2 = 0.2f,
t3 = 0.3f,
t4 = 0.4f,
t5 = 0.6f,
t6 = 0.7f/*,
t7 = 1.0f*/;
timeline.SetValueAtTime(0.2f, t0, rv);
is(rv, NS_OK, "SetValueAtTime succeeded");
timeline.SetValueAtTime(0.3f, t1, rv);
is(rv, NS_OK, "SetValueAtTime succeeded");
timeline.SetValueAtTime(0.4f, t2, rv);
is(rv, NS_OK, "SetValueAtTime succeeded");
timeline.LinearRampToValueAtTime(1.0f, t3, rv);
is(rv, NS_OK, "LinearRampToValueAtTime succeeded");
timeline.LinearRampToValueAtTime(0.15f, t4, rv);
is(rv, NS_OK, "LinearRampToValueAtTime succeeded");
timeline.ExponentialRampToValueAtTime(0.75f, t5, rv);
is(rv, NS_OK, "ExponentialRampToValueAtTime succeeded");
timeline.ExponentialRampToValueAtTime(0.05f, t6, rv);
is(rv, NS_OK, "ExponentialRampToValueAtTime succeeded");
// TODO: Add the SetValueCurveAtTime test
is(timeline.GetValueAtTime(0.0f), 0.2f, "Correct value");
is(timeline.GetValueAtTime(0.05f), 0.2f, "Correct value");
is(timeline.GetValueAtTime(0.1f), 0.3f, "Correct value");
is(timeline.GetValueAtTime(0.15f), 0.3f, "Correct value");
is(timeline.GetValueAtTime(0.2f), 0.4f, "Correct value");
is(timeline.GetValueAtTime(0.25f), (0.4f + 1.0f) / 2, "Correct value");
is(timeline.GetValueAtTime(0.3f), 1.0f, "Correct value");
is(timeline.GetValueAtTime(0.35f), (1.0f + 0.15f) / 2, "Correct value");
is(timeline.GetValueAtTime(0.4f), 0.15f, "Correct value");
is(timeline.GetValueAtTime(0.45f), (0.15f * powf(0.75f / 0.15f, 0.05f / 0.2f)), "Correct value");
is(timeline.GetValueAtTime(0.5f), (0.15f * powf(0.75f / 0.15f, 0.5f)), "Correct value");
is(timeline.GetValueAtTime(0.55f), (0.15f * powf(0.75f / 0.15f, 0.15f / 0.2f)), "Correct value");
is(timeline.GetValueAtTime(0.6f), 0.75f, "Correct value");
is(timeline.GetValueAtTime(0.65f), (0.75f * powf(0.05 / 0.75f, 0.5f)), "Correct value");
is(timeline.GetValueAtTime(0.7f), 0.05f, "Correct value");
is(timeline.GetValueAtTime(1.0f), 0.05f, "Correct value");
}
void TestInvalidEvents()
{
MOZ_STATIC_ASSERT(numeric_limits<float>::has_quiet_NaN, "Platform must have a quiet NaN");
const float NaN = numeric_limits<float>::quiet_NaN();
const float Infinity = numeric_limits<float>::infinity();
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
timeline.SetValueAtTime(NaN, 0.1f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.SetValueAtTime(Infinity, 0.1f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.SetValueAtTime(-Infinity, 0.1f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.LinearRampToValueAtTime(NaN, 0.2f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.LinearRampToValueAtTime(Infinity, 0.2f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.LinearRampToValueAtTime(-Infinity, 0.2f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.ExponentialRampToValueAtTime(NaN, 0.3f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.ExponentialRampToValueAtTime(Infinity, 0.3f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.ExponentialRampToValueAtTime(-Infinity, 0.4f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.SetTargetAtTime(NaN, 0.4f, 1.0f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.SetTargetAtTime(Infinity, 0.4f, 1.0f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.SetTargetAtTime(-Infinity, 0.4f, 1.0f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.SetTargetAtTime(0.4f, NaN, 1.0f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.SetTargetAtTime(0.4f, Infinity, 1.0f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
timeline.SetTargetAtTime(0.4f, -Infinity, 1.0f, rv);
is(rv, NS_ERROR_DOM_SYNTAX_ERR, "Correct error code returned");
// TODO: Test SetValueCurveAtTime
}
void TestEventReplacement()
{
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
is(timeline.GetEventCount(), 0, "No events yet");
timeline.SetValueAtTime(10.0f, 0.1f, rv);
is(timeline.GetEventCount(), 1, "One event scheduled now");
timeline.SetValueAtTime(20.0f, 0.1f, rv);
is(rv, NS_OK, "Event scheduling should be successful");
is(timeline.GetEventCount(), 1, "Event should be replaced");
is(timeline.GetValueAtTime(0.1f), 20.0f, "The first event should be overwritten");
timeline.LinearRampToValueAtTime(30.0f, 0.1f, rv);
is(rv, NS_OK, "Event scheduling should be successful");
is(timeline.GetEventCount(), 2, "Different event type should be appended");
is(timeline.GetValueAtTime(0.1f), 30.0f, "The first event should be overwritten");
}
void TestBeforeFirstEvent()
{
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
timeline.SetValueAtTime(20.0f, 1.0f, rv);
is(timeline.GetValueAtTime(0.5f), 10.0f, "Retrun the default value before the first event");
}
void TestAfterLastValueEvent()
{
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
timeline.SetValueAtTime(20.0f, 1.0f, rv);
is(timeline.GetValueAtTime(1.5f), 20.0f, "Return the last value after the last SetValue event");
}
void TestAfterLastTargetValueEvent()
{
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
timeline.SetTargetAtTime(20.0f, 1.0f, 5.0f, rv);
is(timeline.GetValueAtTime(10.f), (20.f + (10.f - 20.f) * expf(-9.0f / 5.0f)), "Return the value after the last SetTarget event based on the curve");
}
void TestAfterLastTargetValueEventWithValueSet()
{
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
timeline.SetValue(50.f);
timeline.SetTargetAtTime(20.0f, 1.0f, 5.0f, rv);
is(timeline.GetValueAtTime(10.f), (20.f + (50.f - 20.f) * expf(-9.0f / 5.0f)), "Return the value after SetValue and the last SetTarget event based on the curve");
}
void TestValue()
{
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
is(timeline.Value(), 10.0f, "value should initially match the default value");
timeline.SetValue(20.0f);
is(timeline.Value(), 20.0f, "Should be able to set the value");
timeline.SetValueAtTime(20.0f, 1.0f, rv);
// TODO: The following check needs to change when we compute the value based on the current time of the context
is(timeline.Value(), 20.0f, "TODO...");
timeline.SetValue(30.0f);
is(timeline.Value(), 20.0f, "Should not be able to set the value");
}
void TestLinearRampAtZero()
{
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
timeline.LinearRampToValueAtTime(20.0f, 0.0f, rv);
is(timeline.GetValueAtTime(0.0f), 20.0f, "Should get the correct value when t0 == t1 == 0");
}
void TestExponentialRampAtZero()
{
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
timeline.ExponentialRampToValueAtTime(20.0f, 0.0f, rv);
is(timeline.GetValueAtTime(0.0f), 20.0f, "Should get the correct value when t0 == t1 == 0");
}
void TestLinearRampAtSameTime()
{
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
timeline.SetValueAtTime(5.0f, 1.0f, rv);
timeline.LinearRampToValueAtTime(20.0f, 1.0f, rv);
is(timeline.GetValueAtTime(1.0f), 20.0f, "Should get the correct value when t0 == t1");
}
void TestExponentialRampAtSameTime()
{
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
timeline.SetValueAtTime(5.0f, 1.0f, rv);
timeline.ExponentialRampToValueAtTime(20.0f, 1.0f, rv);
is(timeline.GetValueAtTime(1.0f), 20.0f, "Should get the correct value when t0 == t1");
}
void TestSetTargetZeroTimeConstant()
{
Timeline timeline(10.0f, .1f, 20.0f);
ErrorResultMock rv;
timeline.SetTargetAtTime(20.0f, 1.0f, 0.0f, rv);
is(timeline.GetValueAtTime(10.f), 20.f, "Should get the correct value with timeConstant == 0");
}
int main()
{
ScopedXPCOM xpcom("TestAudioEventTimeline");
if (xpcom.failed()) {
return 1;
}
TestSpecExample();
TestInvalidEvents();
TestEventReplacement();
TestBeforeFirstEvent();
TestAfterLastValueEvent();
TestAfterLastTargetValueEvent();
TestAfterLastTargetValueEventWithValueSet();
TestValue();
TestLinearRampAtZero();
TestExponentialRampAtZero();
TestLinearRampAtSameTime();
TestExponentialRampAtSameTime();
TestSetTargetZeroTimeConstant();
return gFailCount > 0;
}

View File

@ -96,6 +96,10 @@ DOMInterfaces = {
'concrete': False, 'concrete': False,
}, },
'AudioParam' : {
'nativeOwnership': 'refcounted'
},
'AudioSourceNode': { 'AudioSourceNode': {
'concrete': False, 'concrete': False,
}, },

View File

@ -0,0 +1,43 @@
/* -*- 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 AudioParam {
attribute float value;
// readonly attribute float computedValue;
readonly attribute float minValue;
readonly attribute float maxValue;
readonly attribute float defaultValue;
// Parameter automation.
[Throws]
void setValueAtTime(float value, float startTime);
[Throws]
void linearRampToValueAtTime(float value, float endTime);
[Throws]
void exponentialRampToValueAtTime(float value, float endTime);
// Exponentially approach the target value with a rate having the given time constant.
[Throws]
void setTargetAtTime(float target, float startTime, float timeConstant);
// Sets an array of arbitrary parameter values starting at time for the given duration.
// The number of values will be scaled to fit into the desired duration.
// [Throws]
// void setValueCurveAtTime(Float32Array values, float startTime, float duration);
// Cancels all scheduled parameter changes with times greater than or equal to startTime.
// void cancelScheduledValues(float startTime);
};

View File

@ -14,6 +14,7 @@ webidl_files = \
AudioContext.webidl \ AudioContext.webidl \
AudioDestinationNode.webidl \ AudioDestinationNode.webidl \
AudioNode.webidl \ AudioNode.webidl \
AudioParam.webidl \
AudioSourceNode.webidl \ AudioSourceNode.webidl \
Blob.webidl \ Blob.webidl \
CanvasRenderingContext2D.webidl \ CanvasRenderingContext2D.webidl \