content/media/webaudio/test/blink/panner-model-testing.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/content/media/webaudio/test/blink/panner-model-testing.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,209 @@
     1.4 +var sampleRate = 48000.0;
     1.5 +
     1.6 +var numberOfChannels = 1;
     1.7 +
     1.8 +// Time step when each panner node starts.
     1.9 +var timeStep = 0.001;
    1.10 +
    1.11 +// Length of the impulse signal.
    1.12 +var pulseLengthFrames = Math.round(timeStep * sampleRate);
    1.13 +
    1.14 +// How many panner nodes to create for the test
    1.15 +var nodesToCreate = 100;
    1.16 +
    1.17 +// Be sure we render long enough for all of our nodes.
    1.18 +var renderLengthSeconds = timeStep * (nodesToCreate + 1);
    1.19 +
    1.20 +// These are global mostly for debugging.
    1.21 +var context;
    1.22 +var impulse;
    1.23 +var bufferSource;
    1.24 +var panner;
    1.25 +var position;
    1.26 +var time;
    1.27 +      
    1.28 +var renderedBuffer;
    1.29 +var renderedLeft;
    1.30 +var renderedRight;
    1.31 +
    1.32 +function createGraph(context, nodeCount) {
    1.33 +    bufferSource = new Array(nodeCount);
    1.34 +    panner = new Array(nodeCount);
    1.35 +    position = new Array(nodeCount);
    1.36 +    time = new Array(nodeCount);
    1.37 +    // Angle between panner locations.  (nodeCount - 1 because we want
    1.38 +    // to include both 0 and 180 deg.
    1.39 +    var angleStep = Math.PI / (nodeCount - 1);
    1.40 +
    1.41 +    if (numberOfChannels == 2) {
    1.42 +        impulse = createStereoImpulseBuffer(context, pulseLengthFrames);
    1.43 +    }
    1.44 +    else
    1.45 +        impulse = createImpulseBuffer(context, pulseLengthFrames);
    1.46 +
    1.47 +    for (var k = 0; k < nodeCount; ++k) {
    1.48 +        bufferSource[k] = context.createBufferSource();
    1.49 +        bufferSource[k].buffer = impulse;
    1.50 +
    1.51 +        panner[k] = context.createPanner();
    1.52 +        panner[k].panningModel = "equalpower";
    1.53 +        panner[k].distanceModel = "linear";
    1.54 +
    1.55 +        var angle = angleStep * k;
    1.56 +        position[k] = {angle : angle, x : Math.cos(angle), z : Math.sin(angle)};
    1.57 +        panner[k].setPosition(position[k].x, 0, position[k].z);
    1.58 +
    1.59 +        bufferSource[k].connect(panner[k]);
    1.60 +        panner[k].connect(context.destination);
    1.61 +
    1.62 +        // Start the source
    1.63 +        time[k] = k * timeStep;
    1.64 +        bufferSource[k].start(time[k]);
    1.65 +    }
    1.66 +}
    1.67 +
    1.68 +function createTestAndRun(context, nodeCount, numberOfSourceChannels) {
    1.69 +    numberOfChannels = numberOfSourceChannels;
    1.70 +
    1.71 +    createGraph(context, nodeCount);
    1.72 +
    1.73 +    context.oncomplete = checkResult;
    1.74 +    context.startRendering();
    1.75 +}
    1.76 +
    1.77 +// Map our position angle to the azimuth angle (in degrees).
    1.78 +//
    1.79 +// An angle of 0 corresponds to an azimuth of 90 deg; pi, to -90 deg.
    1.80 +function angleToAzimuth(angle) {
    1.81 +    return 90 - angle * 180 / Math.PI;
    1.82 +}
    1.83 +
    1.84 +// The gain caused by the EQUALPOWER panning model
    1.85 +function equalPowerGain(angle) {
    1.86 +    var azimuth = angleToAzimuth(angle);
    1.87 +
    1.88 +    if (numberOfChannels == 1) {
    1.89 +        var panPosition = (azimuth + 90) / 180;
    1.90 +
    1.91 +        var gainL = Math.cos(0.5 * Math.PI * panPosition);
    1.92 +        var gainR = Math.sin(0.5 * Math.PI * panPosition);
    1.93 +
    1.94 +        return { left : gainL, right : gainR };
    1.95 +    } else {
    1.96 +        if (azimuth <= 0) {
    1.97 +            var panPosition = (azimuth + 90) / 90;
    1.98 +    
    1.99 +            var gainL = 1 + Math.cos(0.5 * Math.PI * panPosition);
   1.100 +            var gainR = Math.sin(0.5 * Math.PI * panPosition);
   1.101 +    
   1.102 +            return { left : gainL, right : gainR };
   1.103 +        } else {
   1.104 +            var panPosition = azimuth / 90;
   1.105 +    
   1.106 +            var gainL = Math.cos(0.5 * Math.PI * panPosition);
   1.107 +            var gainR = 1 + Math.sin(0.5 * Math.PI * panPosition);
   1.108 +    
   1.109 +            return { left : gainL, right : gainR };
   1.110 +        }
   1.111 +    }
   1.112 +}
   1.113 +
   1.114 +function checkResult(event) {
   1.115 +    renderedBuffer = event.renderedBuffer;
   1.116 +    renderedLeft = renderedBuffer.getChannelData(0);
   1.117 +    renderedRight = renderedBuffer.getChannelData(1);
   1.118 +
   1.119 +    // The max error we allow between the rendered impulse and the
   1.120 +    // expected value.  This value is experimentally determined.  Set
   1.121 +    // to 0 to make the test fail to see what the actual error is.
   1.122 +    var maxAllowedError = 1.3e-6;
   1.123 +  
   1.124 +    var success = true;
   1.125 +
   1.126 +    // Number of impulses found in the rendered result.
   1.127 +    var impulseCount = 0;
   1.128 +
   1.129 +    // Max (relative) error and the index of the maxima for the left
   1.130 +    // and right channels.
   1.131 +    var maxErrorL = 0;
   1.132 +    var maxErrorIndexL = 0;
   1.133 +    var maxErrorR = 0;
   1.134 +    var maxErrorIndexR = 0;
   1.135 +
   1.136 +    // Number of impulses that don't match our expected locations.
   1.137 +    var timeCount = 0;
   1.138 +
   1.139 +    // Locations of where the impulses aren't at the expected locations.
   1.140 +    var timeErrors = new Array();
   1.141 +
   1.142 +    for (var k = 0; k < renderedLeft.length; ++k) {
   1.143 +        // We assume that the left and right channels start at the same instant.
   1.144 +        if (renderedLeft[k] != 0 || renderedRight[k] != 0) {
   1.145 +            // The expected gain for the left and right channels.
   1.146 +            var pannerGain = equalPowerGain(position[impulseCount].angle);
   1.147 +            var expectedL = pannerGain.left;
   1.148 +            var expectedR = pannerGain.right;
   1.149 +
   1.150 +            // Absolute error in the gain.
   1.151 +            var errorL = Math.abs(renderedLeft[k] - expectedL);
   1.152 +            var errorR = Math.abs(renderedRight[k] - expectedR);
   1.153 +
   1.154 +            if (Math.abs(errorL) > maxErrorL) {
   1.155 +                maxErrorL = Math.abs(errorL);
   1.156 +                maxErrorIndexL = impulseCount;
   1.157 +            }
   1.158 +            if (Math.abs(errorR) > maxErrorR) {
   1.159 +                maxErrorR = Math.abs(errorR);
   1.160 +                maxErrorIndexR = impulseCount;
   1.161 +            }
   1.162 +
   1.163 +            // Keep track of the impulses that didn't show up where we
   1.164 +            // expected them to be.
   1.165 +            var expectedOffset = timeToSampleFrame(time[impulseCount], sampleRate);
   1.166 +            if (k != expectedOffset) {
   1.167 +                timeErrors[timeCount] = { actual : k, expected : expectedOffset};
   1.168 +                ++timeCount;
   1.169 +            }
   1.170 +            ++impulseCount;
   1.171 +        }
   1.172 +    }
   1.173 +
   1.174 +    if (impulseCount == nodesToCreate) {
   1.175 +        testPassed("Number of impulses matches the number of panner nodes.");
   1.176 +    } else {
   1.177 +        testFailed("Number of impulses is incorrect.  (Found " + impulseCount + " but expected " + nodesToCreate + ")");
   1.178 +        success = false;
   1.179 +    }
   1.180 +
   1.181 +    if (timeErrors.length > 0) {
   1.182 +        success = false;
   1.183 +        testFailed(timeErrors.length + " timing errors found in " + nodesToCreate + " panner nodes.");
   1.184 +        for (var k = 0; k < timeErrors.length; ++k) {
   1.185 +            testFailed("Impulse at sample " + timeErrors[k].actual + " but expected " + timeErrors[k].expected);
   1.186 +        }
   1.187 +    } else {
   1.188 +        testPassed("All impulses at expected offsets.");
   1.189 +    }
   1.190 +
   1.191 +    if (maxErrorL <= maxAllowedError) {
   1.192 +        testPassed("Left channel gain values are correct.");
   1.193 +    } else {
   1.194 +        testFailed("Left channel gain values are incorrect.  Max error = " + maxErrorL + " at time " + time[maxErrorIndexL] + " (threshold = " + maxAllowedError + ")");
   1.195 +        success = false;
   1.196 +    }
   1.197 +
   1.198 +    if (maxErrorR <= maxAllowedError) {
   1.199 +        testPassed("Right channel gain values are correct.");
   1.200 +    } else {
   1.201 +        testFailed("Right channel gain values are incorrect.  Max error = " + maxErrorR + " at time " + time[maxErrorIndexR] + " (threshold = " + maxAllowedError + ")");
   1.202 +        success = false;
   1.203 +    }
   1.204 +
   1.205 +    if (success) {
   1.206 +        testPassed("EqualPower panner test passed");
   1.207 +    } else {
   1.208 +        testFailed("EqualPower panner test failed");
   1.209 +    }
   1.210 +
   1.211 +    finishJSTest();
   1.212 +}

mercurial