エンベロープジェネレータ

ENVELOPE
START / STOP
gain (AudioParam)
gain (AudioParam)
PropertyValuehasOwnProperty
(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;
        }

        var displayProperties = function(node, tableid, caption) {
            var html = '<caption>' + caption + '</caption>';

            html += '<thead>';
            html += '<tr>';
            html += '<th scope="col">Property</th>';
            html += '<th scope="col">Value</th>';
            html += '<th scope="col">hasOwnProperty</th>';
            html += '</tr>';
            html += '</thead>';

            html += '<tbody>';

            for (var key in node) {
                html += '<tr>';
                html += '<td>' + key + '</td>';
                html += '<td>' + node[key] + '</td>';
                html += '<td>' + node.hasOwnProperty(key) + '</td>';
                html += '</tr>';
            }

            html += '</tbody>';

            document.getElementById(tableid).innerHTML = html;
            document.getElementById(tableid).parentNode.previousElementSibling.style.display = 'block';
        };

        // Create the instance of OscillatorNode
        var oscillator = context.createOscillator();

        // for legacy browsers
        context.createGain = context.createGain || context.createGainNode;

        // Create the instance of GainNode
        var eg           = context.createGain();  // for Envelope Generator
        var masterVolume = context.createGain();  // for Master Volume

        // for legacy browsers
        eg.gain.setTargetAtTime = eg.gain.setTargetAtTime || eg.gain.setTargetValueAtTime;

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

        // Parameters for Envelope Generator
        var egParams = {
            attack  : 0,
            decay   : 0,
            sustain : 0,
            release : 0
        };

        // Initialization
        egParams.attack  = document.getElementById('range-attack').valueAsNumber;
        egParams.decay   = document.getElementById('range-decay').valueAsNumber;
        egParams.sustain = document.getElementById('range-sustain').valueAsNumber;
        egParams.release = document.getElementById('range-release').valueAsNumber;

        // Draw gain property in the instance of GainNode
        var canvas        = document.querySelector('canvas');
        var canvasContext = canvas.getContext('2d');

        var intervalid  = null;
        var currentTime = 0;

        var drawGain = function(t) {
            var VALUE_OF_STOP = 1e-3;

            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;

            var x = (currentTime / t) + paddingLeft;
            var y = ((1 - eg.gain.value) * innerHeight) + paddingTop;

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

            if (currentTime === 0) {
                canvasContext.clearRect(0, 0, width, height);
                canvasContext.beginPath();
                // canvasContext.moveTo(paddingLeft, innerBottom);

                // 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.50', 3, middle);
                canvasContext.fillText('0.00', 3, innerBottom);
            }

            canvasContext.lineTo(x, y);
            canvasContext.stroke();

            currentTime = (x < (paddingLeft + innerWidth)) ? (currentTime + t) : 0;

            if (eg.gain.value < VALUE_OF_STOP) {
                // Stop sound
                oscillator.stop(0);

                if (intervalid !== null) {
                    window.clearInterval(intervalid);
                    intervalid = null;
                }

                isStop = true;
            }
        };

        /*
         * Event Listener
         */

        // Start or Stop sound
        document.querySelector('button').addEventListener(EventWrapper.START, function() {
            if (!isStop) {
                oscillator.stop(0);
            }

            // Create the instance of OscillatorNode
            oscillator = context.createOscillator();

            // for legacy browsers
            oscillator.start = oscillator.start || oscillator.noteOn;
            oscillator.stop  = oscillator.stop  || oscillator.noteOff;

            // OscillatorNode (Input) -> GainNode (Envelope Generator) -> GainNode (Master Volume) -> AudioDestinationNode (Output)
            oscillator.connect(eg);
            eg.connect(masterVolume);
            masterVolume.connect(context.destination);

            /*
             * Envelope Generator : Attack -> Decay -> Sustain
             */

            var t0      = context.currentTime;
            var t1      = t0 + egParams.attack;
            var t2      = egParams.decay;
            var t2Value = egParams.sustain;

            // Clear all shedules
            eg.gain.cancelScheduledValues(t0);

            // Start from gain = 0
            eg.gain.setValueAtTime(0, t0);

            // Attack : gain increases linearly until assigned time (t1)
            eg.gain.linearRampToValueAtTime(1, t1);

            // Decay -> Sustain : gain gradually decreases to value of sustain during decay time (t2) from assigned time (t1)
            eg.gain.setTargetAtTime(t2Value, t1, t2);

            // Start sound at t0
            oscillator.start(t0);

            // Clear
            if (intervalid !== null) {
                window.clearInterval(intervalid);
                intervalid = null;
            }

            // Draw the value of gain
            var interval = 50;

            intervalid = window.setInterval(function() {
                drawGain(interval);
            }, interval);

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

        document.querySelector('button').addEventListener(EventWrapper.END, function() {
            if (isStop) {
                return;
            }

            /*
             * Envelope Generator : Sustain -> Release
             */

            var t3 = context.currentTime;
            var t4 = egParams.release;

            // in the case of mouseup on the way of Attack or Decay
            eg.gain.cancelScheduledValues(t3);
            eg.gain.setValueAtTime(eg.gain.value, t3);

            // Release : gain gradually decreases to 0 during release time (t4) from assigned time (t3)
            eg.gain.setTargetAtTime(0, t3, t4);

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

        // Control Master Volume
        document.getElementById('range-volume').addEventListener('input', function() {
            var min = masterVolume.gain.minValue || 0;
            var max = masterVolume.gain.maxValue || 1;

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

        // Control Attack
        document.getElementById('range-attack').addEventListener('input', function() {
            if (this.valueAsNumber >= 0) {
                egParams.attack = this.valueAsNumber;
                document.getElementById('output-attack').textContent = this.value;
            }
        }, false);

        // Control Decay
        document.getElementById('range-decay').addEventListener('input', function() {
            if (this.valueAsNumber > 0) {
                egParams.decay = this.valueAsNumber;
                document.getElementById('output-decay').textContent = this.value;
            }
        }, false);

        // Control Sustain
        document.getElementById('range-sustain').addEventListener('input', function() {
            if (this.valueAsNumber > 0) {
                egParams.sustain = this.valueAsNumber;
                document.getElementById('output-sustain').textContent = this.value;
            }
        }, false);

        // Control Release
        document.getElementById('range-release').addEventListener('input', function() {
            if (this.valueAsNumber > 0) {
                egParams.release = this.valueAsNumber;
                document.getElementById('output-release').textContent = this.value;
            }
        }, false);

        /*
         * Display properties
         */

        displayProperties(eg.gain, 'gainnode-gain-properties', 'gain (AudioParam) ');
    };

    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;
})();