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
|
// Test k-rate vs a-rate AudioParams.
//
// |options| describes how the testing of the AudioParam should be done:
//
// sourceNodeName: name of source node to use for testing; defaults to
// 'OscillatorNode'. If set to 'none', then no source node
// is created for testing and it is assumed that the AudioNode
// under test are sources and need to be started.
// verifyPieceWiseConstant: if true, verify that the k-rate output is
// piecewise constant for each render quantum.
// nodeName: name of the AudioNode to be tested
// nodeOptions: options to be used in the AudioNode constructor
//
// prefix: Prefix for all output messages (to make them unique for
// testharness)
//
// rateSettings: A vector of dictionaries specifying how to set the automation
// rate(s):
// name: Name of the AudioParam
// value: The automation rate for the AudioParam given by |name|.
//
// automations: A vector of dictionaries specifying how to automate each
// AudioParam:
// name: Name of the AudioParam
//
// methods: A vector of dictionaries specifying the automation methods to
// be used for testing:
// name: Automation method to call
// options: Arguments for the automation method
//
// Testing is somewhat rudimentary. We create two nodes of the same type. One
// node uses the default automation rates for each AudioParam (expecting them to
// be a-rate). The second node sets the automation rate of AudioParams to
// "k-rate". The set is speciified by |options.rateSettings|.
//
// For both of these nodes, the same set of automation methods (given by
// |options.automations|) is applied. A simple oscillator is connected to each
// node which in turn are connected to different channels of an offline context.
// Channel 0 is the k-rate node output; channel 1, the a-rate output; and
// channel 3, the difference between the outputs.
//
// Success is declared if the difference signal is not exactly zero. This means
// the the automations did different things, as expected.
//
// The promise from |startRendering| is returned.
function doTest(context, should, options) {
let merger = new ChannelMergerNode(
context, {numberOfInputs: context.destination.channelCount});
merger.connect(context.destination);
let src = null;
// Skip creating a source to drive the graph if |sourceNodeName| is 'none'.
// If |sourceNodeName| is given, use that, else default to OscillatorNode.
if (options.sourceNodeName !== 'none') {
src = new window[options.sourceNodeName || 'OscillatorNode'](context);
}
let kRateNode = new window[options.nodeName](context, options.nodeOptions);
let aRateNode = new window[options.nodeName](context, options.nodeOptions);
let inverter = new GainNode(context, {gain: -1});
// Set kRateNode filter to use k-rate params.
options.rateSettings.forEach(setting => {
kRateNode[setting.name].automationRate = setting.value;
// Mostly for documentation in the output. These should always
// pass.
should(
kRateNode[setting.name].automationRate,
`${options.prefix}: Setting ${
setting.name
}.automationRate to "${setting.value}"`)
.beEqualTo(setting.value);
});
// Run through all automations for each node separately. (Mostly to keep
// output of automations together.)
options.automations.forEach(param => {
param.methods.forEach(method => {
// Most for documentation in the output. These should never throw.
let message = `${param.name}.${method.name}(${method.options})`
should(() => {
kRateNode[param.name][method.name](...method.options);
}, options.prefix + ': k-rate node: ' + message).notThrow();
});
});
options.automations.forEach(param => {
param.methods.forEach(method => {
// Most for documentation in the output. These should never throw.
let message = `${param.name}.${method.name}(${method.options})`
should(() => {
aRateNode[param.name][method.name](...method.options);
}, options.prefix + ': a-rate node:' + message).notThrow();
});
});
// Connect the source, if specified.
if (src) {
src.connect(kRateNode);
src.connect(aRateNode);
}
// The k-rate result is channel 0, and the a-rate result is channel 1.
kRateNode.connect(merger, 0, 0);
aRateNode.connect(merger, 0, 1);
// Compute the difference between the a-rate and k-rate results and send
// that to channel 2.
kRateNode.connect(merger, 0, 2);
aRateNode.connect(inverter).connect(merger, 0, 2);
if (src) {
src.start();
} else {
// If there's no source, then assume the test nodes are sources and start
// them.
kRateNode.start();
aRateNode.start();
}
return context.startRendering().then(renderedBuffer => {
let kRateOutput = renderedBuffer.getChannelData(0);
let aRateOutput = renderedBuffer.getChannelData(1);
let diff = renderedBuffer.getChannelData(2);
// Some informative messages to print out values of the k-rate and
// a-rate outputs. These should always pass.
should(
kRateOutput, `${options.prefix}: Output of k-rate ${options.nodeName}`)
.beEqualToArray(kRateOutput);
should(
aRateOutput, `${options.prefix}: Output of a-rate ${options.nodeName}`)
.beEqualToArray(aRateOutput);
// The real test. If k-rate AudioParam is working correctly, the
// k-rate result MUST differ from the a-rate result.
should(
diff,
`${
options.prefix
}: Difference between a-rate and k-rate ${options.nodeName}`)
.notBeConstantValueOf(0);
if (options.verifyPieceWiseConstant) {
// Verify that the output from the k-rate parameter is step-wise
// constant.
for (let k = 0; k < kRateOutput.length; k += 128) {
should(
kRateOutput.slice(k, k + 128),
`${options.prefix} k-rate output [${k}: ${k + 127}]`)
.beConstantValueOf(kRateOutput[k]);
}
}
});
}
|