Commit 02938fe2 authored by Raymond Toy's avatar Raymond Toy Committed by Commit Bot

Value setter must call setValueAtTime

When using the AudioParam value setter, the setter must behave as if
setValueAtTime were called with the given value at the current audio
context time.  This includes throwing an error if the time is in the
middle of a setValueCurveAtTime automation.

This is a change in the old behavior where errors weren't thrown.
This also clarifies the behavior of the value setter when automations
are running.  Previously, the interaction between the setter and
automations were not very well specified.

Bug: 764396
Test: AudioParam/audioparam-value-setter-error.html
Change-Id: Ic1da32c27f9e4f29e2df3bcbb6dd0bd485409e0e
Reviewed-on: https://chromium-review.googlesource.com/665318
Commit-Queue: Raymond Toy <rtoy@chromium.org>
Reviewed-by: default avatarHongchan Choi <hongchan@chromium.org>
Cr-Commit-Position: refs/heads/master@{#534737}
parent 45d52b4f
CONSOLE WARNING: line 13: An AudioContext in a cross origin iframe must be created or resumed from a user gesture to enable audio output.
CONSOLE WARNING: line 23: An AudioContext in a cross origin iframe must be created or resumed from a user gesture to enable audio output.
CONSOLE WARNING: line 30: AudioParam value setter will become equivalent to AudioParam.setValueAtTime() in M65, around March 2018 See https://webaudio.github.io/web-audio-api/#dom-audioparam-value for more details.
CONSOLE WARNING: line 36: An AudioContext in a cross origin iframe must be created or resumed from a user gesture to enable audio output.
CONSOLE ERROR: line 2676: Uncaught Error: assert_equals: stateAfterClick expected "running" but got "suspended"
This is a testharness.js-based test.
......
<!doctype html>
<html>
<head>
<title>
Test AudioListener.setPosition and AudioListener.setOrientation Errors
</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../resources/audit-util.js"></script>
<script src="../resources/audit.js"></script>
<script src="../resources/set-position-vs-curve-test.js"></script>
</head>
<body>
<script id="layout-test-code">
// Fairly arbitrary rate
let sampleRate = 16000;
// For the tests we need to render for at least two render quanta.
// Otherwise, pretty arbitrary.
let renderFrames = 256;
let renderDuration = renderFrames / sampleRate;
// The curve duration for the test. Anything less than one render quantum
// is fine. Arbitrarily choose something small.
let curveDurationFrames = 8;
let curveDuration = curveDurationFrames / sampleRate;
// When to call setPosition, after the setValueCurve has ended. Any value
// after the end of the first render quantum is fine.
let suspendFrame = 129;
let audit = Audit.createTaskRunner();
// Array of tests to do. Each element of this array is used to create a
// task to test the entry.
let tests = [
// Test |setPosition| against |positionX|, |positionY|, and |positionZ|
// setValueCurves. Include test where there's overlap and where there
// isn't.
{
name: 'Listener setPosition X error',
options: {paramName: 'positionX', curveDuration: renderDuration}
},
{
name: 'Listener setPosition X no error',
options: {
paramName: 'positionX',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
{
name: 'Listener setPosition Y error',
options: {paramName: 'positionY', curveDuration: renderDuration}
},
{
name: 'Listener setPosition Y no error',
options: {
paramName: 'positionY',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
{
name: 'Listener setPosition Z error',
options: {paramName: 'positionZ', curveDuration: renderDuration}
},
{
name: 'Listener setPosition Z no error',
options: {
paramName: 'positionZ',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
// Now do the same with |setOrientation|, for forward and up vectors.
{
name: 'Listener setOrientation forward X error',
options: {paramName: 'forwardX', curveDuration: renderDuration}
},
{
name: 'Listener setOrientation forward X no error',
options: {
paramName: 'forwardX',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
{
name: 'Listener setOrientation forward Y error',
options: {paramName: 'forwardY', curveDuration: renderDuration}
},
{
name: 'Listener setOrientation forward Y no error',
options: {
paramName: 'forwardY',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
{
name: 'Listener setOrientation forward Z error',
options: {paramName: 'forwardZ', curveDuration: renderDuration}
},
{
name: 'Listener setOrientation forward Z no error',
options: {
paramName: 'forwardZ',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
{
name: 'Listener setOrientation up X error',
options: {paramName: 'upX', curveDuration: renderDuration}
},
{
name: 'Listener setOrientation up X no error',
options: {
paramName: 'upX',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
{
name: 'Listener setOrientation up Y error',
options: {paramName: 'upY', curveDuration: renderDuration}
},
{
name: 'Listener setOrientation up Y no error',
options: {
paramName: 'upY',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
{
name: 'Listener setOrientation up Z error',
options: {paramName: 'upZ', curveDuration: renderDuration}
},
{
name: 'Listener setOrientation up Z no error',
options: {
paramName: 'upZ',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
];
// Create an audit test for each entry in |tests|.
tests.forEach(test => {
audit.define(test.name, (task, should) => {
let context = new OfflineAudioContext(1, renderFrames, sampleRate);
testPositionSetterVsCurve(
should, context,
Object.assign(
{testName: test.name, nodeName: 'listener'}, test.options))
.then(() => task.done());
});
});
audit.run();
</script>
</body>
</html>
......@@ -137,9 +137,11 @@
source.buffer = createConstantBuffer(context, 1, 1);
source.loop = true;
// Automation is applied to a gain node
let gain = context.createGain();
gain.gain.value = v0;
// Automation is applied to a gain node. Set the initial value of the
// gain in the constructor; using the value setter does an implicit
// setValueAtTime which sets an initial event. This defeats the purpose
// of the test.
let gain = new GainNode(context, {gain: v0});
// Delay start of automation, if requested
if (delay) {
......
CONSOLE WARNING: line 371: AudioParam value setter will become equivalent to AudioParam.setValueAtTime() in M65, around March 2018 See https://webaudio.github.io/web-audio-api/#dom-audioparam-value for more details.
CONSOLE WARNING: line 371: Delay.delayTime.value -1 outside nominal range [0, 1.5]; value will be clamped.
CONSOLE WARNING: line 371: Delay.delayTime.setValueAtTime value -1 outside nominal range [0, 1.5]; value will be clamped.
CONSOLE WARNING: line 383: Delay.delayTime.value 4 outside nominal range [0, 1.5]; value will be clamped.
CONSOLE WARNING: line 383: Delay.delayTime.setValueAtTime value 4 outside nominal range [0, 1.5]; value will be clamped.
CONSOLE WARNING: line 371: StereoPanner.pan.value -3 outside nominal range [-1, 1]; value will be clamped.
CONSOLE WARNING: line 371: StereoPanner.pan.setValueAtTime value -3 outside nominal range [-1, 1]; value will be clamped.
CONSOLE WARNING: line 383: StereoPanner.pan.value 3 outside nominal range [-1, 1]; value will be clamped.
CONSOLE WARNING: line 383: StereoPanner.pan.setValueAtTime value 3 outside nominal range [-1, 1]; value will be clamped.
CONSOLE WARNING: line 371: DynamicsCompressor.threshold.value -201 outside nominal range [-100, 0]; value will be clamped.
CONSOLE WARNING: line 371: DynamicsCompressor.threshold.setValueAtTime value -201 outside nominal range [-100, 0]; value will be clamped.
CONSOLE WARNING: line 383: DynamicsCompressor.threshold.value 1 outside nominal range [-100, 0]; value will be clamped.
CONSOLE WARNING: line 383: DynamicsCompressor.threshold.setValueAtTime value 1 outside nominal range [-100, 0]; value will be clamped.
CONSOLE WARNING: line 371: DynamicsCompressor.knee.value -1 outside nominal range [0, 40]; value will be clamped.
CONSOLE WARNING: line 371: DynamicsCompressor.knee.setValueAtTime value -1 outside nominal range [0, 40]; value will be clamped.
CONSOLE WARNING: line 383: DynamicsCompressor.knee.value 81 outside nominal range [0, 40]; value will be clamped.
CONSOLE WARNING: line 383: DynamicsCompressor.knee.setValueAtTime value 81 outside nominal range [0, 40]; value will be clamped.
CONSOLE WARNING: line 383: DynamicsCompressor.ratio.value 41 outside nominal range [1, 20]; value will be clamped.
CONSOLE WARNING: line 383: DynamicsCompressor.ratio.setValueAtTime value 41 outside nominal range [1, 20]; value will be clamped.
CONSOLE WARNING: line 371: DynamicsCompressor.attack.value -1 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 371: DynamicsCompressor.attack.setValueAtTime value -1 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 383: DynamicsCompressor.attack.value 3 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 383: DynamicsCompressor.attack.setValueAtTime value 3 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 371: DynamicsCompressor.release.value -1 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 371: DynamicsCompressor.release.setValueAtTime value -1 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 383: DynamicsCompressor.release.value 3 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 383: DynamicsCompressor.release.setValueAtTime value 3 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 371: BiquadFilter.frequency.value -1 outside nominal range [0, 24000]; value will be clamped.
CONSOLE WARNING: line 371: BiquadFilter.frequency.setValueAtTime value -1 outside nominal range [0, 24000]; value will be clamped.
CONSOLE WARNING: line 383: BiquadFilter.frequency.value 48001 outside nominal range [0, 24000]; value will be clamped.
CONSOLE WARNING: line 383: BiquadFilter.frequency.setValueAtTime value 48001 outside nominal range [0, 24000]; value will be clamped.
CONSOLE WARNING: line 371: Oscillator.frequency.value -48001 outside nominal range [-24000, 24000]; value will be clamped.
CONSOLE WARNING: line 371: Oscillator.frequency.setValueAtTime value -48001 outside nominal range [-24000, 24000]; value will be clamped.
CONSOLE WARNING: line 383: Oscillator.frequency.value 48001 outside nominal range [-24000, 24000]; value will be clamped.
CONSOLE WARNING: line 383: Oscillator.frequency.setValueAtTime value 48001 outside nominal range [-24000, 24000]; value will be clamped.
CONSOLE WARNING: line 302: Delay.delayTime.setValueAtTime value -1 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 303: Delay.delayTime.linearRampToValueAtTime value 2 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 304: Delay.delayTime.exponentialRampToValue value 3 outside nominal range [0, 1]; value will be clamped.
......
<!DOCTYPE html>
<html>
<head>
<title>
AudioParam Value Setter Error Tests
</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../resources/audit-util.js"></script>
<script src="../resources/audit.js"></script>
<script src="../resources/audioparam-testing.js"></script>
</head>
<body>
<script id="layout-test-code">
let sampleRate = 16384;
// Number of frames in a rendering quantum.
// Test doesn't need to run for very long.
let renderDuration = 0.2;
let renderFrames = renderDuration * sampleRate;
let automationEndTime = 0.1;
let audit = Audit.createTaskRunner();
audit.define(
'Test value setter with setValueCurveAtTime', (task, should) => {
let context = new OfflineAudioContext(
{length: renderFrames, sampleRate: sampleRate});
let src = new ConstantSourceNode(context);
let gain = new GainNode(context);
src.connect(gain).connect(context.destination);
let render_quantum_duration =
RENDER_QUANTUM_FRAMES / context.sampleRate;
// Start and duration of the curve automation. These are fairly
// arbitrary, but the start should be at least 2 render quanta from
// the beginning to allow time for testing the value setter before
// the curve starts. The duration should be at least 2 render
// quanta to allow us to apply the value setter in the middle
// (somewhere) of the curve.
let curveStartTime = 3 * render_quantum_duration;
let curveDuration = 4 * render_quantum_duration;
should(
() => {
gain.gain.setValueCurveAtTime(
[-2, 1], curveStartTime, curveDuration);
},
`setValueCurveAtTime([...], ${curveStartTime}, ${
curveDuration
})`)
.notThrow();
// Applying the value setter outside the curve automation should not
// throw. The gain value is arbitrary.
context.suspend(curveStartTime - render_quantum_duration)
.then(() => {
should(
() => gain.gain.value = Math.PI,
'Using value setter at time ' + context.currentTime +
' before curve starts')
.notThrow();
})
.then(() => context.resume());
// Applying the value setter inside the curve automation should not
// throw. The gain value is arbitrary.
context.suspend(curveStartTime + curveDuration / 2)
.then(() => {
should(
() => gain.gain.value = 0,
'Using value setter at time ' + context.currentTime +
' in the middle of the curve')
.throw('NotSupportedError');
})
.then(() => context.resume());
src.start();
// Don't care about the actual output.
context.startRendering().then(() => task.done());
});
audit.run();
</script>
</body>
</html>
CONSOLE ERROR: line 39: [audit.js] this test requires the explicit comparison with the expected result when it runs with run-webkit-tests.
CONSOLE MESSAGE: line 129: Test: test-0
CONSOLE WARNING: line 135: AudioParam value setter will become equivalent to AudioParam.setValueAtTime() in M65, around March 2018 See https://webaudio.github.io/web-audio-api/#dom-audioparam-value for more details.
CONSOLE WARNING: line 148: ConstantSource.offset.value setter called at time 0.4986666666666666 overlaps event setValueAtTime(1, 0.2) to linearRampToValueAtTime(99, 0.7)
CONSOLE MESSAGE: line 129: Test: test-1
CONSOLE WARNING: line 148: ConstantSource.offset.value setter called at time 0.09866666666666667 overlaps event setValueAtTime(0, 0) to linearRampToValueAtTime(1, 0.21)
CONSOLE WARNING: line 148: ConstantSource.offset.value setter called at time 0.4986666666666666 overlaps event linearRampToValueAtTime(1, 0.21) to linearRampToValueAtTime(99, 0.71)
CONSOLE MESSAGE: line 129: Test: test-2
CONSOLE WARNING: line 148: ConstantSource.offset.value setter called at time 0.09866666666666667 overlaps event setValueAtTime(0, 0) to linearRampToValueAtTime(1, 0.22)
CONSOLE MESSAGE: line 129: Test: test-3
CONSOLE WARNING: line 148: ConstantSource.offset.value setter called at time 0.09866666666666667 overlaps event setValueAtTime(0, 0) to linearRampToValueAtTime(1, 0.23)
CONSOLE MESSAGE: line 129: Test: test-4
CONSOLE WARNING: line 148: ConstantSource.offset.value setter called at time 0.4986666666666666 overlaps event setTargetAtTime(1, 0.24, 0.1)
CONSOLE MESSAGE: line 129: Test: test-5
CONSOLE WARNING: line 148: ConstantSource.offset.value setter called at time 0.4986666666666666 overlaps event setValueCurveAtTime(..., 0.25, 0.5) to setValueAtTime(3, 0.75)
This is a testharness.js-based test.
PASS # AUDIT TASK RUNNER STARTED.
PASS > [test-0]
......@@ -31,8 +24,9 @@ PASS > [test-4]
PASS test-4 : rendered successfully
PASS < [test-4] All assertions passed. (total 1 assertions)
PASS > [test-5]
PASS Value setter overlaps setValueCurve threw NotSupportedError: "Failed to set the 'value' property on 'AudioParam': setValueAtTime(99, 0.4986666666666666) overlaps setValueCurveAtTime(..., 0.25, 0.5)".
PASS test-5 : rendered successfully
PASS < [test-5] All assertions passed. (total 1 assertions)
PASS < [test-5] All assertions passed. (total 2 assertions)
PASS # AUDIT TASK RUNNER FINISHED: 6 tasks ran successfully.
Harness: the test ran to completion.
......@@ -112,7 +112,7 @@
// No overlap.
{time: 0.1, value: -1},
// Overlaps setValueCurve
{time: 0.5, value: 99},
{time: 0.5, value: 99, shouldThrow: true},
// Past end of setValueCurve, so no warning
{time: .9, value: 50}
])
......@@ -145,7 +145,13 @@
for (let item of valueSetterList) {
context.suspend(item.time)
.then(() => {
src.offset.value = item.value;
if (item.shouldThrow === true) {
should(() => src.offset.value = item.value,
'Value setter overlaps setValueCurve')
.throw('NotSupportedError');
} else {
src.offset.value = item.value;
}
})
.then(() => context.resume());
}
......
CONSOLE WARNING: line 36: AudioParam value setter will become equivalent to AudioParam.setValueAtTime() in M65, around March 2018 See https://webaudio.github.io/web-audio-api/#dom-audioparam-value for more details.
CONSOLE WARNING: line 39: AudioWorklet("noise-generator").amplitude.value 99 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 39: AudioWorklet("noise-generator").amplitude.setValueAtTime value 99 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 43: AudioWorklet("noise-generator").amplitude.setValueAtTime value -1 outside nominal range [0, 1]; value will be clamped.
CONSOLE WARNING: line 44: AudioWorklet("noise-generator").amplitude.linearRampToValueAtTime value 5 outside nominal range [0, 1]; value will be clamped.
This is a testharness.js-based test.
......
......@@ -68,7 +68,7 @@
initializer: {type: 'peaking', gain: 1},
changeList:
[{quantum: 2, newValue: 5}, {quantum: 6, newValue: -.3}],
threshold: 1.3710e-6
threshold: 1.9074e-6
}).then(() => task.done());
});
......
<!doctype html>
<html>
<head>
<title>
Test Panner.setPosition and Panner.setOrientation Errors
</title>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<script src="../resources/audit-util.js"></script>
<script src="../resources/audit.js"></script>
<script src="../resources/set-position-vs-curve-test.js"></script>
</head>
<body>
<script id="layout-test-code">
// Fairly arbitrary rate
let sampleRate = 16000;
// For the tests we need to render for at least two render quanta.
// Otherwise, pretty arbitrary.
let renderFrames = 256;
let renderDuration = renderFrames / sampleRate;
// The curve duration for the test. Anything less than one render quantum
// is fine. Arbitrarily choose something small.
let curveDurationFrames = 8;
let curveDuration = curveDurationFrames / sampleRate;
// When to call setPosition, after the setValueCurve has ended. Any value
// after the end of the first render quantum is fine.
let suspendFrame = 129;
let audit = Audit.createTaskRunner();
// Array of tests to do. Each element of this array is used to create a
// task to test the entry.
let tests = [
// Test setPosition against positionX, positionY, and positionZ
// setValueCurves. Include test where there's overlap and where there
// isn't.
{
name: 'Panner setPosition X error',
options: {paramName: 'positionX', curveDuration: renderDuration}
},
{
name: 'Panner setPosition X no error',
options: {
paramName: 'positionX',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
{
name: 'Panner setPosition Y error',
options: {paramName: 'positionY', curveDuration: renderDuration}
},
{
name: 'Panner setPosition Y no error',
options: {
paramName: 'positionY',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
{
name: 'Panner setPosition Z error',
options: {paramName: 'positionZ', curveDuration: renderDuration}
},
{
name: 'Panner setPosition Z no error',
options: {
paramName: 'positionZ',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
// Now do the same with setOrientation.
{
name: 'Panner setOrientation X error',
options: {paramName: 'orientationX', curveDuration: renderDuration}
},
{
name: 'Panner setOrientation X no error',
options: {
paramName: 'orientationX',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
{
name: 'Panner setOrientation Y error',
options: {paramName: 'orientationY', curveDuration: renderDuration}
},
{
name: 'Panner setOrientation Y no error',
options: {
paramName: 'orientationY',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
{
name: 'Panner setOrientation Z error',
options: {paramName: 'orientationZ', curveDuration: renderDuration}
},
{
name: 'Panner setOrientation Z no error',
options: {
paramName: 'orientationZ',
curveDuration: curveDuration,
suspendFrame: suspendFrame
}
},
];
// Create an audit test for each entry in |tests|.
tests.forEach(test => {
audit.define(test.name, (task, should) => {
let context = new OfflineAudioContext(1, renderFrames, sampleRate);
testPositionSetterVsCurve(
should, context,
Object.assign(
{testName: test.name, nodeName: 'panner'}, test.options))
.then(() => task.done());
});
});
audit.run();
</script>
</body>
</html>
// This depends on audit.js to define the |should| function used herein.
//
// Test that setPosition throws an error if there is already a
// setValueCurve scheduled during the same time period.
function testPositionSetterVsCurve(should, context, options) {
// Create the graph consisting of a source node and the panner.
let src = new ConstantSourceNode(context, {offset: 1});
let panner = new PannerNode(context);
src.connect(panner).connect(context.destination);
let curve = Float32Array.from([-10, 10]);
// Determine if we're testing the panner or the listener and set the node
// appropriately.
let testNode = options.nodeName === 'panner' ? panner : context.listener;
let prefix = options.nodeName === 'panner' ? 'panner.' : 'listener.';
let message = prefix + options.paramName + '.setValueCurve(..., 0, ' +
options.curveDuration + ')';
// If the coordinate name has 'position', we're testing setPosition;
// otherwise assume we're testing setOrientation.
let methodName =
options.paramName.includes('position') ? 'setPosition' : 'setOrientation';
// Sanity check that we're testing the right node. (The |testName| prefix is
// to make each message unique for testharness.)
if (options.nodeName === 'panner') {
should(
testNode instanceof PannerNode,
options.testName + ': Node under test is a PannerNode')
.beTrue();
} else {
should(
testNode instanceof AudioListener,
options.testName + ': Node under test is an AudioLIstener')
.beTrue();
}
// Set the curve automation on the specified axis.
should(() => {
testNode[options.paramName].setValueCurveAtTime(
curve, 0, options.curveDuration);
}, message).notThrow();
let resumeContext = context.resume.bind(context);
// Get correct argument string for the setter for printing the message.
let setterArguments;
if (options.nodeName === 'panner') {
setterArguments = '(1,1,1)';
} else {
if (methodName === 'setPosition') {
setterArguments = '(1,1,1)';
} else {
setterArguments = '(1,1,1,1,1,1)';
}
}
let setterMethod = () => {
// It's ok to give extra args.
testNode[methodName](1, 1, 1, 1, 1, 1);
};
if (options.suspendFrame) {
// We're testing setPosition after the curve has ended to verify that
// we don't throw an error. Thus, suspend the context and call
// setPosition.
let suspendTime = options.suspendFrame / context.sampleRate;
context.suspend(suspendTime)
.then(() => {
should(
setterMethod,
prefix + methodName + setterArguments + ' for ' +
options.paramName + ' at time ' + suspendTime)
.notThrow();
})
.then(resumeContext);
} else {
// Basic test where setPosition is called where setValueCurve is
// already active.
context.suspend(0)
.then(() => {
should(
setterMethod,
prefix + methodName + setterArguments + ' for ' +
options.paramName)
.throw('NotSupportedError');
})
.then(resumeContext);
}
src.start();
return context.startRendering();
}
......@@ -271,37 +271,52 @@ void AudioListener::MarkPannersAsDirty(unsigned type) {
panner->MarkPannerAsDirty(type);
}
void AudioListener::setPosition(const FloatPoint3D& position) {
void AudioListener::setPosition(const FloatPoint3D& position,
ExceptionState& exceptionState) {
DCHECK(IsMainThread());
// This synchronizes with panner's process().
MutexLocker listener_locker(listener_lock_);
position_x_->setValue(position.X());
position_y_->setValue(position.Y());
position_z_->setValue(position.Z());
double now = position_x_->Context()->currentTime();
position_x_->setValueAtTime(position.X(), now, exceptionState);
position_y_->setValueAtTime(position.Y(), now, exceptionState);
position_z_->setValueAtTime(position.Z(), now, exceptionState);
MarkPannersAsDirty(PannerHandler::kAzimuthElevationDirty |
PannerHandler::kDistanceConeGainDirty);
}
void AudioListener::setOrientation(const FloatPoint3D& orientation) {
void AudioListener::setOrientation(const FloatPoint3D& orientation,
ExceptionState& exceptionState) {
DCHECK(IsMainThread());
// This synchronizes with panner's process().
MutexLocker listener_locker(listener_lock_);
forward_x_->setValue(orientation.X());
forward_y_->setValue(orientation.Y());
forward_z_->setValue(orientation.Z());
double now = forward_x_->Context()->currentTime();
forward_x_->setValueAtTime(orientation.X(), now, exceptionState);
forward_y_->setValueAtTime(orientation.Y(), now, exceptionState);
forward_z_->setValueAtTime(orientation.Z(), now, exceptionState);
MarkPannersAsDirty(PannerHandler::kAzimuthElevationDirty);
}
void AudioListener::SetUpVector(const FloatPoint3D& up_vector) {
void AudioListener::SetUpVector(const FloatPoint3D& up_vector,
ExceptionState& exceptionState) {
DCHECK(IsMainThread());
// This synchronizes with panner's process().
MutexLocker listener_locker(listener_lock_);
up_x_->setValue(up_vector.X());
up_y_->setValue(up_vector.Y());
up_z_->setValue(up_vector.Z());
double now = up_x_->Context()->currentTime();
up_x_->setValueAtTime(up_vector.X(), now, exceptionState);
up_y_->setValueAtTime(up_vector.Y(), now, exceptionState);
up_z_->setValueAtTime(up_vector.Z(), now, exceptionState);
MarkPannersAsDirty(PannerHandler::kAzimuthElevationDirty);
}
......
......@@ -100,8 +100,8 @@ class AudioListener : public ScriptWrappable {
const float* GetUpZValues(size_t frames_to_process);
// Position
void setPosition(float x, float y, float z) {
setPosition(FloatPoint3D(x, y, z));
void setPosition(float x, float y, float z, ExceptionState& exceptionState) {
setPosition(FloatPoint3D(x, y, z), exceptionState);
}
// Orientation and Up-vector
......@@ -110,9 +110,10 @@ class AudioListener : public ScriptWrappable {
float z,
float up_x,
float up_y,
float up_z) {
setOrientation(FloatPoint3D(x, y, z));
SetUpVector(FloatPoint3D(up_x, up_y, up_z));
float up_z,
ExceptionState& exceptionState) {
setOrientation(FloatPoint3D(x, y, z), exceptionState);
SetUpVector(FloatPoint3D(up_x, up_y, up_z), exceptionState);
}
Mutex& ListenerLock() { return listener_lock_; }
......@@ -132,9 +133,9 @@ class AudioListener : public ScriptWrappable {
private:
AudioListener(BaseAudioContext&);
void setPosition(const FloatPoint3D&);
void setOrientation(const FloatPoint3D&);
void SetUpVector(const FloatPoint3D&);
void setPosition(const FloatPoint3D&, ExceptionState&);
void setOrientation(const FloatPoint3D&, ExceptionState&);
void SetUpVector(const FloatPoint3D&, ExceptionState&);
void MarkPannersAsDirty(unsigned);
......
......@@ -28,8 +28,8 @@
// See https://webaudio.github.io/web-audio-api/#AudioListener
interface AudioListener {
[MeasureAs=AudioListenerSetPosition] void setPosition(float x, float y, float z);
[MeasureAs=AudioListenerSetOrientation] void setOrientation(float x, float y, float z, float xUp, float yUp, float zUp);
[RaisesException, MeasureAs=AudioListenerSetPosition] void setPosition(float x, float y, float z);
[RaisesException, MeasureAs=AudioListenerSetOrientation] void setOrientation(float x, float y, float z, float xUp, float yUp, float zUp);
// Location of the listener
readonly attribute AudioParam positionX;
......
......@@ -303,6 +303,17 @@ void AudioParam::setValue(float value) {
Handler().SetValue(value);
}
void AudioParam::setValue(float value, ExceptionState& exception_state) {
WarnIfOutsideRange("value", value);
// This is to signal any errors, if necessary, about conflicting
// automations.
setValueAtTime(value, Context()->currentTime(), exception_state);
// This is to change the value so that an immediate query for the
// value returns the expected values.
Handler().SetValue(value);
}
// TODO(crbug.com/764396): Remove this when fixed.
void AudioParam::WarnIfSetterOverlapsEvent() {
DCHECK(IsMainThread());
......
......@@ -234,6 +234,7 @@ class AudioParam final : public ScriptWrappable {
String GetParamName() const;
float value() const;
void setValue(float, ExceptionState&);
void setValue(float);
float defaultValue() const;
......
......@@ -28,7 +28,7 @@
// See https://webaudio.github.io/web-audio-api/#AudioParam
interface AudioParam {
attribute float value;
[RaisesException=Setter] attribute float value;
readonly attribute float defaultValue;
// Nominal range for the value.
......
......@@ -430,25 +430,35 @@ void PannerHandler::SetConeOuterGain(double angle) {
MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
}
void PannerHandler::SetPosition(float x, float y, float z) {
void PannerHandler::SetPosition(float x,
float y,
float z,
ExceptionState& exceptionState) {
// This synchronizes with process().
MutexLocker process_locker(process_lock_);
position_x_->SetValue(x);
position_y_->SetValue(y);
position_z_->SetValue(z);
double now = Context()->currentTime();
position_x_->Timeline().SetValueAtTime(x, now, exceptionState);
position_y_->Timeline().SetValueAtTime(y, now, exceptionState);
position_z_->Timeline().SetValueAtTime(z, now, exceptionState);
MarkPannerAsDirty(PannerHandler::kAzimuthElevationDirty |
PannerHandler::kDistanceConeGainDirty);
}
void PannerHandler::SetOrientation(float x, float y, float z) {
void PannerHandler::SetOrientation(float x,
float y,
float z,
ExceptionState& exceptionState) {
// This synchronizes with process().
MutexLocker process_locker(process_lock_);
orientation_x_->SetValue(x);
orientation_y_->SetValue(y);
orientation_z_->SetValue(z);
double now = Context()->currentTime();
orientation_x_->Timeline().SetValueAtTime(x, now, exceptionState);
orientation_y_->Timeline().SetValueAtTime(y, now, exceptionState);
orientation_z_->Timeline().SetValueAtTime(z, now, exceptionState);
MarkPannerAsDirty(PannerHandler::kDistanceConeGainDirty);
}
......@@ -729,12 +739,18 @@ void PannerNode::setPanningModel(const String& model) {
GetPannerHandler().SetPanningModel(model);
}
void PannerNode::setPosition(float x, float y, float z) {
GetPannerHandler().SetPosition(x, y, z);
void PannerNode::setPosition(float x,
float y,
float z,
ExceptionState& exceptionState) {
GetPannerHandler().SetPosition(x, y, z, exceptionState);
}
void PannerNode::setOrientation(float x, float y, float z) {
GetPannerHandler().SetOrientation(x, y, z);
void PannerNode::setOrientation(float x,
float y,
float z,
ExceptionState& exceptionState) {
GetPannerHandler().SetOrientation(x, y, z, exceptionState);
}
String PannerNode::distanceModel() const {
......
......@@ -84,8 +84,8 @@ class PannerHandler final : public AudioHandler {
void SetPanningModel(const String&);
// Position and orientation
void SetPosition(float x, float y, float z);
void SetOrientation(float x, float y, float z);
void SetPosition(float x, float y, float z, ExceptionState&);
void SetOrientation(float x, float y, float z, ExceptionState&);
// Distance parameters
String DistanceModel() const;
......@@ -226,8 +226,8 @@ class PannerNode final : public AudioNode {
String panningModel() const;
void setPanningModel(const String&);
void setPosition(float x, float y, float z);
void setOrientation(float x, float y, float z);
void setPosition(float x, float y, float z, ExceptionState&);
void setOrientation(float x, float y, float z, ExceptionState&);
String distanceModel() const;
void setDistanceModel(const String&);
double refDistance() const;
......
......@@ -44,8 +44,8 @@ enum DistanceModelType {
attribute PanningModelType panningModel;
// Uses a 3D cartesian coordinate system
[MeasureAs=PannerNodeSetPosition] void setPosition(float x, float y, float z);
[MeasureAs=PannerNodeSetOrientation] void setOrientation(float x, float y, float z);
[RaisesException, MeasureAs=PannerNodeSetPosition] void setPosition(float x, float y, float z);
[RaisesException, MeasureAs=PannerNodeSetOrientation] void setOrientation(float x, float y, float z);
// Uses a 3D cartesian coordinate system
readonly attribute AudioParam positionX;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment