(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';
};
// Create the instance of OscillatorNode
var oscillator = context.createOscillator();
// for legacy browsers
context.createGain = context.createGain || context.createGainNode;
// Create the instance of GainNode
var eg = context.createGain(); // for Envelope Generator
var masterVolume = context.createGain(); // for Master Volume
// for legacy browsers
eg.gain.setTargetAtTime = eg.gain.setTargetAtTime || eg.gain.setTargetValueAtTime;
// Flag for starting or stopping sound
var isStop = true;
// Parameters for Envelope Generator
var egParams = {
attack : 0,
decay : 0,
sustain : 0,
release : 0
};
// Initialization
egParams.attack = document.getElementById('range-attack').valueAsNumber;
egParams.decay = document.getElementById('range-decay').valueAsNumber;
egParams.sustain = document.getElementById('range-sustain').valueAsNumber;
egParams.release = document.getElementById('range-release').valueAsNumber;
// Draw gain property in the instance of GainNode
var canvas = document.querySelector('canvas');
var canvasContext = canvas.getContext('2d');
var intervalid = null;
var currentTime = 0;
var drawGain = function(t) {
var VALUE_OF_STOP = 1e-3;
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;
var x = (currentTime / t) + paddingLeft;
var y = ((1 - eg.gain.value) * innerHeight) + paddingTop;
// Set style
canvasContext.strokeStyle = 'rgba(0, 0, 255, 1.0)';
canvasContext.lineWidth = 2;
canvasContext.lineCap = 'round';
canvasContext.lineJoin = 'miter';
if (currentTime === 0) {
canvasContext.clearRect(0, 0, width, height);
canvasContext.beginPath();
// canvasContext.moveTo(paddingLeft, innerBottom);
// 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 = '16px "Times New Roman"';
canvasContext.fillText('1.00', 3, paddingTop);
canvasContext.fillText('0.50', 3, middle);
canvasContext.fillText('0.00', 3, innerBottom);
}
canvasContext.lineTo(x, y);
canvasContext.stroke();
currentTime = (x < (paddingLeft + innerWidth)) ? (currentTime + t) : 0;
if (eg.gain.value < VALUE_OF_STOP) {
// Stop sound
oscillator.stop(0);
if (intervalid !== null) {
window.clearInterval(intervalid);
intervalid = null;
}
isStop = true;
}
};
/*
* Event Listener
*/
// Start or Stop sound
document.querySelector('button').addEventListener(EventWrapper.START, function() {
if (!isStop) {
oscillator.stop(0);
}
// Create the instance of OscillatorNode
oscillator = context.createOscillator();
// for legacy browsers
oscillator.start = oscillator.start || oscillator.noteOn;
oscillator.stop = oscillator.stop || oscillator.noteOff;
// OscillatorNode (Input) -> GainNode (Envelope Generator) -> GainNode (Master Volume) -> AudioDestinationNode (Output)
oscillator.connect(eg);
eg.connect(masterVolume);
masterVolume.connect(context.destination);
/*
* Envelope Generator : Attack -> Decay -> Sustain
*/
var t0 = context.currentTime;
var t1 = t0 + egParams.attack;
var t2 = egParams.decay;
var t2Value = egParams.sustain;
// Clear all shedules
eg.gain.cancelScheduledValues(t0);
// Start from gain = 0
eg.gain.setValueAtTime(0, t0);
// Attack : gain increases linearly until assigned time (t1)
eg.gain.linearRampToValueAtTime(1, t1);
// Decay -> Sustain : gain gradually decreases to value of sustain during decay time (t2) from assigned time (t1)
eg.gain.setTargetAtTime(t2Value, t1, t2);
// Start sound at t0
oscillator.start(t0);
// Clear
if (intervalid !== null) {
window.clearInterval(intervalid);
intervalid = null;
}
// Draw the value of gain
var interval = 50;
intervalid = window.setInterval(function() {
drawGain(interval);
}, interval);
isStop = false;
this.innerHTML = '<span class="icon-pause"></span>';
}, false);
document.querySelector('button').addEventListener(EventWrapper.END, function() {
if (isStop) {
return;
}
/*
* Envelope Generator : Sustain -> Release
*/
var t3 = context.currentTime;
var t4 = egParams.release;
// in the case of mouseup on the way of Attack or Decay
eg.gain.cancelScheduledValues(t3);
eg.gain.setValueAtTime(eg.gain.value, t3);
// Release : gain gradually decreases to 0 during release time (t4) from assigned time (t3)
eg.gain.setTargetAtTime(0, t3, t4);
this.innerHTML = '<span class="icon-start"></span>';
}, false);
// Control Master Volume
document.getElementById('range-volume').addEventListener('input', function() {
var min = masterVolume.gain.minValue || 0;
var max = masterVolume.gain.maxValue || 1;
if ((this.valueAsNumber >= min) && (this.valueAsNumber <= max)) {
masterVolume.gain.value = this.valueAsNumber;
document.getElementById('output-volume').textContent = this.value;
}
}, false);
// Control Attack
document.getElementById('range-attack').addEventListener('input', function() {
if (this.valueAsNumber >= 0) {
egParams.attack = this.valueAsNumber;
document.getElementById('output-attack').textContent = this.value;
}
}, false);
// Control Decay
document.getElementById('range-decay').addEventListener('input', function() {
if (this.valueAsNumber > 0) {
egParams.decay = this.valueAsNumber;
document.getElementById('output-decay').textContent = this.value;
}
}, false);
// Control Sustain
document.getElementById('range-sustain').addEventListener('input', function() {
if (this.valueAsNumber > 0) {
egParams.sustain = this.valueAsNumber;
document.getElementById('output-sustain').textContent = this.value;
}
}, false);
// Control Release
document.getElementById('range-release').addEventListener('input', function() {
if (this.valueAsNumber > 0) {
egParams.release = this.valueAsNumber;
document.getElementById('output-release').textContent = this.value;
}
}, false);
/*
* Display properties
*/
displayProperties(eg.gain, 'gainnode-gain-properties', 'gain (AudioParam) ');
};
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;
})();