OLD | NEW |
1 // Globals, to make testing and debugging easier. | 1 // Globals, to make testing and debugging easier. |
2 var context; | 2 var context; |
3 var filter; | 3 var filter; |
4 var signal; | 4 var signal; |
5 var renderedBuffer; | 5 var renderedBuffer; |
6 var renderedData; | 6 var renderedData; |
7 | 7 |
8 var sampleRate = 44100.0; | 8 var sampleRate = 44100.0; |
9 var pulseLengthFrames = .1 * sampleRate; | 9 var pulseLengthFrames = .1 * sampleRate; |
10 | 10 |
11 // Maximum allowed error for the test to succeed. Experimentally determined. | 11 // Maximum allowed error for the test to succeed. Experimentally determined. |
12 var maxAllowedError = 5.9e-8; | 12 var maxAllowedError = 5.9e-8; |
13 | 13 |
14 // This must be large enough so that the filtered result is | 14 // This must be large enough so that the filtered result is |
15 // essentially zero. See comments for createTestAndRun. | 15 // essentially zero. See comments for createTestAndRun. |
16 var timeStep = .1; | 16 var timeStep = .1; |
17 | 17 |
18 // Maximum number of filters we can process (mostly for setting the | 18 // Maximum number of filters we can process (mostly for setting the |
19 // render length correctly.) | 19 // render length correctly.) |
20 var maxFilters = 5; | 20 var maxFilters = 5; |
21 | 21 |
22 // How long to render. Must be long enough for all of the filters we | 22 // How long to render. Must be long enough for all of the filters we |
23 // want to test. | 23 // want to test. |
24 var renderLengthSeconds = timeStep * (maxFilters + 1) ; | 24 var renderLengthSeconds = timeStep * (maxFilters + 1); |
25 | 25 |
26 var renderLengthSamples = Math.round(renderLengthSeconds * sampleRate); | 26 var renderLengthSamples = Math.round(renderLengthSeconds * sampleRate); |
27 | 27 |
28 // Number of filters that will be processed. | 28 // Number of filters that will be processed. |
29 var nFilters; | 29 var nFilters; |
30 | 30 |
31 function createImpulseBuffer(context, length) { | 31 function createImpulseBuffer(context, length) { |
32 var impulse = context.createBuffer(1, length, context.sampleRate); | 32 let impulse = context.createBuffer(1, length, context.sampleRate); |
33 var data = impulse.getChannelData(0); | 33 let data = impulse.getChannelData(0); |
34 for (var k = 1; k < data.length; ++k) { | 34 for (let k = 1; k < data.length; ++k) { |
35 data[k] = 0; | 35 data[k] = 0; |
36 } | 36 } |
37 data[0] = 1; | 37 data[0] = 1; |
38 | 38 |
39 return impulse; | 39 return impulse; |
40 } | 40 } |
41 | 41 |
42 | 42 |
43 function createTestAndRun(context, filterType, testParameters) { | 43 function createTestAndRun(context, filterType, testParameters) { |
44 // To test the filters, we apply a signal (an impulse) to each of | 44 // To test the filters, we apply a signal (an impulse) to each of |
45 // the specified filters, with each signal starting at a different | 45 // the specified filters, with each signal starting at a different |
46 // time. The output of the filters is summed together at the | 46 // time. The output of the filters is summed together at the |
47 // output. Thus for filter k, the signal input to the filter | 47 // output. Thus for filter k, the signal input to the filter |
48 // starts at time k * timeStep. For this to work well, timeStep | 48 // starts at time k * timeStep. For this to work well, timeStep |
49 // must be large enough for the output of each filter to have | 49 // must be large enough for the output of each filter to have |
50 // decayed to zero with timeStep seconds. That way the filter | 50 // decayed to zero with timeStep seconds. That way the filter |
51 // outputs don't interfere with each other. | 51 // outputs don't interfere with each other. |
52 | 52 |
53 var filterParameters = testParameters.filterParameters; | 53 let filterParameters = testParameters.filterParameters; |
54 nFilters = Math.min(filterParameters.length, maxFilters); | 54 nFilters = Math.min(filterParameters.length, maxFilters); |
55 | 55 |
56 signal = new Array(nFilters); | 56 signal = new Array(nFilters); |
57 filter = new Array(nFilters); | 57 filter = new Array(nFilters); |
58 | 58 |
59 impulse = createImpulseBuffer(context, pulseLengthFrames); | 59 impulse = createImpulseBuffer(context, pulseLengthFrames); |
60 | 60 |
61 // Create all of the signal sources and filters that we need. | 61 // Create all of the signal sources and filters that we need. |
62 for (var k = 0; k < nFilters; ++k) { | 62 for (let k = 0; k < nFilters; ++k) { |
63 signal[k] = context.createBufferSource(); | 63 signal[k] = context.createBufferSource(); |
64 signal[k].buffer = impulse; | 64 signal[k].buffer = impulse; |
65 | 65 |
66 filter[k] = context.createBiquadFilter(); | 66 filter[k] = context.createBiquadFilter(); |
67 filter[k].type = filterType; | 67 filter[k].type = filterType; |
68 filter[k].frequency.value = context.sampleRate / 2 * filterParameters[k]
.cutoff; | 68 filter[k].frequency.value = |
69 filter[k].detune.value = (filterParameters[k].detune === undefined) ? 0
: filterParameters[k].detune; | 69 context.sampleRate / 2 * filterParameters[k].cutoff; |
70 filter[k].Q.value = filterParameters[k].q; | 70 filter[k].detune.value = (filterParameters[k].detune === undefined) ? |
71 filter[k].gain.value = filterParameters[k].gain; | 71 0 : |
| 72 filterParameters[k].detune; |
| 73 filter[k].Q.value = filterParameters[k].q; |
| 74 filter[k].gain.value = filterParameters[k].gain; |
72 | 75 |
73 signal[k].connect(filter[k]); | 76 signal[k].connect(filter[k]); |
74 filter[k].connect(context.destination); | 77 filter[k].connect(context.destination); |
75 | 78 |
76 signal[k].start(timeStep * k); | 79 signal[k].start(timeStep * k); |
77 } | 80 } |
78 | 81 |
79 return context.startRendering() | 82 return context.startRendering().then(buffer => { |
80 .then(buffer => { | 83 checkFilterResponse(buffer, filterType, testParameters); |
81 checkFilterResponse(buffer, filterType, testParameters); | 84 }); |
82 }); | |
83 } | 85 } |
84 | 86 |
85 function addSignal(dest, src, destOffset) { | 87 function addSignal(dest, src, destOffset) { |
86 // Add src to dest at the given dest offset. | 88 // Add src to dest at the given dest offset. |
87 for (var k = destOffset, j = 0; k < dest.length, j < src.length; ++k, ++j) { | 89 for (let k = destOffset, j = 0; k < dest.length, j < src.length; ++k, ++j) { |
88 dest[k] += src[j]; | 90 dest[k] += src[j]; |
89 } | 91 } |
90 } | 92 } |
91 | 93 |
92 function generateReference(filterType, filterParameters) { | 94 function generateReference(filterType, filterParameters) { |
93 var result = new Array(renderLengthSamples); | 95 let result = new Array(renderLengthSamples); |
94 var data = new Array(renderLengthSamples); | 96 let data = new Array(renderLengthSamples); |
95 // Initialize the result array and data. | 97 // Initialize the result array and data. |
96 for (var k = 0; k < result.length; ++k) { | 98 for (let k = 0; k < result.length; ++k) { |
97 result[k] = 0; | 99 result[k] = 0; |
98 data[k] = 0; | 100 data[k] = 0; |
99 } | 101 } |
100 // Make data an impulse. | 102 // Make data an impulse. |
101 data[0] = 1; | 103 data[0] = 1; |
102 | |
103 for (var k = 0; k < nFilters; ++k) { | |
104 // Filter an impulse | |
105 var detune = (filterParameters[k].detune === undefined) ? 0 : filterPara
meters[k].detune; | |
106 var frequency = filterParameters[k].cutoff * Math.pow(2, detune / 1200);
// Apply detune, converting from Cents. | |
107 | |
108 var filterCoef = createFilter(filterType, | |
109 frequency, | |
110 filterParameters[k].q, | |
111 filterParameters[k].gain); | |
112 var y = filterData(filterCoef, data, renderLengthSamples); | |
113 | 104 |
114 // Accumulate this filtered data into the final output at the desired of
fset. | 105 for (let k = 0; k < nFilters; ++k) { |
115 addSignal(result, y, timeToSampleFrame(timeStep * k, sampleRate)); | 106 // Filter an impulse |
116 } | 107 let detune = (filterParameters[k].detune === undefined) ? |
| 108 0 : |
| 109 filterParameters[k].detune; |
| 110 let frequency = filterParameters[k].cutoff * |
| 111 Math.pow(2, detune / 1200); // Apply detune, converting from Cents. |
117 | 112 |
118 return result; | 113 let filterCoef = createFilter( |
| 114 filterType, frequency, filterParameters[k].q, filterParameters[k].gain); |
| 115 let y = filterData(filterCoef, data, renderLengthSamples); |
| 116 |
| 117 // Accumulate this filtered data into the final output at the desired |
| 118 // offset. |
| 119 addSignal(result, y, timeToSampleFrame(timeStep * k, sampleRate)); |
| 120 } |
| 121 |
| 122 return result; |
119 } | 123 } |
120 | 124 |
121 function checkFilterResponse(renderedBuffer, filterType, testParameters) { | 125 function checkFilterResponse(renderedBuffer, filterType, testParameters) { |
122 var filterParameters = testParameters.filterParameters; | 126 let filterParameters = testParameters.filterParameters; |
123 var maxAllowedError = testParameters.threshold; | 127 let maxAllowedError = testParameters.threshold; |
124 var should = testParameters.should; | 128 let should = testParameters.should; |
125 | 129 |
126 renderedData = renderedBuffer.getChannelData(0); | 130 renderedData = renderedBuffer.getChannelData(0); |
127 | 131 |
128 reference = generateReference(filterType, filterParameters); | 132 reference = generateReference(filterType, filterParameters); |
129 | 133 |
130 var len = Math.min(renderedData.length, reference.length); | 134 let len = Math.min(renderedData.length, reference.length); |
131 | 135 |
132 var success = true; | 136 let success = true; |
133 | 137 |
134 // Maximum error between rendered data and expected data | 138 // Maximum error between rendered data and expected data |
135 var maxError = 0; | 139 let maxError = 0; |
136 | 140 |
137 // Sample offset where the maximum error occurred. | 141 // Sample offset where the maximum error occurred. |
138 var maxPosition = 0; | 142 let maxPosition = 0; |
139 | 143 |
140 // Number of infinities or NaNs that occurred in the rendered data. | 144 // Number of infinities or NaNs that occurred in the rendered data. |
141 var invalidNumberCount = 0; | 145 let invalidNumberCount = 0; |
142 | 146 |
143 should(nFilters, "Number of filters tested") | 147 should(nFilters, 'Number of filters tested') |
144 .beEqualTo(filterParameters.length); | 148 .beEqualTo(filterParameters.length); |
145 | 149 |
146 // Compare the rendered signal with our reference, keeping | 150 // Compare the rendered signal with our reference, keeping |
147 // track of the maximum difference (and the offset of the max | 151 // track of the maximum difference (and the offset of the max |
148 // difference.) Check for bad numbers in the rendered output | 152 // difference.) Check for bad numbers in the rendered output |
149 // too. There shouldn't be any. | 153 // too. There shouldn't be any. |
150 for (var k = 0; k < len; ++k) { | 154 for (let k = 0; k < len; ++k) { |
151 var err = Math.abs(renderedData[k] - reference[k]); | 155 let err = Math.abs(renderedData[k] - reference[k]); |
152 if (err > maxError) { | 156 if (err > maxError) { |
153 maxError = err; | 157 maxError = err; |
154 maxPosition = k; | 158 maxPosition = k; |
155 } | |
156 if (!isValidNumber(renderedData[k])) { | |
157 ++invalidNumberCount; | |
158 } | |
159 } | 159 } |
| 160 if (!isValidNumber(renderedData[k])) { |
| 161 ++invalidNumberCount; |
| 162 } |
| 163 } |
160 | 164 |
161 should(invalidNumberCount, | 165 should( |
162 "Number of non-finite values in the rendered output") | 166 invalidNumberCount, 'Number of non-finite values in the rendered output') |
163 .beEqualTo(0); | 167 .beEqualTo(0); |
164 | 168 |
165 should(maxError, | 169 should(maxError, 'Max error in ' + filterTypeName[filterType] + ' response') |
166 "Max error in " + filterTypeName[filterType] + " response") | 170 .beLessThanOrEqualTo(maxAllowedError); |
167 .beLessThanOrEqualTo(maxAllowedError); | |
168 } | 171 } |
OLD | NEW |