michael@0: var sampleRate = 44100.0; michael@0: michael@0: var renderLengthSeconds = 8; michael@0: var pulseLengthSeconds = 1; michael@0: var pulseLengthFrames = pulseLengthSeconds * sampleRate; michael@0: michael@0: function createSquarePulseBuffer(context, sampleFrameLength) { michael@0: var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate); michael@0: michael@0: var n = audioBuffer.length; michael@0: var data = audioBuffer.getChannelData(0); michael@0: michael@0: for (var i = 0; i < n; ++i) michael@0: data[i] = 1; michael@0: michael@0: return audioBuffer; michael@0: } michael@0: michael@0: // The triangle buffer holds the expected result of the convolution. michael@0: // It linearly ramps up from 0 to its maximum value (at the center) michael@0: // then linearly ramps down to 0. The center value corresponds to the michael@0: // point where the two square pulses overlap the most. michael@0: function createTrianglePulseBuffer(context, sampleFrameLength) { michael@0: var audioBuffer = context.createBuffer(1, sampleFrameLength, context.sampleRate); michael@0: michael@0: var n = audioBuffer.length; michael@0: var halfLength = n / 2; michael@0: var data = audioBuffer.getChannelData(0); michael@0: michael@0: for (var i = 0; i < halfLength; ++i) michael@0: data[i] = i + 1; michael@0: michael@0: for (var i = halfLength; i < n; ++i) michael@0: data[i] = n - i - 1; michael@0: michael@0: return audioBuffer; michael@0: } michael@0: michael@0: function log10(x) { michael@0: return Math.log(x)/Math.LN10; michael@0: } michael@0: michael@0: function linearToDecibel(x) { michael@0: return 20*log10(x); michael@0: } michael@0: michael@0: // Verify that the rendered result is very close to the reference michael@0: // triangular pulse. michael@0: function checkTriangularPulse(rendered, reference) { michael@0: var match = true; michael@0: var maxDelta = 0; michael@0: var valueAtMaxDelta = 0; michael@0: var maxDeltaIndex = 0; michael@0: michael@0: for (var i = 0; i < reference.length; ++i) { michael@0: var diff = rendered[i] - reference[i]; michael@0: var x = Math.abs(diff); michael@0: if (x > maxDelta) { michael@0: maxDelta = x; michael@0: valueAtMaxDelta = reference[i]; michael@0: maxDeltaIndex = i; michael@0: } michael@0: } michael@0: michael@0: // allowedDeviationFraction was determined experimentally. It michael@0: // is the threshold of the relative error at the maximum michael@0: // difference between the true triangular pulse and the michael@0: // rendered pulse. michael@0: var allowedDeviationDecibels = -129.4; michael@0: var maxDeviationDecibels = linearToDecibel(maxDelta / valueAtMaxDelta); michael@0: michael@0: if (maxDeviationDecibels <= allowedDeviationDecibels) { michael@0: testPassed("Triangular portion of convolution is correct."); michael@0: } else { michael@0: testFailed("Triangular portion of convolution is not correct. Max deviation = " + maxDeviationDecibels + " dB at " + maxDeltaIndex); michael@0: match = false; michael@0: } michael@0: michael@0: return match; michael@0: } michael@0: michael@0: // Verify that the rendered data is close to zero for the first part michael@0: // of the tail. michael@0: function checkTail1(data, reference, breakpoint) { michael@0: var isZero = true; michael@0: var tail1Max = 0; michael@0: michael@0: for (var i = reference.length; i < reference.length + breakpoint; ++i) { michael@0: var mag = Math.abs(data[i]); michael@0: if (mag > tail1Max) { michael@0: tail1Max = mag; michael@0: } michael@0: } michael@0: michael@0: // Let's find the peak of the reference (even though we know a michael@0: // priori what it is). michael@0: var refMax = 0; michael@0: for (var i = 0; i < reference.length; ++i) { michael@0: refMax = Math.max(refMax, Math.abs(reference[i])); michael@0: } michael@0: michael@0: // This threshold is experimentally determined by examining the michael@0: // value of tail1MaxDecibels. michael@0: var threshold1 = -129.7; michael@0: michael@0: var tail1MaxDecibels = linearToDecibel(tail1Max/refMax); michael@0: if (tail1MaxDecibels <= threshold1) { michael@0: testPassed("First part of tail of convolution is sufficiently small."); michael@0: } else { michael@0: testFailed("First part of tail of convolution is not sufficiently small: " + tail1MaxDecibels + " dB"); michael@0: isZero = false; michael@0: } michael@0: michael@0: return isZero; michael@0: } michael@0: michael@0: // Verify that the second part of the tail of the convolution is michael@0: // exactly zero. michael@0: function checkTail2(data, reference, breakpoint) { michael@0: var isZero = true; michael@0: var tail2Max = 0; michael@0: // For the second part of the tail, the maximum value should be michael@0: // exactly zero. michael@0: var threshold2 = 0; michael@0: for (var i = reference.length + breakpoint; i < data.length; ++i) { michael@0: if (Math.abs(data[i]) > 0) { michael@0: isZero = false; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (isZero) { michael@0: testPassed("Rendered signal after tail of convolution is silent."); michael@0: } else { michael@0: testFailed("Rendered signal after tail of convolution should be silent."); michael@0: } michael@0: michael@0: return isZero; michael@0: } michael@0: michael@0: function checkConvolvedResult(trianglePulse) { michael@0: return function(event) { michael@0: var renderedBuffer = event.renderedBuffer; michael@0: michael@0: var referenceData = trianglePulse.getChannelData(0); michael@0: var renderedData = renderedBuffer.getChannelData(0); michael@0: michael@0: var success = true; michael@0: michael@0: // Verify the triangular pulse is actually triangular. michael@0: michael@0: success = success && checkTriangularPulse(renderedData, referenceData); michael@0: michael@0: // Make sure that portion after convolved portion is totally michael@0: // silent. But round-off prevents this from being completely michael@0: // true. At the end of the triangle, it should be close to michael@0: // zero. If we go farther out, it should be even closer and michael@0: // eventually zero. michael@0: michael@0: // For the tail of the convolution (where the result would be michael@0: // theoretically zero), we partition the tail into two michael@0: // parts. The first is the at the beginning of the tail, michael@0: // where we tolerate a small but non-zero value. The second part is michael@0: // farther along the tail where the result should be zero. michael@0: michael@0: // breakpoint is the point dividing the first two tail parts michael@0: // we're looking at. Experimentally determined. michael@0: var breakpoint = 12800; michael@0: michael@0: success = success && checkTail1(renderedData, referenceData, breakpoint); michael@0: michael@0: success = success && checkTail2(renderedData, referenceData, breakpoint); michael@0: michael@0: if (success) { michael@0: testPassed("Test signal was correctly convolved."); michael@0: } else { michael@0: testFailed("Test signal was not correctly convolved."); michael@0: } michael@0: michael@0: finishJSTest(); michael@0: } michael@0: }