(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; } // Create the instance of OscillatorNode var oscillator = context.createOscillator(); // Parameter for the instance of OscillatorNode var type = oscillator.type; // for legacy browsers context.createGain = context.createGain || context.createGainNode; // Create the instance of GainNode (for Master Volume) var gain = context.createGain(); // Phaser // 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; var MAXIMUM_STAGES = 24; var filters = new Array(MAXIMUM_STAGES); for (var i = 0; i < MAXIMUM_STAGES; i++) { // Create the instance of BiquadFilterNode filters[i] = context.createBiquadFilter(); // All-Pass Filter filters[i].type = (typeof filters[i].type === 'string') ? 'allpass' : 7; // Initialize parameters for All-Pass Filter filters[i].frequency.value = document.getElementById('range-phaser-frequency').valueAsNumber; filters[i].Q.value = Math.SQRT1_2; filters[i].gain.value = 0; // Not used } // for Phaser parameters var numberOfStages = 0; var depthRate = 0; var depth = context.createGain(); var rate = lfo.frequency; var mix = context.createGain(); var feedback = context.createGain(); // Initialize parameters for Phaser numberOfStages = document.forms['form-phaser-stage'].elements['radio-phaser-stage'][1].value; depthRate = document.getElementById('range-phaser-depth').valueAsNumber / 100; depth.gain.value = document.getElementById('range-phaser-frequency').valueAsNumber * depthRate; rate.value = document.getElementById('range-phaser-rate').valueAsNumber; mix.gain.value = document.getElementById('range-phaser-mix').valueAsNumber; feedback.gain.value = document.getElementById('range-phaser-feedback').valueAsNumber; // Flag for starting or stopping sound var isStop = true; /* * Event Listener */ // Start or Stop sound var PIANO_88S = [ 'A-4', 'A-4h', 'B-4', 'C-3', 'C-3h', 'D-3', 'D-3h', 'E-3', 'F-3', 'F-3h', 'G-3', 'G-3h', 'A-3', 'A-3h', 'B-3', 'C-2', 'C-2h', 'D-2', 'D-2h', 'E-2', 'F-2', 'F-2h', 'G-2', 'G-2h', 'A-2', 'A-2h', 'B-2', 'C-1', 'C-1h', 'D-1', 'D-1h', 'E-1', 'F-1', 'F-1h', 'G-1', 'G-1h', 'A-1', 'A-1h', 'B-1', 'C', 'Ch', 'D', 'Dh', 'E', 'F', 'Fh', 'G', 'Gh', 'A', 'Ah', 'B', 'C1', 'C1h', 'D1', 'D1h', 'E1', 'F1', 'F1h', 'G1', 'G1h', 'A1', 'A1h', 'B1', 'C2', 'C2h', 'D2', 'D2h', 'E2', 'F2', 'F2h', 'G2', 'G2h', 'A2', 'A2h', 'B2', 'C3', 'C3h', 'D3', 'D3h', 'E3', 'F3', 'F3h', 'G3', 'G3h', 'A3', 'A3h', 'B3', 'C4' ]; var pianoKeys = Array.prototype.slice.call(document.querySelectorAll('#piano ul li'), 0); var convertIndex = function(index) { // // The 12 equal temparement // // Min -> A 27.5Hz, Max -> C 4186 Hz // // Example : // A * 1.059463 -> A# (half up) // var FREQUENCY_RATIO = Math.pow(2, (1 / 12)); // about 1.059463; var MIN_A = 27.5; return (MIN_A * Math.pow(FREQUENCY_RATIO, index)); }; pianoKeys.forEach(function(element, index, array) { // Start sound element.addEventListener(EventWrapper.START, function(event) { if (!isStop) { oscillator.stop(0); lfo.stop(0); } var pianoIndex = PIANO_88S.indexOf(this.id); var frequency = convertIndex(pianoIndex); // Create the instance of OscillatorNode oscillator = context.createOscillator(); // for legacy browsers oscillator.start = oscillator.start || oscillator.noteOn; oscillator.stop = oscillator.stop || oscillator.noteOff; // Prepare LFO // 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; rate = lfo.frequency; // Change reference // GainNode (Master Volume) -> AudioDestinationNode (Output) gain.connect(context.destination); // Connect nodes for original sound // OscillatorNode (Input) -> GainNode (Master Volume) (-> AudioDestinationNode (Output)) oscillator.connect(gain); if (numberOfStages > 0) { // Phaser ON // Connect nodes for effect (Phaser) sound // OscillatorNode (Input) -> BiquadFilterNode (All-Pass Filter) x N -> GainNode (Mix) -> GainNode (Master Volume) (-> AudioDestinationNode (Output)) oscillator.connect(filters[0]); for (var i = 0; i < numberOfStages; i++) { if (i < (numberOfStages - 1)) { filters[i].connect(filters[i + 1]); } else { filters[i].connect(mix); // Connect nodes for Feedback // (OscillatorNode (Input) ->) BiquadFilterNode (All-Pass Filter) x N -> GainNode (Feedback) -> BiquadFilterNode (All-Pass Filter) x N -> ... filters[i].connect(feedback); feedback.connect(filters[0]); } } mix.connect(gain); // Connect nodes for LFO that changes frequency in BiquadFilterNode (All-Pass Filter) periodically // Clear connection depth.disconnect(0); // OscillatorNode (LFO) -> GainNode (Depth) -> frequency (AudioParam) x N lfo.connect(depth); for (var i = 0; i < MAXIMUM_STAGES; i++) { depth.connect(filters[i].frequency); } } else { // Phaser OFF } // Set parameters oscillator.type = type; oscillator.frequency.value = frequency; // Start sound oscillator.start(0); // Start LFO lfo.start(0); isStop = false; this.classList.remove('key-off'); this.classList.add('key-on'); }, false); // Stop sound element.addEventListener(EventWrapper.END, function() { if (isStop) { return; } // Stop sound oscillator.stop(0); // Stop LFO lfo.stop(0); isStop = true; this.classList.remove('key-on'); this.classList.add('key-off'); }, 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); // Select type document.getElementById('form-wave-type').addEventListener('change', function() { for (var i = 0, len = this.elements['radio-wave-type'].length; i < len; i++) { if (this.elements['radio-wave-type'][i].checked) { oscillator.type = type = (typeof oscillator.type === 'string') ? this.elements['radio-wave-type'][i].value : i; break; } } }, false); // Select the number of All-Pass Filters document.getElementById('form-phaser-stage').addEventListener('change', function() { for (var i = 0, len = this.elements['radio-phaser-stage'].length; i < len; i++) { if (this.elements['radio-phaser-stage'][i].checked) { numberOfStages = parseInt(this.elements['radio-phaser-stage'][i].value); // Clear connection oscillator.disconnect(0); for (var i = 0; i < MAXIMUM_STAGES; i++) { filters[i].disconnect(0); } mix.disconnect(0); // Connect nodes for original audio // OscillatorNode (Input) -> GainNode (Master Volume) (-> AudioDestinationNode (Output)) oscillator.connect(gain); if (numberOfStages > 0) { // Phaser ON // Connect nodes for effect (Phaser) sound // OscillatorNode (Input) -> BiquadFilterNode (All-Pass Filter) x N -> GainNode (Mix) -> GainNode (Master Volume) (-> AudioDestinationNode (Output)) oscillator.connect(filters[0]); for (var i = 0; i < numberOfStages; i++) { if (i < (numberOfStages - 1)) { filters[i].connect(filters[i + 1]); } else { filters[i].connect(mix); // Connect nodes for Feedback // (OscillatorNode (Input) ->) BiquadFilterNode (All-Pass Filter) x N -> GainNode (Feedback) -> BiquadFilterNode (All-Pass Filter) x N -> ... filters[i].connect(feedback); feedback.connect(filters[0]); } } mix.connect(gain); } else { // Phaser OFF } break; } } }, false); // Control frequency document.getElementById('range-phaser-frequency').addEventListener('input', function() { for (var i = 0; i < MAXIMUM_STAGES; i++) { filters[i].frequency.value = this.valueAsNumber; } document.getElementById('output-phaser-frequency').textContent = this.value; }, false); // Control Phaser Depth document.getElementById('range-phaser-depth').addEventListener('input', function() { depth.gain.value = document.getElementById('range-phaser-frequency').valueAsNumber * (this.valueAsNumber / 100); depthRate = this.valueAsNumber / 100; document.getElementById('output-phaser-depth').textContent = this.value; }, false); // Control Phaser Rate document.getElementById('range-phaser-rate').addEventListener('input', function() { rate.value = this.valueAsNumber; document.getElementById('output-phaser-rate').textContent = this.value; }, false); // Control Phaser Mix document.getElementById('range-phaser-mix').addEventListener('input', function() { var min = mix.gain.minValue || 0; var max = mix.gain.maxValue || 1; if ((this.valueAsNumber >= min) && (this.valueAsNumber <= max)) { mix.gain.value = this.valueAsNumber; document.getElementById('output-phaser-mix').textContent = this.value; } }, false); // Control Phaser Feedback document.getElementById('range-phaser-feedback').addEventListener('input', function() { var min = feedback.gain.minValue || 0; var max = feedback.gain.maxValue || 1; if ((this.valueAsNumber >= min) && (this.valueAsNumber <= max)) { feedback.gain.value = this.valueAsNumber; document.getElementById('output-phaser-feedback').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; })();