(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;
// Create the instance of ChannelSplitterNode for LFO
var lfoSplitter = context.createChannelSplitter(2);
// 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
}
};
// This callback is executed in order to reverse amplitude each channel
var onaudioprocessCallback =function(event) {
// Get the instance of Float32Array for input data (Array size equals buffer size)
var inputLs = event.inputBuffer.getChannelData(0); // Left channel
var inputRs = event.inputBuffer.getChannelData(1); // Right channel
// 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
// Reverse amplitude each channel
for (var i = 0; i < this.bufferSize; i++) {
outputLs[i] = 1 * inputLs[i]; // GainNode.gain (1) + outputLs[i]
outputRs[i] = -1 * inputRs[i]; // GainNode.gain (1) - outputLs[i]
}
};
// Create the instance of ScriptProcessorNode for LFO
var processor = context.createScriptProcessor(getBufferSize(), 2, 2);
// for Auto Panner parameters
var amplitudeL = context.createGain();
var amplitudeR = context.createGain();
var splitter = context.createChannelSplitter(2); // Create the instance of ChannelSplitterNode for original sound
var merger = context.createChannelMerger(2); // Create the instance of ChannelMergerNode for original sound
var depth = context.createGain();
var rate = lfo.frequency;
// Initialize parameters for Auto Panner
amplitudeL.gain.value = 1; // 1 +- Depth
amplitudeR.gain.value = 1; // 1 +- Depth
depth.gain.value = document.getElementById('range-autopanner-depth').valueAsNumber;
rate.value = document.getElementById('range-autopanner-rate').valueAsNumber;
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);
splitter.disconnect(0);
splitter.disconnect(1);
amplitudeL.disconnect(0);
amplitudeR.disconnect(0);
merger.disconnect(0);
if (document.getElementById('toggle-effect').checked) {
// Auto Panner ON
// Connect nodes for effect (Auto Panner) sound
// -> GainNode for Left Channel (Amplitude) -
// AudioBufferSourceNode (Input) -> ChannelSplitterNode -| |-> ChannelMergerNode -> GainNode (Master Volume) (-> AudioDestinationNode (Output))
// -> GainNode for Right Channel (Amplitude) -
source.connect(splitter);
splitter.connect(amplitudeL, 0, 0);
splitter.connect(amplitudeR, 1, 0);
amplitudeL.connect(merger, 0, 0);
amplitudeR.connect(merger, 0, 1);
merger.connect(gain);
processor.onaudioprocess = onaudioprocessCallback;
} else {
// Auto Panner OFF
// AudioBufferSourceNode (Input) -> GainNode (Master Volume) (-> AudioDestinationNode (Output))
source.connect(gain);
}
// Connect nodes for LFO that changes Amplitude periodically
// -> gain (GainNode for Left Channel)
// OscillatorNode (LFO) -> GainNode (Depth) -> ScriptProcessorNode (Reverse Amplitude) -> ChannelSplitter -|
// -> gain (GainNode for Right Channel)
lfo.connect(depth);
depth.connect(processor);
processor.connect(lfoSplitter);
lfoSplitter.connect(amplitudeL.gain, 0);
lfoSplitter.connect(amplitudeR.gain, 1);
// Start audio
source.start(0);
// Start LFO
lfo.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);
splitter.disconnect(0);
splitter.disconnect(1);
amplitudeL.disconnect(0);
amplitudeR.disconnect(0);
merger.disconnect(0);
if (this.checked) {
// Auto Panner ON
// Connect nodes for effect (Auto Panner) sound
// -> GainNode for Left Channel (Amplitude) -
// AudioBufferSourceNode (Input) -> ChannelSplitterNode -| |-> ChannelMergerNode -> GainNode (Master Volume) (-> AudioDestinationNode (Output))
// -> GainNode for Right Channel (Amplitude) -
source.connect(splitter);
splitter.connect(amplitudeL, 0, 0);
splitter.connect(amplitudeR, 1, 0);
amplitudeL.connect(merger, 0, 0);
amplitudeR.connect(merger, 0, 1);
merger.connect(gain);
// Resume onaudioprocess event
processor.connect(lfoSplitter);
processor.onaudioprocess = onaudioprocessCallback;
} else {
// Auto Panner OFF
// AudioBufferSourceNode (Input) -> GainNode (Master Volume) (-> AudioDestinationNode (Output))
source.connect(gain);
// Pause onaudioprocess event
processor.disconnect(0);
processor.onaudioprocess = null;
}
}, false);
// Control Auto Panner Depth
document.getElementById('range-autopanner-depth').addEventListener('input', function() {
depth.gain.value = 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(splitter, 'channelsplitternode-properties', 'ChannelSplitterNode');
displayProperties(merger, 'channelmergernode-properties', 'ChannelMergerNode');
};
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;
})();