カスタム波形 | サウンドの視覚化

START / STOP
PeriodicWave
PeriodicWave
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';
        };

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

        // Create the instance of GainNode
        var gain = context.createGain();

        // for legacy browsers
        context.createPeriodicWave = context.createPeriodicWave || context.createWaveTable;

        // for the instance of OscillatorNode
        var oscillator = null;

        // for PeriodicWave
        // 1 (index 0) + 1 (Fundamental frequency) + 15 (harmonics)
        var TABLE_SIZE = 17;

        var reals = new Float32Array(TABLE_SIZE);
        var imags = new Float32Array(TABLE_SIZE);

        // Initialization
        for (var i = 0; i < TABLE_SIZE; i++) {
            reals[i] = 0;
            imags[i] = 0;
        }

        // No use
        reals[0] = 0;  // fixed
        imags[0] = 0;  // fixed

        // Fundamental Frequency
        reals[1] = document.getElementById('range-custom-real-1').valueAsNumber;
        imags[1] = document.getElementById('range-custom-imag-1').valueAsNumber;

        var getInstanceOfOscillatorNode = function() {
            // Create the instance of OscillatorNode
            var osc = context.createOscillator();

            // for legacy browsers
            osc.setPeriodicWave = osc.setPeriodicWave || osc.setWaveTable;
            osc.start           = osc.start           || osc.noteOn;
            osc.stop            = osc.stop            || osc.noteOff;

            osc.frequency.value = 440;

            // Create the instance of PeriodicWave
            var periodicwave = context.createPeriodicWave(reals, imags);

            osc.setPeriodicWave(periodicwave);

            console.log(osc.type);  // 'custom'

            // OscillatorNode (Input) -> GainNode (Volume)
            osc.connect(gain);

            displayProperties(periodicwave, 'periodicwave-properties', 'PeriodicWave');

            return osc;
        }

        // for drawing sound wave

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

        var timerids = [null, null];
        var interval = document.getElementById('range-draw-interval').valueAsNumber;

        var drawWave = function(canvas, canvasContext) {
            return function(domain) {
                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;

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

                // Draw sound wave
                canvasContext.beginPath();

                var data = null;

                switch (domain) {
                    case 'time' :
                        data = new Uint8Array(analyser.fftSize);
                        analyser.getByteTimeDomainData(data);

                        canvasContext.moveTo(paddingLeft, middle);

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

                            canvasContext.lineTo(x, y);

                            // 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);

                            // 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';

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

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

                        break;
                    case 'frequency' :
                        data = new Float32Array(analyser.frequencyBinCount / 4);
                        analyser.getFloatFrequencyData(data);

                        analyser.maxDecibels = 0;
                        analyser.minDecibels = -60;

                        var range = analyser.maxDecibels - analyser.minDecibels;

                        canvasContext.moveTo(paddingLeft, innerBottom);

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

                            canvasContext.lineTo(x, y);

                            // Frequency resolution
                            var fsDivN = context.sampleRate / analyser.fftSize;

                            // This value is the number of samples during 440 Hz
                            var n440Hz = Math.floor(440 / fsDivN);

                            // 440 Hz ?
                            if (i % n440Hz === 0) {
                                var f    = Math.floor(440 * (i / n440Hz));  // index -> frequency
                                var text = (f < 1000) ? (f + ' Hz') : ((f / 1000) + ' kHz');

                                // 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      = '13px "Times New Roman"';
                                canvasContext.fillText(text, (x - (canvasContext.measureText(text).width / 2)), (height - 3));
                            }
                        }

                        break;
                    default :
                        break;
                }

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

                switch (domain) {
                    case 'time' :
                        // 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      = '13px "Times New Roman"';
                        canvasContext.fillText(' 1.00', 3, paddingTop);
                        canvasContext.fillText(' 0.00', 3, middle);
                        canvasContext.fillText('-1.00', 3, innerBottom);

                        break;
                    case 'frequency' :
                        for (var i = analyser.minDecibels; i <= analyser.maxDecibels; i += 10) {
                            var gy = Math.floor(-1 * ((i - analyser.maxDecibels) / range) * innerHeight) + paddingTop;

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

                            // Draw text (Y)
                            canvasContext.fillStyle = 'rgba(255, 255, 255, 1.0)';
                            canvasContext.font      = '13px "Times New Roman"';
                            canvasContext.fillText((i + ' dB'), 3, gy);
                        }

                        break;
                    default :
                        break;
                }

                var self = arguments.callee;

                switch (domain) {
                    case 'time' :
                        timerids[0] = window.setTimeout(function() {
                            self(domain);
                        }, interval);

                        break;
                    case 'frequency' :
                        timerids[1] = window.setTimeout(function() {
                            self(domain);
                        }, interval);

                        break;
                    default :
                        break;
                }
            };
        };

        var canvases = {
            time     : null,
            spectrum : null
        };

        var contexts = {
            time     : null,
            spectrum : null
        };

        canvases.time     = document.querySelectorAll('canvas')[0];
        canvases.spectrum = document.querySelectorAll('canvas')[1];

        contexts.time     = canvases.time.getContext('2d');
        contexts.spectrum = canvases.spectrum.getContext('2d');

        var drawTimeDomain = drawWave(canvases.time,     contexts.time);
        var drawSpectrum   = drawWave(canvases.spectrum, contexts.spectrum);

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

        /*
         * Event Listener
         */

        // Start or Stop sound
        document.querySelector('button').addEventListener(EventWrapper.CLICK, function() {
            if (isStop) {
                // OscillatorNode (Input) ->) GainNode (Volume) -> AnalyserNode (Visualization) -> AudioDestinationNode (Output)
                oscillator = getInstanceOfOscillatorNode();
                gain.connect(analyser);
                analyser.connect(context.destination);

                // Start sound
                oscillator.start(0);

                // Start drawing sound wave
                drawTimeDomain('time');
                drawSpectrum('frequency');

                isStop = false;
                this.innerHTML = '<span class="icon-pause"></span>';
            } else {
                // Stop sound
                oscillator.stop(0);

                // Stop drawing sound wave
                if (timerids[0] !== null) {window.clearTimeout(timerids[0]); timerids[0] = null;}
                if (timerids[1] !== null) {window.clearTimeout(timerids[1]); timerids[1] = null;}

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

        // Control Draw Interval
        document.getElementById('range-draw-interval').addEventListener('input', function() {
            interval = this.valueAsNumber;
            document.getElementById('output-draw-interval').textContent = this.value;
        }, false);

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

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

        // Create the instanceof PeriodicWave
        for (var i = 0; i < (TABLE_SIZE - 1); i++) {
            document.getElementById('range-custom-real-' + (i + 1)).addEventListener('input', function() {
                if (oscillator instanceof OscillatorNode) {
                    var index = parseInt(this.id.replace('range-custom-real-', ''));
                    reals[index] = this.valueAsNumber;

                    var periodicwave = context.createPeriodicWave(reals, imags);
                    oscillator.setPeriodicWave(periodicwave);

                    document.getElementById('output-custom-real-' + index).textContent = this.value;
                }
            }, false);

            document.getElementById('range-custom-imag-' + (i + 1)).addEventListener('input', function() {
                if (oscillator instanceof OscillatorNode) {
                    var index = parseInt(this.id.replace('range-custom-imag-', ''));
                    imags[index] = this.valueAsNumber;

                    var periodicwave = context.createPeriodicWave(reals, imags);
                    oscillator.setPeriodicWave(periodicwave);

                    document.getElementById('output-custom-imag-' + index).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;
})();