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