<!DOCTYPE html>

<html>
<head>
<script src="../resources/js-test.js"></script>
<script type="text/javascript" src="resources/audio-testing.js"></script>
<script type="text/javascript" src="resources/buffer-loader.js"></script>
</head>

<body>

<div id="description"></div>
<div id="console"></div>

<script>
description("Tests that WaveShaperNode applies proper non-linear distortion.");

var sampleRate = 44100;
var lengthInSeconds = 4;
var numberOfRenderFrames = sampleRate * lengthInSeconds;
var numberOfCurveFrames = 65536;
var inputBuffer;
var waveShapingCurve;

var context;

function generateInputBuffer() {
    // Create mono input buffer.
    var buffer = context.createBuffer(1, numberOfRenderFrames, context.sampleRate);
    var data = buffer.getChannelData(0);
    
    // Generate an input vector with values from -1 -> +1 over a duration of lengthInSeconds.
    // This exercises the full nominal input range and will touch every point of the shaping curve.
    for (var i = 0; i < numberOfRenderFrames; ++i) {
        var x = i / numberOfRenderFrames; // 0 -> 1
        x = 2 * x - 1; // -1 -> +1
        data[i] = x;
    }

    return buffer;
}

// Generates a symmetric curve: Math.atan(5 * x) / (0.5 * Math.PI)
// (with x == 0 corresponding to the center of the array)
// This curve is arbitrary, but would be useful in the real-world.
// To some extent, the actual curve we choose is not important in this test,
// since the input vector walks through all possible curve values.
function generateWaveShapingCurve() {
    var curve = new Float32Array(numberOfCurveFrames);
    
    var n = numberOfCurveFrames;
    var n2 = n / 2;
    
    for (var i = 0; i < n; ++i) {
        var x = (i - n2) / n2;
        var y = Math.atan(5 * x) / (0.5 * Math.PI);
    }

    return curve;
}

function checkShapedCurve(event) {
    var buffer = event.renderedBuffer;

    var inputData = inputBuffer.getChannelData(0);
    var outputData = buffer.getChannelData(0);

    var success = true;
    
    // Go through every sample and make sure it has been shaped exactly according to the shaping curve we gave it.
    for (var i = 0; i < buffer.length; ++i) {
        var input = inputData[i];
        
        // Calculate an index based on input -1 -> +1 with 0 being at the center of the curve data.
        var index = Math.floor(numberOfCurveFrames * 0.5 * (input + 1));

        // Clip index to the input range of the curve.
        // This takes care of input outside of nominal range -1 -> +1
        index = index < 0 ? 0 : index;
        index = index > numberOfCurveFrames - 1 ? numberOfCurveFrames - 1 : index;

        var expectedOutput = waveShapingCurve[index];
        
        var output = outputData[i];
        
        if (output != expectedOutput) {
            success = false;
            break;
        }
    }

    if (success) {
        testPassed("WaveShaperNode properly applied non-linear distortion.");
    } else {
        testFailed("WaveShaperNode did not properly apply non-linear distortion.");
    }

    finishJSTest();
}

function runTest() {
    window.jsTestIsAsync = true;
        
    // Create offline audio context.
    context = new OfflineAudioContext(1, numberOfRenderFrames, sampleRate);
    
    // source -> waveshaper -> destination
    var source = context.createBufferSource();
    var waveshaper = context.createWaveShaper();
    source.connect(waveshaper);
    waveshaper.connect(context.destination);

    // Create an input test vector.
    inputBuffer = generateInputBuffer();
    source.buffer = inputBuffer;
    
    // We'll apply non-linear distortion according to this shaping curve.
    waveShapingCurve = generateWaveShapingCurve();
    waveshaper.curve = waveShapingCurve;
    
    source.start(0);
    
    context.oncomplete = checkShapedCurve;
    context.startRendering();
}

runTest();

</script>
</body>
</html>
