1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
<!DOCTYPE html>
<html>
<head>
<title>
Test Clamping of Distance for PannerNode
</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>
</head>
<body>
<script id="layout-test-code">
// Arbitrary sample rate and render length.
let sampleRate = 48000;
let renderFrames = 128;
let audit = Audit.createTaskRunner();
audit.define('ref-distance-error', (task, should) => {
testDistanceLimits(should, {name: 'refDistance', isZeroAllowed: true});
task.done();
});
audit.define('max-distance-error', (task, should) => {
testDistanceLimits(should, {name: 'maxDistance', isZeroAllowed: false});
task.done();
});
function testDistanceLimits(should, options) {
// Verify that exceptions are thrown for invalid values of refDistance.
let context = new OfflineAudioContext(1, renderFrames, sampleRate);
let attrName = options.name;
let prefix = 'new PannerNode(c, {' + attrName + ': ';
should(function() {
let nodeOptions = {};
nodeOptions[attrName] = -1;
new PannerNode(context, nodeOptions);
}, prefix + '-1})').throw(RangeError);
if (options.isZeroAllowed) {
should(function() {
let nodeOptions = {};
nodeOptions[attrName] = 0;
new PannerNode(context, nodeOptions);
}, prefix + '0})').notThrow();
} else {
should(function() {
let nodeOptions = {};
nodeOptions[attrName] = 0;
new PannerNode(context, nodeOptions);
}, prefix + '0})').throw(RangeError);
}
// The smallest representable positive single float.
let leastPositiveDoubleFloat = 4.9406564584124654e-324;
should(function() {
let nodeOptions = {};
nodeOptions[attrName] = leastPositiveDoubleFloat;
new PannerNode(context, nodeOptions);
}, prefix + leastPositiveDoubleFloat + '})').notThrow();
prefix = 'panner.' + attrName + ' = ';
panner = new PannerNode(context);
should(function() {
panner[attrName] = -1;
}, prefix + '-1').throw(RangeError);
if (options.isZeroAllowed) {
should(function() {
panner[attrName] = 0;
}, prefix + '0').notThrow();
} else {
should(function() {
panner[attrName] = 0;
}, prefix + '0').throw(RangeError);
}
should(function() {
panner[attrName] = leastPositiveDoubleFloat;
}, prefix + leastPositiveDoubleFloat).notThrow();
}
audit.define('min-distance', async (task, should) => {
// Test clamping of panner distance to refDistance for all of the
// distance models. The actual distance is arbitrary as long as it's
// less than refDistance. We test default and non-default values for
// the panner's refDistance and maxDistance.
// correctly.
await runTest(should, {
distance: 0.01,
distanceModel: 'linear',
});
await runTest(should, {
distance: 0.01,
distanceModel: 'exponential',
});
await runTest(should, {
distance: 0.01,
distanceModel: 'inverse',
});
await runTest(should, {
distance: 2,
distanceModel: 'linear',
maxDistance: 1000,
refDistance: 10,
});
await runTest(should, {
distance: 2,
distanceModel: 'exponential',
maxDistance: 1000,
refDistance: 10,
});
await runTest(should, {
distance: 2,
distanceModel: 'inverse',
maxDistance: 1000,
refDistance: 10,
});
task.done();
});
audit.define('max-distance', async (task, should) => {
// Like the "min-distance" task, but for clamping to the max
// distance. The actual distance is again arbitrary as long as it is
// greater than maxDistance.
await runTest(should, {
distance: 20000,
distanceModel: 'linear',
});
await runTest(should, {
distance: 21000,
distanceModel: 'exponential',
});
await runTest(should, {
distance: 23000,
distanceModel: 'inverse',
});
await runTest(should, {
distance: 5000,
distanceModel: 'linear',
maxDistance: 1000,
refDistance: 10,
});
await runTest(should, {
distance: 5000,
distanceModel: 'exponential',
maxDistance: 1000,
refDistance: 10,
});
await runTest(should, {
distance: 5000,
distanceModel: 'inverse',
maxDistance: 1000,
refDistance: 10,
});
task.done();
});
function runTest(should, options) {
let context = new OfflineAudioContext(2, renderFrames, sampleRate);
let src = new OscillatorNode(context, {
type: 'sawtooth',
frequency: 20 * 440,
});
// Set panner options. Use a non-default rolloffFactor so that the
// various distance models look distinctly different.
let pannerOptions = {};
Object.assign(pannerOptions, options, {rolloffFactor: 0.5});
let pannerRef = new PannerNode(context, pannerOptions);
let pannerTest = new PannerNode(context, pannerOptions);
// Split the panner output so we can grab just one of the output
// channels.
let splitRef = new ChannelSplitterNode(context, {numberOfOutputs: 2});
let splitTest = new ChannelSplitterNode(context, {numberOfOutputs: 2});
// Merge the panner outputs back into one stereo stream for the
// destination.
let merger = new ChannelMergerNode(context, {numberOfInputs: 2});
src.connect(pannerTest).connect(splitTest).connect(merger, 0, 0);
src.connect(pannerRef).connect(splitRef).connect(merger, 0, 1);
merger.connect(context.destination);
// Move the panner some distance away. Arbitrarily select the x
// direction. For the reference panner, manually clamp the distance.
// All models clamp the distance to a minimum of refDistance. Only the
// linear model also clamps to a maximum of maxDistance.
let xRef = Math.max(options.distance, pannerRef.refDistance);
if (pannerRef.distanceModel === 'linear') {
xRef = Math.min(xRef, pannerRef.maxDistance);
}
let xTest = options.distance;
pannerRef.positionZ.setValueAtTime(xRef, 0);
pannerTest.positionZ.setValueAtTime(xTest, 0);
src.start();
return context.startRendering().then(function(resultBuffer) {
let actual = resultBuffer.getChannelData(0);
let expected = resultBuffer.getChannelData(1);
should(
xTest < pannerRef.refDistance || xTest > pannerRef.maxDistance,
'Model: ' + options.distanceModel + ': Distance (' + xTest +
') is outside the range [' + pannerRef.refDistance + ', ' +
pannerRef.maxDistance + ']')
.beEqualTo(true);
should(actual, 'Test panner output ' + JSON.stringify(options))
.beEqualToArray(expected);
});
}
audit.run();
</script>
</body>
</html>
|