2 var lengthInSeconds
= 2;
4 // Skip this many frames before comparing against reference to allow
5 // a steady-state to be reached in the up-sampling filters.
6 var filterStabilizeSkipFrames
= 2048;
8 var numberOfCurveFrames
= 65536;
13 // FIXME: test at more frequencies.
14 // When using the up-sampling filters (2x, 4x) any significant aliasing components
15 // should be at very high frequencies near Nyquist. These tests could be improved
16 // to allow for a higher acceptable amount of aliasing near Nyquist, but then
17 // become more stringent for lower frequencies.
19 // These test parameters are set in runWaveShaperOversamplingTest().
23 var fundamentalFrequency
;
24 var acceptableAliasingThresholdDecibels
;
28 // Chebyshev Polynomials.
29 // Given an input sinusoid, returns an output sinusoid of the given frequency multiple.
30 function T0(x
) { return 1; }
31 function T1(x
) { return x
; }
32 function T2(x
) { return 2*x
*x
- 1; }
33 function T3(x
) { return 4*x
*x
*x
- 3*x
; }
34 function T4(x
) { return 8*x
*x
*x
*x
- 8*x
*x
+ 1; }
36 function generateWaveShapingCurve() {
39 var curve
= new Float32Array(n
);
41 // The shaping curve uses Chebyshev Polynomial such that an input sinusoid
42 // at frequency f will generate an output of four sinusoids of frequencies:
44 // each of which is scaled.
45 for (var i
= 0; i
< n
; ++i
) {
46 var x
= (i
- n2
) / n2
;
47 var y
= kScale
* (T1(x
) + T2(x
) + T3(x
) + T4(x
));
54 function checkShapedCurve(event
) {
55 var buffer
= event
.renderedBuffer
;
57 var outputData
= buffer
.getChannelData(0);
58 var n
= buffer
.length
;
60 // The WaveShaperNode will have a processing latency if oversampling is used,
61 // so we should account for it.
63 // FIXME: .latency should be exposed as an attribute of the node
64 // var waveShaperLatencyFrames = waveshaper.latency * sampleRate;
65 // But for now we'll use the hard-coded values corresponding to the actual latencies:
66 var waveShaperLatencyFrames
= 0;
67 if (oversample
== "2x")
68 waveShaperLatencyFrames
= 128;
69 else if (oversample
== "4x")
70 waveShaperLatencyFrames
= 192;
72 var worstDeltaInDecibels
= -1000;
74 for (var i
= waveShaperLatencyFrames
; i
< n
; ++i
) {
75 var actual
= outputData
[i
];
77 // Account for the expected processing latency.
78 var j
= i
- waveShaperLatencyFrames
;
80 // Compute reference sinusoids.
81 var phaseInc
= 2 * Math
.PI
* fundamentalFrequency
/ sampleRate
;
83 // Generate an idealized reference based on the four generated frequencies truncated
84 // to the Nyquist rate. Ideally, we'd like the waveshaper's oversampling to perfectly
85 // remove all frequencies above Nyquist to avoid aliasing. In reality the oversampling filters are not
86 // quite perfect, so there will be a (hopefully small) amount of aliasing. We should
87 // be close to the ideal.
90 // Sum in fundamental frequency.
91 if (fundamentalFrequency
< nyquist
)
92 reference
+= Math
.sin(phaseInc
* j
);
94 // Note that the phase of each of the expected generated harmonics is different.
95 if (fundamentalFrequency
* 2 < nyquist
)
96 reference
+= -Math
.cos(phaseInc
* j
* 2);
97 if (fundamentalFrequency
* 3 < nyquist
)
98 reference
+= -Math
.sin(phaseInc
* j
* 3);
99 if (fundamentalFrequency
* 4 < nyquist
)
100 reference
+= Math
.cos(phaseInc
* j
* 4);
102 // Scale the reference the same as the waveshaping curve itself.
105 var delta
= Math
.abs(actual
- reference
);
106 var deltaInDecibels
= delta
> 0 ? 20 * Math
.log(delta
)/Math
.log(10) : -200;
108 if (j
>= filterStabilizeSkipFrames
) {
109 if (deltaInDecibels
> worstDeltaInDecibels
) {
110 worstDeltaInDecibels
= deltaInDecibels
;
115 // console.log("worstDeltaInDecibels: " + worstDeltaInDecibels);
117 var success
= worstDeltaInDecibels
< acceptableAliasingThresholdDecibels
;
120 testPassed(oversample
+ " WaveShaperNode oversampling within acceptable tolerance.");
122 testFailed(oversample
+ " WaveShaperNode oversampling not within acceptable tolerance. Error = " + worstDeltaInDecibels
+ " dBFS");
128 function createImpulseBuffer(context
, sampleFrameLength
) {
129 var audioBuffer
= context
.createBuffer(1, sampleFrameLength
, context
.sampleRate
);
130 var n
= audioBuffer
.length
;
131 var dataL
= audioBuffer
.getChannelData(0);
133 for (var k
= 0; k
< n
; ++k
)
141 function runWaveShaperOversamplingTest(testParams
) {
142 sampleRate
= testParams
.sampleRate
;
143 nyquist
= 0.5 * sampleRate
;
144 oversample
= testParams
.oversample
;
145 fundamentalFrequency
= testParams
.fundamentalFrequency
;
146 acceptableAliasingThresholdDecibels
= testParams
.acceptableAliasingThresholdDecibels
;
148 if (window
.testRunner
) {
149 testRunner
.dumpAsText();
150 testRunner
.waitUntilDone();
153 window
.jsTestIsAsync
= true;
155 // Create offline audio context.
156 var numberOfRenderFrames
= sampleRate
* lengthInSeconds
;
157 context
= new OfflineAudioContext(1, numberOfRenderFrames
, sampleRate
);
159 // source -> waveshaper -> destination
160 var source
= context
.createBufferSource();
161 source
.buffer
= createToneBuffer(context
, fundamentalFrequency
, lengthInSeconds
, 1);
163 // Apply a non-linear distortion curve.
164 waveshaper
= context
.createWaveShaper();
165 waveshaper
.curve
= generateWaveShapingCurve();
166 waveshaper
.oversample
= oversample
;
168 source
.connect(waveshaper
);
169 waveshaper
.connect(context
.destination
);
173 context
.oncomplete
= checkShapedCurve
;
174 context
.startRendering();