オーディオデータの波形描画 (SVG)

LEFT CHANNEL
RIGHT CHANNEL
REAL TIME (TIME DOMAIN)
REAL TIME (FREQUENCY DOMAIN)
LOOP
(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 XMLNS = 'http://www.w3.org/2000/svg';

        // for the instance of AudioBufferSourceNode
        var source = null;

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

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

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

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

        var drawAudio = function(svg, data, sampleRate) {
            var width  = parseInt(svg.getAttribute('width'));
            var height = parseInt(svg.getAttribute('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 / sampleRate;

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

            // This value is the number of samples during 60 sec
            var n60sec = Math.floor(60 * sampleRate);

            // for audio wave
            var waveStyle = 'stroke : rgba(0, 0, 255, 1.0); fill : none; stroke-width : 0.5; stroke-linecap : round; stroke-linejoin : miter;';

            // Clear previous data
            svg.innerHTML = '';

            // Create element
            var path = document.createElementNS(XMLNS, 'path');

            // Draw audio wave
            var d = '';

            for (var i = 0, len = data.length; i < len; i++) {
                // 50 msec ?
                if ((i % n50msec) === 0) {
                    var x = Math.floor((i / len) * innerWidth) + paddingLeft;
                    var y = Math.floor(((1 - data[i]) / 2) * innerHeight) + paddingTop;

                    if (i === 0) {
                        d += 'M' + x + ' ' + y;
                    } else {
                        d += ' ';
                        d += 'L' + x + ' ' + y;
                    }
                }
            }

            path.setAttribute('stroke',          'rgba(0, 0, 255, 1.0)');
            path.setAttribute('fill',            'none');
            path.setAttribute('stroke-width',    '0.5');
            path.setAttribute('stroke-linecap',  'round');
            path.setAttribute('stroke-linejoin', 'miter');
            path.setAttribute('d',                d);

            svg.appendChild(path);

            // Draw grid and text (X)
            for (var i = 0, len = data.length; i < len; i++) {
                // 60 sec ?
                if ((i % n60sec) === 0) {
                    var x   = Math.floor((i / len) * innerWidth) + paddingLeft;
                    var sec = i * period;  // index -> time

                    // Draw grid (X)
                    var rect = document.createElementNS(XMLNS, 'rect');

                    rect.setAttribute('x',      x);
                    rect.setAttribute('y',      paddingTop);
                    rect.setAttribute('width',  1);
                    rect.setAttribute('height', innerHeight);
                    rect.setAttribute('stroke', 'none');
                    rect.setAttribute('fill',   'rgba(255, 0, 0, 1.0)');

                    svg.appendChild(rect);

                    // Draw text (X)
                    var text = document.createElementNS(XMLNS, 'text');

                    text.textContent = Math.floor(sec) + ' sec';

                    text.setAttribute('x',           x);
                    text.setAttribute('y',           (height - 3));
                    text.setAttribute('stroke',      'none');
                    text.setAttribute('fill',        'rgba(255, 255, 255, 1.0)');
                    text.setAttribute('text-anchor', 'middle');
                    text.setAttribute('font-family', 'Times New Roman');
                    text.setAttribute('font-size',   '16px');
                    text.setAttribute('font-style',  'normal');
                    text.setAttribute('font-weight', 'normal');

                    svg.appendChild(text);
                }
            }

            // Draw grid (Y)
            [middle, paddingTop, innerBottom].forEach(function(y) {
                var rect = document.createElementNS(XMLNS, 'rect');

                rect.setAttribute('x',      paddingLeft);
                rect.setAttribute('y',      y);
                rect.setAttribute('width',  innerWidth);
                rect.setAttribute('height', 1);
                rect.setAttribute('stroke', 'none');
                rect.setAttribute('fill',   'rgba(255, 0, 0, 1.0)');

                svg.appendChild(rect);
            });

            // Draw text (Y)
            [paddingTop, middle, innerBottom].forEach(function(y) {
                var text = document.createElementNS(XMLNS, 'text');

                switch (y) {
                    case paddingTop :
                        text.textContent = ' 1.00';
                        break;
                    case middle  :
                        text.textContent = ' 0.00';
                        break;
                    case innerBottom :
                        text.textContent = '-1.00';
                        break;
                    default :
                        break;
                }

                text.setAttribute('x',           18);
                text.setAttribute('y',           y);
                text.setAttribute('stroke',      'none');
                text.setAttribute('fill',        'rgba(255, 255, 255, 1.0)');
                text.setAttribute('text-anchor', 'middle');
                text.setAttribute('font-family', 'Times New Roman');
                text.setAttribute('font-size',   '16px');
                text.setAttribute('font-style',  'normal');
                text.setAttribute('font-weight', 'normal');

                svg.appendChild(text);
            });
        };

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

        svgs.time     = document.querySelectorAll('svg')[2];
        svgs.spectrum = document.querySelectorAll('svg')[3];

        var drawTimeDomain = function(sampleRate) {
            var svg = svgs.time;

            var width  = parseInt(svg.getAttribute('width'));
            var height = parseInt(svg.getAttribute('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 / sampleRate;

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

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

            // Sampling period
            var period = 1 / sampleRate;

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

            // Clear previous data
            svg.innerHTML = '';

            // Create element
            var path = document.createElementNS(XMLNS, 'path');

            // Draw audio wave
            var d = '';

            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) {
                    d += 'M' + x + ' ' + y;
                } else {
                    d += ' ';
                    d += 'L' + x + ' ' + y;
                }
            }

            path.setAttribute('stroke',          'rgba(0, 0, 255, 1.0)');
            path.setAttribute('fill',            'none');
            path.setAttribute('stroke-width',    '1.5');
            path.setAttribute('stroke-linecap',  'round');
            path.setAttribute('stroke-linejoin', 'miter');
            path.setAttribute('d',                d);

            svg.appendChild(path);

            for (var i = 0, len = times.length; i < len; i++) {
                // 5 msec ?
                if ((i % n5msec) === 0) {
                    var x = Math.floor((i / len) * innerWidth) + paddingLeft;

                    var sec  = i * period;             // index -> time
                    var msec = sec * Math.pow(10, 3);  // sec -> msec

                    // Draw grid (X)
                    var rect = document.createElementNS(XMLNS, 'rect');

                    rect.setAttribute('x',      x);
                    rect.setAttribute('y',      paddingTop);
                    rect.setAttribute('width',  1);
                    rect.setAttribute('height', innerHeight);
                    rect.setAttribute('stroke', 'none');
                    rect.setAttribute('fill',   'rgba(255, 0, 0, 1.0)');

                    svg.appendChild(rect);

                    // Draw text (X)
                    var text = document.createElementNS(XMLNS, 'text');

                    text.textContent = Math.round(msec) + ' msec';

                    text.setAttribute('x',           x);
                    text.setAttribute('y',           (height - 3));
                    text.setAttribute('stroke',      'none');
                    text.setAttribute('fill',        'rgba(255, 255, 255, 1.0)');
                    text.setAttribute('text-anchor', 'middle');
                    text.setAttribute('font-family', 'Times New Roman');
                    text.setAttribute('font-size',   '16px');
                    text.setAttribute('font-style',  'normal');
                    text.setAttribute('font-weight', 'normal');

                    svg.appendChild(text);
                }
            }

            // Draw grid (Y)
            [middle, paddingTop, innerBottom].forEach(function(y) {
                var rect = document.createElementNS(XMLNS, 'rect');

                rect.setAttribute('x',      paddingLeft);
                rect.setAttribute('y',      y);
                rect.setAttribute('width',  innerWidth);
                rect.setAttribute('height', 1);
                rect.setAttribute('stroke', 'none');
                rect.setAttribute('fill',   'rgba(255, 0, 0, 1.0)');

                svg.appendChild(rect);
            });

            // Draw text (Y)
            [paddingTop, middle, innerBottom].forEach(function(y) {
                var text = document.createElementNS(XMLNS, 'text');

                switch (y) {
                    case paddingTop :
                        text.textContent = ' 1.00';
                        break;
                    case middle  :
                        text.textContent = ' 0.00';
                        break;
                    case innerBottom :
                        text.textContent = '-1.00';
                        break;
                    default :
                        break;
                }

                text.setAttribute('x',           18);
                text.setAttribute('y',           y);
                text.setAttribute('stroke',      'none');
                text.setAttribute('fill',        'rgba(255, 255, 255, 1.0)');
                text.setAttribute('text-anchor', 'middle');
                text.setAttribute('font-family', 'Times New Roman');
                text.setAttribute('font-size',   '16px');
                text.setAttribute('font-style',  'normal');
                text.setAttribute('font-weight', 'normal');

                svg.appendChild(text);
            });

            timerids[0] = window.setTimeout(function() {
                drawTimeDomain(sampleRate);
            }, interval);
        };

        var drawSpectrum = function(sampleRate) {
            var svg = svgs.spectrum

            var width  = parseInt(svg.getAttribute('width'));
            var height = parseInt(svg.getAttribute('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;

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

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

            // Get data for drawing spectrum
            var spectrums = new Uint8Array(analyser.frequencyBinCount / 4);
            analyser.getByteFrequencyData(spectrums);

            // Clear previous data
            svg.innerHTML = '';

            // Create element
            var path = document.createElementNS(XMLNS, 'path');

            // Draw audio wave
            var d = '';

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

                if (i === 0) {
                    d += 'M' + x + ' ' + y;
                } else {
                    d += ' ';
                    d += 'L' + x + ' ' + y;
                }
            }

            path.setAttribute('stroke',          'rgba(0, 0, 255, 1.0)');
            path.setAttribute('fill',            'none');
            path.setAttribute('stroke-width',    '1.5');
            path.setAttribute('stroke-linecap',  'round');
            path.setAttribute('stroke-linejoin', 'miter');
            path.setAttribute('d',                d);

            svg.appendChild(path);

            // Draw grid and text (X)

            for (var i = 0, len = spectrums.length; i < len; i++) {
                // 1000 Hz ?
                if ((i % n1kHz) === 0) {
                    var x = Math.floor((i / len) * innerWidth) + paddingLeft;
                    var f = Math.floor(1000 * (i / n1kHz));  // index -> frequency

                    // Draw grid (X)
                    var rect = document.createElementNS(XMLNS, 'rect');

                    rect.setAttribute('x',      x);
                    rect.setAttribute('y',      paddingTop);
                    rect.setAttribute('width',  1);
                    rect.setAttribute('height', innerHeight);
                    rect.setAttribute('stroke', 'none');
                    rect.setAttribute('fill',   'rgba(255, 0, 0, 1.0)');

                    svg.appendChild(rect);

                    // Draw text (X)
                    var text = document.createElementNS(XMLNS, 'text');

                    text.textContent = (f < 1000) ? (f + ' Hz') : ((f / 1000) + ' kHz');

                    text.setAttribute('x',           x);
                    text.setAttribute('y',           (height - 3));
                    text.setAttribute('stroke',      'none');
                    text.setAttribute('fill',        'rgba(255, 255, 255, 1.0)');
                    text.setAttribute('text-anchor', 'middle');
                    text.setAttribute('font-family', 'Times New Roman');
                    text.setAttribute('font-size',   '16px');
                    text.setAttribute('font-style',  'normal');
                    text.setAttribute('font-weight', 'normal');

                    svg.appendChild(text);
                }
            }

            // Draw grid (Y)
            [middle, paddingTop, innerBottom].forEach(function(y) {
                var rect = document.createElementNS(XMLNS, 'rect');

                rect.setAttribute('x',      paddingLeft);
                rect.setAttribute('y',      y);
                rect.setAttribute('width',  innerWidth);
                rect.setAttribute('height', 1);
                rect.setAttribute('stroke', 'none');
                rect.setAttribute('fill',   'rgba(255, 0, 0, 1.0)');

                svg.appendChild(rect);
            });

            // Draw text (Y)
            [paddingTop, middle, innerBottom].forEach(function(y) {
                var text = document.createElementNS(XMLNS, 'text');

                switch (y) {
                    case paddingTop :
                        text.textContent = '1.00';
                        break;
                    case middle  :
                        text.textContent = '0.50';
                        break;
                    case innerBottom :
                        text.textContent = '0.00';
                        break;
                    default :
                        break;
                }

                text.setAttribute('x',           18);
                text.setAttribute('y',           y);
                text.setAttribute('stroke',      'none');
                text.setAttribute('fill',        'rgba(255, 255, 255, 1.0)');
                text.setAttribute('text-anchor', 'middle');
                text.setAttribute('font-family', 'Times New Roman');
                text.setAttribute('font-size',   '16px');
                text.setAttribute('font-style',  'normal');
                text.setAttribute('font-weight', 'normal');

                svg.appendChild(text);
            });

            timerids[1] = window.setTimeout(function() {
                drawSpectrum(sampleRate);
            }, interval);
        };

        // Trigger 'ended' event
        var trigger = function() {
            var event = document.createEvent('Event');
            event.initEvent('ended', true, true);

            if (source instanceof AudioBufferSourceNode) {
                source.dispatchEvent(event);
            }
        };

        // This funciton is executed after getting ArrayBuffer of audio data
        var startAudio = function(arrayBuffer) {

            // The 2nd argument for decodeAudioData
            var successCallback = function(audioBuffer) {
                // The 1st argument (audioBuffer) is the instance of AudioBuffer

                // Get audio binary data for drawing wave
                var channelLs = new Float32Array(audioBuffer.length);
                var channelRs = new Float32Array(audioBuffer.length);

                console.log('numberOfChannels : ' + audioBuffer.numberOfChannels);

                // Stereo ?
                if (audioBuffer.numberOfChannels > 1) {
                    // Stereo
                    channelLs.set(audioBuffer.getChannelData(0));
                    channelRs.set(audioBuffer.getChannelData(1));

                    drawAudio(document.querySelectorAll('svg')[0], channelLs, audioBuffer.sampleRate);
                    drawAudio(document.querySelectorAll('svg')[1], channelRs, audioBuffer.sampleRate);
                } else if (audioBuffer.numberOfChannels > 0) {
                    // Monaural
                    channelLs.set(audioBuffer.getChannelData(0));
                    drawAudio(document.querySelectorAll('svg')[0], channelLs, audioBuffer.sampleRate);
                } else {
                    window.alert('The number of channels is invalid.');
                    return;
                }

                // If there is previous AudioBufferSourceNode, program stops previous audio
                if ((source instanceof AudioBufferSourceNode) && (source.buffer instanceof AudioBuffer)) {
                    // Execute onended event handler
                    trigger();
                    source = null;
                }

                // Create the instance of AudioBufferSourceNode
                source = context.createBufferSource();

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

                // Set the instance of AudioBuffer
                source.buffer = audioBuffer;

                // Set parameters
                source.playbackRate.value = document.getElementById('range-playback-rate').valueAsNumber;
                source.loop               = document.querySelector('[type="checkbox"]').checked;

                // AudioBufferSourceNode (Input) -> GainNode (Volume) -> AnalyserNode (Visualization) -> AudioDestinationNode (Output)
                source.connect(gain);
                gain.connect(analyser);
                analyser.connect(context.destination);

                // Start audio
                source.start(0);

                // Draw wave (Real Time)
                drawTimeDomain(audioBuffer.sampleRate);
                drawSpectrum(audioBuffer.sampleRate);

                // Set Callback
                source.onended = function(event) {
                    // Remove event handler
                    source.onended     = null;
                    document.onkeydown = null;

                    // Stop audio
                    source.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;}

                    console.log('STOP by "on' + event.type + '" event handler !!');

                    // Audio is not started !!
                    // It is necessary to create the instance of AudioBufferSourceNode again

                    // Cannot replay
                    // source.start(0);
                };

                // Stop audio
                document.onkeydown = function(event) {
                    // Space ?
                    if (event.keyCode !== 32) {
                        return;
                    }

                    // Execute onended event handler
                    trigger();

                    return false;
                };
            };

            // The 3rd argument for decodeAudioData
            var errorCallback = function(error) {
                if (error instanceof Error) {
                    window.alert(error.message);
                } else {
                    window.alert('Error : "decodeAudioData" method.');
                }
            };

            // Create the instance of AudioBuffer (Asynchronously)
            context.decodeAudioData(arrayBuffer, successCallback, errorCallback);
        };

        /*
         * File Uploader
         */

        document.getElementById('file-upload-audio').addEventListener('change', function(event) {
            var uploader     = this;
            var progressArea = document.getElementById('progress-file-upload-audio');

            // Get the instance of File (extends Blob)
            var file = event.target.files[0];

            if (!(file instanceof File)) {
                window.alert('Please upload file.');
            } else if (file.type.indexOf('audio') === -1) {
                window.alert('Please upload audio file.');
            } else {
                // Create the instance of FileReader
                var reader = new FileReader();

                reader.onprogress = function(event) {
                    if (event.lengthComputable && (event.total > 0)) {
                        var rate = Math.floor((event.loaded / event.total) * 100);
                        progressArea.textContent = rate + ' %';
                    }
                };

                reader.onerror = function() {
                    window.alert('FileReader Error : Error code is ' + reader.error.code);
                    uploader.value = '';
                };

                // Success read
                reader.onload = function() {
                    var arrayBuffer = reader.result;  // Get ArrayBuffer

                    startAudio(arrayBuffer);

                    uploader.value           = '';
                    progressArea.textContent = file.name;
                };

                // Read the instance of File
                reader.readAsArrayBuffer(file);
            }
        }, 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);

        // Control playbackRate
        document.getElementById('range-playback-rate').addEventListener('input', function() {
            if (source instanceof AudioBufferSourceNode) {
                var min = source.playbackRate.minValue || 0;
                var max = source.playbackRate.maxValue || 1024;

                if ((this.valueAsNumber >= min) && (this.valueAsNumber <= max)) {
                    source.playbackRate.value = this.valueAsNumber;
                }
            }

            document.getElementById('output-playback-rate').textContent = this.value;
        }, false);

        // Toggle loop
        document.querySelector('[type="checkbox"]').addEventListener(EventWrapper.CLICK, function() {
            if (source instanceof AudioBufferSourceNode) {
                source.loop = this.checked;
            }
        }, false);

        // Select fftSize
        document.getElementById('select-fft-size').addEventListener('change', function() {
            switch (parseInt(this.value)) {
                case   32 :
                case   64 :
                case  128 :
                case  256 :
                case  512 :
                case 1024 :
                case 2048 :
                    analyser.fftSize = this.value;
                    break;
                default :
                    window.alert('The selected FFT size is invalid.');
                    break;
            }
        }, false);

        // Control smoothingTimeConstant
        document.getElementById('range-smoothing-time-constant').addEventListener('input', function() {
            var min = 0;
            var max = 1;

            if ((this.valueAsNumber >= min) && (this.valueAsNumber <= max)) {
                analyser.smoothingTimeConstant = this.valueAsNumber;
                document.getElementById('output-smoothing-time-constant').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;
})();