オートパン (オーディオ) | エフェクター

LOOP
Movement Type
PannerNode
PannerNode
PropertyValuehasOwnProperty
AudioListener
Audio Listener
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 the instance of AudioBufferSourceNode
        var source = null;

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

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

        // Auto Panner

        // Create the instance of OscillatorNode (for LFO)
        var lfo = context.createOscillator();

        // for leagcy browsers
        lfo.start = lfo.start || lfo.noteOn;
        lfo.stop  = lfo.stop  || lfo.noteOff;

        // for Auto Panner parameters
        var panner       = context.createPanner();  // Create the instance of PannerNode
        var listener     = context.listener;        // The instance of AudioListener
        var depth        = 0;
        var rate         = lfo.frequency;
        var movementType = 'linear';

        // Initialize parameters for Auto Panner
        depth      = document.getElementById('range-autopanner-depth').valueAsNumber;
        rate.value = document.getElementById('range-autopanner-rate').valueAsNumber;

        // 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 for LFO
        var processor = context.createScriptProcessor(getBufferSize(), 1, 1);

        var canvas          = document.querySelector('canvas');
        var canvasContext   = canvas.getContext('2d');
        var padding         = 20;
        var width           = canvas.width;
        var height          = canvas.height;
        var innerWidth      = width  - (2 * padding);
        var innerHeight     = height - (2 * padding);
        var halfInnerWidth  = parseInt(innerWidth  / 2);
        var halfInnerHeight = parseInt(innerHeight / 2);
        var listenerX       = halfInnerWidth;
        var listenerZ       = halfInnerHeight;
        var maxPannerXZ     =  1 * parseFloat(document.getElementById('range-autopanner-depth').max);
        var minPannerXZ     = -1 * parseFloat(document.getElementById('range-autopanner-depth').max);
        var positionRange   = maxPannerXZ - minPannerXZ;

        var pannerIcon   = new Image();
        var listenerIcon = new Image();

        pannerIcon.src   = 'data:image/gif;base64,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';
        listenerIcon.src = 'data:image/gif;base64,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';

        var iconSize        = 64;
        var halfIconSize    = 32;
        var quarterIconSize = 16;

        var drawPosition = function(pannerX, pannerZ) {
            canvasContext.clearRect(0, 0, width, height);

            // Draw text
            canvasContext.fillStyle = 'rgba(119, 119, 119, 1.0)';
            canvasContext.font      = '16px "Times New Roman"';
            canvasContext.textAlign = 'center';

            // x axis
            for (var i = -5; i <= 5; i += 1) {
                var textX  = i + '.0';
                var textXX = parseInt(i * (halfInnerWidth / maxPannerXZ)) + halfInnerWidth + padding;
                var textXY = height - 3;

                canvasContext.fillText(textX, textXX, textXY);
            }

            // z axis
            for (var i = -5; i <= 5; i += 1) {
                var textZ  = i + '.0';

                if (textZ >= 0) {
                    textZ = ' ' + textZ;
                }

                var textZX = canvasContext.measureText(textZ).width;
                var textZY = parseInt(i * (halfInnerHeight / maxPannerXZ)) + halfInnerHeight + padding;

                canvasContext.fillText(textZ, textZX, textZY);
            }

            // Draw position for PannerNode
            canvasContext.fillStyle = 'rgba(0, 0, 255, 1.0)';

            var px = parseInt(pannerX * (halfInnerWidth  / maxPannerXZ)) + halfInnerWidth  + padding - halfIconSize;
            var pz = parseInt(pannerZ * (halfInnerHeight / maxPannerXZ)) + halfInnerHeight + padding - halfIconSize;

            canvasContext.drawImage(pannerIcon, px, pz, iconSize, iconSize);

            // Draw position for AudioListener
            canvasContext.fillStyle = 'rgba(255, 0, 0, 1.0)';

            var lx = listenerX + padding - halfIconSize;
            var lz = listenerZ + padding - halfIconSize;

            canvasContext.drawImage(listenerIcon, lx, lz, iconSize, iconSize);
        };

        listenerIcon.onload = function() {
            drawPosition(0, 0);
        };

        (function() {
            var isDown = false;

            var setListenerPosition = function(x, z) {
                var posX = (positionRange * (x / innerWidth))  - maxPannerXZ;
                var posZ = (positionRange * (z / innerHeight)) - maxPannerXZ;

                listener.setPosition(posX, 0, posZ);
            };

            var getX = function(event) {
                if (event.pageX) {
                    return event.pageX;
                } else if (event.touches[0]) {
                    return event.touches[0].pageX;
                }
            };

            var getZ = function(event) {
                if (event.pageY) {
                    return event.pageY;
                } else if (event.touches[0]) {
                    return event.touches[0].pageY;
                }
            };

            var clipListenerX = function(x) {
                var min = padding;
                var max = innerWidth - quarterIconSize;

                if (x < min) {x = min;}
                if (x > max) {x = max;}

                return x;
            };

            var clipListenerZ = function(z) {
                var min = padding;
                var max = innerHeight - quarterIconSize;

                if (z < min) {z = min;}
                if (z > max) {z = max;}

                return z;
            };

            var isHit = function(x, z) {
                var lx = listenerX + padding;
                var lz = listenerZ + padding;

                canvasContext.beginPath();
                canvasContext.arc(lx, lz, halfIconSize, 0, (2 * Math.PI), true);

                return canvasContext.isPointInPath(x, z);
            };

            canvas.addEventListener(EventWrapper.START, function(event) {
                if (!isHit((getX(event) - canvas.offsetLeft), (getZ(event) - canvas.offsetTop))) {
                    return;
                }

                listenerX = clipListenerX(getX(event) - (canvas.offsetLeft + quarterIconSize));
                listenerZ = clipListenerZ(getZ(event) - (canvas.offsetTop  + quarterIconSize));
                isDown    = true;

                setListenerPosition(listenerX, listenerZ);
            }, true);

            canvas.addEventListener(EventWrapper.MOVE, function(event) {
                if (!isDown) {
                    return false;
                }

                listenerX = clipListenerX(getX(event) - (canvas.offsetLeft + quarterIconSize));
                listenerZ = clipListenerZ(getZ(event) - (canvas.offsetTop  + quarterIconSize));
                setListenerPosition(listenerX, listenerZ);
            }, true);

            window.addEventListener(EventWrapper.END, function() {
                if (isDown) {
                    isDown = false;
                }
            }, true);
        })();

        // This callback is executed as LFO
        var onaudioprocessCallback = function() {

            // Connect nodes for LFO that changes pan periodically
            // OscillatorNode (LFO) -> ScriptProcessorNode (setPosition) (-> AudioDestinationNode (dummy))
            lfo.connect(processor);
            processor.connect(context.destination);

            processor.onaudioprocess = function(event) {
                // Get the instance of Float32Array for input data (Array size equals buffer size)
                var inputs  = event.inputBuffer.getChannelData(0);
                // var outputs = event.outputBuffer.getChannelData(0);  // Not used

                var x = 0;
                var z = 0;

                for (var i = 0; i < this.bufferSize; i++) {
                    // Move pan
                    switch (movementType) {
                        case 'linear' :
                            x = depth * inputs[i];

                            panner.setPosition(x, 0, z);
                            break;
                        case 'arc-front' :
                        case 'arc-rear'  :
                            x = depth * inputs[i];

                            if (depth > 0) {
                                var radian = Math.acos(inputs[i]);

                                var sign = 1;

                                if (movementType === 'arc-rear') {
                                    sign = -1;
                                }

                                z = sign * depth * Math.sin(radian);
                            }

                            panner.setPosition(x, 0, z);
                            break;
                        default :
                            break;
                    }
                }

                // Draw Panner and Listener
                drawPosition(x, z);
            }

            lfo.start(0);
        };

        var stopLFO = function() {
            // Stop LFO
            lfo.stop(0);

            // Create the instance of OscillatorNode (for LFO)
            lfo = context.createOscillator();

            // for leagcy browsers
            lfo.start = lfo.start || lfo.noteOn;
            lfo.stop  = lfo.stop  || lfo.noteOff;

            // Set Rate
            lfo.frequency.value = rate.value;

            // Change reference
            rate = lfo.frequency;

            // Pause onaudioprocess event
            processor.disconnect(0);
            processor.onaudioprocess = null;
        };

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

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

                // GainNode (Master Volume) -> AudioDestinationNode (Output)
                gain.connect(context.destination);

                // Clear connection
                source.disconnect(0);
                panner.disconnect(0);

                if (document.getElementById('toggle-effect').checked) {
                    // Auto Panner ON

                    // Connect nodes for effect (Auto Panner) sound
                    // AudioBufferSourceNode (Input) -> PannerNode (Pan) -> GainNode (Master Volume) (-> AudioDestinationNode (Output))
                    source.connect(panner);
                    panner.connect(gain);

                    onaudioprocessCallback();
                } else {
                    // Auto Panner OFF

                    // AudioBufferSourceNode (Input) -> GainNode (Master Volume) (-> AudioDestinationNode (Output))
                    source.connect(gain);
                }

                // Start audio
                source.start(0);

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

                    // Stop audio
                    source.stop(0);

                    // Stop LFO
                    stopLFO();

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

        // Toggle Effect
        document.getElementById('toggle-effect').addEventListener(EventWrapper.CLICK, function() {
            if (!(source instanceof AudioBufferSourceNode)) {
                return;
            }

            // Clear connection
            source.disconnect(0);
            panner.disconnect(0);

            if (this.checked) {
                // Auto Panner ON

                // Connect nodes for effect (Auto Panner) sound
                // AudioBufferSourceNode (Input) -> PannerNode (Pan) -> GainNode (Master Volume) (-> AudioDestinationNode (Output))
                source.connect(panner);
                panner.connect(gain);

                onaudioprocessCallback();
            } else {
                // Auto Panner OFF

                // AudioBufferSourceNode (Input) -> GainNode (Master Volume) (-> AudioDestinationNode (Output))
                source.connect(gain);

                stopLFO();
            }
        }, false);

        // Select panningModel
        document.getElementById('select-panning-model').addEventListener('change', function() {
            var PANNING_MODELS = {
                equalpower : 0,
                HRTF       : 1
            };

            panner.panningModel = (typeof panner.panningModel === 'string') ? this.value : PANNING_MODELS[this.value];
        });

        // Select distanceModel
        document.getElementById('select-distance-model').addEventListener('change', function() {
            var DISTANCE_MODELS = {
                linear      : 0,
                inverse     : 1,
                exponential : 2
            };

            panner.distanceModel = (typeof panner.distanceModel === 'string') ? this.value : DISTANCE_MODELS[this.value];
        });

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

        // Control Auto Panner Depth
        document.getElementById('range-autopanner-depth').addEventListener('input', function() {
            depth = this.valueAsNumber;
            document.getElementById('output-autopanner-depth').textContent = this.value;
        }, false);

        // Control Auto Panner Rate
        document.getElementById('range-autopanner-rate').addEventListener('input', function() {
            rate.value = this.valueAsNumber;
            document.getElementById('output-autopanner-rate').textContent = this.value;
        }, false);

        /*
         * Display properties
         */

        displayProperties(panner,   'pannernode-properties',    'PannerNode');
        displayProperties(listener, 'audiolistener-properties', 'AudioListener');
    };

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