基本波形の生成 | ScriptProcessorNode

START / STOP
WAVE TYPE
(function() {

    var onDOMContentLoaded = function() {

        window.AudioContext = window.AudioContext || window.webkitAudioContext;

        try {
            // Create the instance of AudioContext
            var context = new AudioContext();
        } catch (error) {
            window.alert(error.message + ' : Please use Chrome or Safari.');
            return;
        }

        // for legacy browsers
        context.createScriptProcessor = context.createScriptProcessor || context.createJavaScriptNode;

        // for selecting optimized buffer size
        var getBufferSize = function() {
            if (/(Win(dows )?NT 6\.2)/.test(navigator.userAgent)) {
                return 1024;  // Windows 8
            } else if (/(Win(dows )?NT 6\.1)/.test(navigator.userAgent)) {
                return 1024;  // Windows 7
            } else if (/(Win(dows )?NT 6\.0)/.test(navigator.userAgent)) {
                return 2048;  // Windows Vista
            } else if (/Win(dows )?(NT 5\.1|XP)/.test(navigator.userAgent)) {
                return 4096;  // Windows XP
            } else if (/Mac|PPC/.test(navigator.userAgent)) {
                return 1024;  // Mac OS X
            } else if (/Linux/.test(navigator.userAgent)) {
                return 8192;  // Linux
            } else if (/iPhone|iPad|iPod/.test(navigator.userAgent)) {
                return 2048;  // iOS
            } else {
                return 16384;  // Otherwise
            }
        };

        // Create the instance of ScriptProcessorNode
        var processor = context.createScriptProcessor(getBufferSize(), 2, 2);

        // for iOS
        var dummy = context.createBufferSource();

        // Create the instance of AnalyserNode
        var analyser = context.createAnalyser();

        var canvas        = document.querySelector('canvas');
        var canvasContext = canvas.getContext('2d');

        var drawWave = function() {
            var width  = canvas.width;
            var height = canvas.height;

            var paddingTop    = 20;
            var paddingBottom = 20;
            var paddingLeft   = 30;
            var paddingRight  = 30;

            var innerWidth  = width  - paddingLeft - paddingRight;
            var innerHeight = height - paddingTop  - paddingBottom;
            var innerBottom = height - paddingBottom;

            var middle = (innerHeight / 2) + paddingTop;

            // Sampling period
            var period = 1 / context.sampleRate;

            // This value is the number of samples during 5 msec
            var n5msec = Math.floor(5 * Math.pow(10, -3) * context.sampleRate);

            // Get data for drawing sound wave
            var times = new Uint8Array(analyser.fftSize);
            analyser.getByteTimeDomainData(times);

            // Clear previous data
            canvasContext.clearRect(0, 0, width, height);

            // Draw sound wave
            canvasContext.beginPath();

            for (var i = 0, len = times.length; i < len; i++) {
                var x = Math.floor((i / len) * innerWidth) + paddingLeft;
                var y = Math.floor((1 - (times[i] / 255)) * innerHeight) + paddingTop;

                if (i === 0) {
                    canvasContext.moveTo(x, y);
                } else {
                    canvasContext.lineTo(x, y);
                }

                // 5 msec ?
                if ((i % n5msec) === 0) {
                    var sec  = i * period;             // index -> time
                    var msec = sec * Math.pow(10, 3);  // sec -> msec
                    var text = Math.round(msec) + ' msec';

                    // Draw grid (X)
                    canvasContext.fillStyle = 'rgba(255, 0, 0, 1.0)';
                    canvasContext.fillRect(x, paddingTop, 1, innerHeight);

                    // Draw text (X)
                    canvasContext.fillStyle = 'rgba(255, 255, 255, 1.0)';
                    canvasContext.font      = '16px "Times New Roman"';
                    canvasContext.fillText(text, (x - (canvasContext.measureText(text).width / 2)), (height - 3));
                }
            }

            canvasContext.strokeStyle = 'rgba(0, 0, 255, 1.0)';
            canvasContext.lineWidth   = 2;
            canvasContext.lineCap     = 'round';
            canvasContext.lineJoin    = 'miter';
            canvasContext.stroke();

            // Draw grid (Y)
            canvasContext.fillStyle = 'rgba(255, 0, 0, 1.0)';
            canvasContext.fillRect(paddingLeft, paddingTop,  innerWidth, 1);
            canvasContext.fillRect(paddingLeft, middle,      innerWidth, 1);
            canvasContext.fillRect(paddingLeft, innerBottom, innerWidth, 1);

            // Draw text (Y)
            canvasContext.fillStyle = 'rgba(255, 255, 255, 1.0)';
            canvasContext.font      = '16px "Times New Roman"';
            canvasContext.fillText(' 1.00', 3, paddingTop);
            canvasContext.fillText(' 0.00', 3, middle);
            canvasContext.fillText('-1.00', 3, innerBottom);
        };

        // Sound parameters
        var volume    = document.getElementById('range-volume').valueAsNumber;
        var type      = document.forms['form-wave-type'].elements['radio-wave-type'][0].value;
        var frequency = document.getElementById('range-frequency').valueAsNumber;

        // Flag for starting or stopping sound
        var isStop = true;

        /*
         * Event Listener
         */

        // Start or Stop sound
        document.querySelector('button').addEventListener(EventWrapper.CLICK, async function() {
            await context.resume();
           
            if (isStop) {
                // Start onaudioprocess event

                // (AudioBufferSourceNode ->) ScriptProcessorNode (Input) -> AnalyserNode (Visualization) -> AudioDestinationNode (Output)
                dummy.connect(processor);
                processor.connect(analyser);
                analyser.connect(context.destination);

                var fs = context.sampleRate;  // Sampling frequency
                var n  = 0;                   // Phase

                processor.onaudioprocess = function(event) {
                    // Get the instance of Float32Array for output data (Array size equals buffer size)
                    var outputLs = event.outputBuffer.getChannelData(0);  // Left  channel
                    var outputRs = event.outputBuffer.getChannelData(1);  // Right channel

                    for (var i = 0; i < this.bufferSize; i++) {
                        // Fundamental period
                        var t0 = fs / frequency;

                        var output = 0;

                        switch (type) {
                            case 'sine' :
                                output = Math.sin((2 * Math.PI * frequency * n) / fs);
                                break;
                            case 'square' :
                                output = (n < (t0 / 2)) ? 1 : -1;
                                break;
                            case 'sawtooth' :
                                var s = 2 * n / t0;
                                output = s - 1;
                                break;
                            case 'triangle' :
                                var s = 4 * n / t0;
                                output = (n < (t0 / 2)) ? (-1 + s) : (3 - s);
                                break;
                            default :
                                break;
                        }

                        // Output sound
                        outputLs[i] = volume * output;
                        outputRs[i] = volume * output;

                        // Update phase
                        n++;

                        // Exceed fundamental period ?
                        if (n >= t0) {
                            n = 0;
                        }
                    }

                    // Draw sound wave
                    drawWave();
                };

                isStop = false;
                this.innerHTML = '<span class="icon-pause"></span>';
            } else {
                // Stop onaudioprocess event
                processor.disconnect();
                processor.onaudioprocess = null;

                isStop = true;
                this.innerHTML = '<span class="icon-start"></span>';
            }
        }, false);

        // Control Volume (without GainNode)
        document.getElementById('range-volume').addEventListener('input', function() {
            var min = 0;
            var max = 1;

            if ((this.valueAsNumber >= min) && (this.valueAsNumber <= max)) {
                volume = this.valueAsNumber;
                document.getElementById('output-volume').textContent = this.value;
            }
        }, false);

        // Select Wave Type
        document.getElementById('form-wave-type').addEventListener('change', function() {
            for (var i = 0, len = this.elements['radio-wave-type'].length; i < len; i++) {
                if (this.elements['radio-wave-type'][i].checked) {
                    type = this.elements['radio-wave-type'][i].value;
                    break;
                }
            }
        }, false);

        // Control Frequency
        document.getElementById('range-frequency').addEventListener('input', function() {
            var min = 0;

            if (this.valueAsNumber >= min) {
                frequency = this.valueAsNumber;
                document.getElementById('output-frequency').textContent = this.value;
            }
        }, false);
    };

    if ((document.readyState === 'interactive') || (document.readyState === 'complete')) {
        onDOMContentLoaded();
    } else {
        document.addEventListener('DOMContentLoaded', onDOMContentLoaded, true);
    }

})();
function EventWrapper(){
}

(function(){
    var click = '';
    var start = '';
    var move  = '';
    var end   = '';

    // Touch Panel ?
    if (/iPhone|iPad|iPod|Android/.test(navigator.userAgent)) {
        click = 'click';
        start = 'touchstart';
        move  = 'touchmove';
        end   = 'touchend';
    } else {
        click = 'click';
        start = 'mousedown';
        move  = 'mousemove';
        end   = 'mouseup';
    }

    EventWrapper.CLICK = click;
    EventWrapper.START = start;
    EventWrapper.MOVE  = move;
    EventWrapper.END   = end;
})();