diff --git a/img/analyser-bg.png b/img/analyser-bg.png new file mode 100644 index 0000000..b62524c Binary files /dev/null and b/img/analyser-bg.png differ diff --git a/img/ico-stop.gif b/img/ico-stop.gif new file mode 100644 index 0000000..7b40319 Binary files /dev/null and b/img/ico-stop.gif differ diff --git a/index.html b/index.html index 0ed7af3..fb0d688 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,11 @@ + +
@@ -26,9 +31,10 @@ Add Audio Source @@ -36,50 +42,23 @@ Add Module
- - - - -
-
-
Analyser
- Analyzer graph -
-
 
-
 
- x -
- + + -
-
-
Delay
-
-
- Delay Time - 6.55 -
-
-
-
-
 
-
 
- x -
- +--> + + - + + + + + + + +
diff --git a/js/analyser.js b/js/analyser.js index 3b807c1..7392fbc 100644 --- a/js/analyser.js +++ b/js/analyser.js @@ -4,18 +4,19 @@ var CANVAS_WIDTH = 150; var CANVAS_HEIGHT = 120; function updateAnalyser(e) { - var SPACER_WIDTH = 2; - var BAR_WIDTH = 2; + var SPACER_WIDTH = 1; + var BAR_WIDTH = 1; var OFFSET = 100; var CUTOFF = 23; - var numBars = Math.round(CANVAS_WIDTH / SPACER_WIDTH); var ctx = e.drawingContext; + var canvas = ctx.canvas; + var numBars = Math.round(canvas.width / SPACER_WIDTH); var freqByteData = new Uint8Array(e.audioNode.frequencyBinCount); e.audioNode.getByteFrequencyData(freqByteData); //analyser.getByteTimeDomainData(freqByteData); - ctx.clearRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT); + ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = '#F6D565'; ctx.lineCap = 'round'; var multiplier = e.audioNode.frequencyBinCount / numBars; @@ -30,7 +31,7 @@ function updateAnalyser(e) { magnitude = magnitude / multiplier; var magnitude2 = freqByteData[i * multiplier]; ctx.fillStyle = "hsl( " + Math.round((i*360)/numBars) + ", 100%, 50%)"; - ctx.fillRect(i * SPACER_WIDTH, CANVAS_HEIGHT, BAR_WIDTH, -magnitude); + ctx.fillRect(i * SPACER_WIDTH, canvas.height, BAR_WIDTH, -magnitude); } } diff --git a/js/dragging.js b/js/dragging.js index 833392d..9493b0a 100644 --- a/js/dragging.js +++ b/js/dragging.js @@ -225,6 +225,7 @@ function whileDraggingConnector(event) { // the src and dst params are connection point elems, NOT // the node elems themselves. function connectNodes( src, dst ) { + var connectorShape = dragObj.connectorShape; src.className += " connected"; dst.className += " connected"; @@ -236,13 +237,13 @@ function connectNodes( src, dst ) { if (!src.outputConnections) src.outputConnections = new Array(); var connector = new Object(); - connector.line = dragObj.connectorShape; + connector.line = connectorShape; connector.destination = dst; src.outputConnections.push(connector); //Make sure the connector line points go from src->dest (x1->x2) if (!dragObj.input) { // need to flip - var shape = dragObj.connectorShape; + var shape = connectorShape; var x = shape.getAttributeNS(null, "x2"); var y = shape.getAttributeNS(null, "y2"); shape.setAttributeNS(null, "x2", shape.getAttributeNS(null, "x1")); @@ -254,8 +255,9 @@ function connectNodes( src, dst ) { if (!dst.inputConnections) dst.inputConnections = new Array(); connector = new Object(); - connector.line = dragObj.connectorShape; + connector.line = connectorShape; connector.source = src; + connector.destination = dst; dst.inputConnections.push(connector); // if the source has an audio node, connect them up. @@ -265,7 +267,79 @@ function connectNodes( src, dst ) { if (dst.onConnectInput) dst.onConnectInput(); - dragObj.connectorShape = null; + + connectorShape.inputConnection = connector; + connectorShape.destination = dst; + connectorShape.onclick = deleteConnection; + + connectorShape = null; +} + +function deleteConnection() { + var connections = this.destination.inputConnections; + breakSingleInputConnection( connections, connections.indexOf( this.inputConnection ) ); +} + +function breakSingleInputConnection( connections, index ) { + var connector = connections[index]; + var src = connector.source; + + // delete us from their .outputConnections, + src.outputConnections.splice( src.outputConnections.indexOf( connector.destination ), 1); + // call disconnect() on the src, + src.audioNode.disconnect(); + // if there's anything left in their outputConnections, re.connect() those nodes. + // TODO: again, this will break due to src resetting. + for (var j=0; j 500) - tempx = 100; - else - tempx += 50; e.style.top="" + tempy + "px"; - if (tempy > 1000) + if (tempx > 700) { + tempy += 250; + tempx = 50; + } else + tempx += 250; + if (tempy > 600) tempy = 100; - else - tempy += 50; + e.setAttribute("audioNodeType", nodeType ); e.onmousedown=startDraggingNode; var content = document.createElement("div"); @@ -48,6 +71,7 @@ function createNewModule( nodeType, input, output ) { var close = document.createElement("a"); close.href = "#"; close.className = "close"; + close.onclick = deleteModule; e.appendChild( close ); // add the node into the soundfield @@ -55,22 +79,26 @@ function createNewModule( nodeType, input, output ) { return(content); } -function addModuleSlider( element, label, value, min, max, units ) { +function addModuleSlider( element, label, ivalue, imin, imax, stepUnits, units, onUpdate ) { var group = document.createElement("div"); group.className="control-group"; var info = document.createElement("div"); info.className="slider-info"; - info.setAttribute("min", min ); - info.setAttribute("max", max ); + info.setAttribute("min", imin ); + info.setAttribute("max", imax ); var lab = document.createElement("span"); lab.className="label"; lab.appendChild(document.createTextNode(label)); info.appendChild(lab); var val = document.createElement("span"); val.className="value"; - val.appendChild(document.createTextNode("" + value + units)); + val.appendChild(document.createTextNode("" + ivalue + " " + units)); + + // cache the units type on the element for updates + val.setAttribute("units",units); info.appendChild(val); + group.appendChild(info); var slider = document.createElement("div"); @@ -78,55 +106,164 @@ function addModuleSlider( element, label, value, min, max, units ) { group.appendChild(slider); element.appendChild(group); - return slider; + return $( slider ).slider( { slide: onUpdate, value: ivalue, min: imin, max: imax, step: stepUnits } ); } -function createOscillator() { - var osc = createNewModule( "oscillator", false, true ); - $( addModuleSlider( osc, "frequency", 440, 0, 8000, "Hz" ) ).slider(); - $( addModuleSlider( osc, "detune", 0, -1200, 1200, "cents" ) ).slider(); - // TODO: add play button - osc = osc.parentNode; - osc.className += " has-footer"; +function onUpdateDetune(event, ui) { + updateSlider(event, ui).audioNode.detune.value = ui.value; +} - // Add footer element - var footer = document.createElement("footer"); - var sel = document.createElement("select"); - sel.className = "osc-type"; - var opt = document.createElement("option"); - opt.appendChild( document.createTextNode("sine")); - sel.appendChild( opt ); - opt = document.createElement("option"); - opt.appendChild( document.createTextNode("square")); - sel.appendChild( opt ); - opt = document.createElement("option"); - opt.appendChild( document.createTextNode("sawtooth")); - sel.appendChild( opt ); - opt = document.createElement("option"); - opt.appendChild( document.createTextNode("triangle")); - sel.appendChild( opt ); - opt = document.createElement("option"); - opt.appendChild( document.createTextNode("wavetable")); - sel.appendChild( opt ); - footer.appendChild( sel ); - osc.appendChild( footer ); - - // Add select element and type options +function onUpdateFrequency(event, ui) { + updateSlider(event, ui).audioNode.frequency.value = ui.value; +} - var oscNode = audioContext.createOscillator(); - oscNode.frequency = 440; - oscNode.detune = 0; - oscNode.type = oscNode.SINE; - osc.audioNode = oscNode; -// oscNode.noteOn(0); +function onUpdateGain(event, ui) { + updateSlider(event, ui).audioNode.gain.value = ui.value; } +function onUpdateDelay(event, ui) { + updateSlider(event, ui).audioNode.delayTime.value = ui.value; +} +function onUpdateThreshold(event, ui) { + updateSlider(event, ui).audioNode.threshold.value = ui.value; +} + +function onUpdateKnee(event, ui) { + updateSlider(event, ui).audioNode.knee.value = ui.value; +} +function onUpdateRatio(event, ui) { + updateSlider(event, ui).audioNode.ratio.value = ui.value; +} +function onUpdateAttack(event, ui) { + updateSlider(event, ui).audioNode.attack.value = ui.value; +} +function onUpdateRelease(event, ui) { + updateSlider(event, ui).audioNode.release.value = ui.value; +} +function onUpdateQ(event, ui) { + updateSlider(event, ui).audioNode.Q.value = ui.value; +} + +function updateSlider(event, ui, callback) { + var e = event.target; + var group = null; + while (!e.classList.contains("module")) { + if (e.classList.contains("control-group")) + group = e; + e = e.parentNode; + } + + //TODO: yes, this is lazy coding, and fragile. + var output = group.children[0].children[1]; + + // update the value text + output.innerText = "" + ui.value + " " + output.getAttribute("units"); + return e; +} + +function onPlayOscillator(event) { + var playButton = event.target; + if (playButton.isPlaying) { + //stop + playButton.isPlaying = false; + playButton.src = "img/ico-play.gif"; + var e = playButton.parentNode; + while (e && !e.audioNode) + e = e.parentNode; + if (e) + e.audioNode.noteOff(0); + } else { + //play - TODO: fix up for second play + playButton.isPlaying = true; + playButton.src = "img/ico-stop.gif"; + var e = playButton.parentNode; + while (e && !e.audioNode) + e = e.parentNode; + if (e) + e.audioNode.noteOn(0); + } +} + +function onToggleLoop(event) { + var checkbox = event.target; + +//TODO: fix up for second play + var e = checkbox.parentNode; + while (e && !e.audioNode) + e = e.parentNode; + if (e) + e.audioNode.loop = checkbox.checked; +} + +function onToggleNormalize(event) { + var checkbox = event.target; + + var e = checkbox.parentNode; + while (e && !e.audioNode) + e = e.parentNode; + if (e) + e.audioNode.normalize = checkbox.checked; +} + +function switchOscillatorTypes(event) { + var select = event.target; + + var e = select.parentNode; + while (e && !e.audioNode) + e = e.parentNode; + if (e) { + // TODO: wavetable! + e.audioNode.type = select.selectedIndex; + } +} + +function switchFilterTypes(event) { + var select = event.target; + var fType = select.selectedIndex; + + var e = select.parentNode; + while (e && !e.audioNode) + e = e.parentNode; + if (e) { + e.audioNode.type = fType; + if (fType>2 && fType<6) { + $( e.gainSlider ).slider( "option", "disabled", false ); + } else { + $( e.gainSlider ).slider( "option", "disabled", true ); + } + } +} + +function onPlayABSource(event) { + var playButton = event.target; + if (playButton.isPlaying) { + //stop + playButton.isPlaying = false; + playButton.src = "img/ico-play.gif"; + var e = playButton.parentNode; + while (e && !e.audioNode) + e = e.parentNode; + if (e) + e.audioNode.noteOff(0); + } else { + //play - TODO: fix up for second play + playButton.isPlaying = true; + playButton.src = "img/ico-stop.gif"; + var e = playButton.parentNode; + while (e && !e.audioNode) + e = e.parentNode; + if (e) + e.audioNode.noteOn(0); + } +} + +/* historic code, need to poach connection stuff function hitplay(e) { e = e.target.parentNode; // the node element, not the play button. @@ -150,194 +287,229 @@ function hitplay(e) { e.audioNode.noteOn(0); } -function oscNoteOn(e) { - e = e.target.parentNode; // the node element, not the play button. - if (e.audioNode) - e.audioNode.noteOn(0); -} - function hitstop(e) { e.target.parentNode.audioNode.noteOff(0); + e.target.parentNode.audioNode = null; } +*/ -function flipLoop(e) { - e.target.checked = !e.target.checked; - if (e.target.parentNode.audioNode) - e.target.parentNode.audioNode.loop = e.target.checked; -} -function onUpdateFilterQ(e) { - var param = e.parentNode.audioNode.Q; - - var i = parseFloat(e.getAttribute("val")); // in range 0.0 - 100.0 - var range = param.maxValue - param.minValue; - param.value = ((i * range) / 100) + param.minValue; - // console.log( e.parentNode.audioNode.frequency.value + "Hz Q=" + e.parentNode.audioNode.Q.value + "\n"); -} -function onUpdateFrequency(e) { - var param = e.parentNode.audioNode.frequency; +function createOscillator() { + var osc = createNewModule( "oscillator", false, true ); + addModuleSlider( osc, "frequency", 440, 0, 8000, 1, "Hz", onUpdateFrequency ); + addModuleSlider( osc, "detune", 0, -1200, 1200, 1, "cents", onUpdateDetune ); + + var play = document.createElement("img"); + play.src = "img/ico-play.gif"; + play.style.marginTop = "10px"; + play.alt = "play"; + play.onclick = onPlayOscillator; + osc.appendChild( play ); - var i = parseFloat(e.getAttribute("val")); // in range 0.0 - 100.0 - var range = param.maxValue - param.minValue; - param.value = ((i * range) / 100) + param.minValue; -// console.log( e.parentNode.audioNode.frequency.value + "Hz Q=" + e.parentNode.audioNode.Q.value + "\n"); -} + osc = osc.parentNode; + osc.className += " has-footer"; -function onUpdateFilterGain(e) { - var param = e.parentNode.audioNode.gain; + // Add footer element + var footer = document.createElement("footer"); + var sel = document.createElement("select"); + sel.className = "osc-type"; + var opt = document.createElement("option"); + opt.appendChild( document.createTextNode("sine")); + sel.appendChild( opt ); + opt = document.createElement("option"); + opt.appendChild( document.createTextNode("square")); + sel.appendChild( opt ); + opt = document.createElement("option"); + opt.appendChild( document.createTextNode("sawtooth")); + sel.appendChild( opt ); + opt = document.createElement("option"); + opt.appendChild( document.createTextNode("triangle")); + sel.appendChild( opt ); + opt = document.createElement("option"); + opt.appendChild( document.createTextNode("wavetable")); + sel.onchange = switchOscillatorTypes; + sel.appendChild( opt ); + footer.appendChild( sel ); + osc.appendChild( footer ); - var i = parseFloat(e.getAttribute("val")); // in range 0.0 - 100.0 - var range = param.maxValue - param.minValue; - param.value = ((i * range) / 100) + param.minValue; + // Add select element and type options + var oscNode = audioContext.createOscillator(); + oscNode.frequency = 440; + oscNode.detune = 0; + oscNode.type = oscNode.SINE; + osc.audioNode = oscNode; } -function onUpdateABSNGain(e) { - var i = parseFloat(e.getAttribute("val")); // in range 0.0 - 100.0 - var node = e.parentNode; - node.gain = i / 100; - if (node.audioNode) { - node.audioNode.gain.value = i / 100; - } -// console.log( e.parentNode.audioNode.gain.value + "\n"); -} +function createGain() { + var module = createNewModule( "gain", true, true ); + addModuleSlider( module, "gain", 1.0, 0.0, 10.0, 0.01, "", onUpdateGain ); -function onUpdateDelayTime(e) { - var i = parseFloat(e.getAttribute("val")); // in range 0.0 - 100.0 - var node = e.parentNode; - node.delay = i / 10; - if (node.audioNode) { - node.audioNode.delayTime.value = i / 10; - } -// console.log( e.parentNode.audioNode.gain.value + "\n"); -} + // after adding sliders, walk up to the module to store the audioNode. + module = module.parentNode; -function changeType(e) { - if (e.target.parentNode.audioNode) - e.target.parentNode.audioNode.type = e.target.selectedIndex; + var gainNode = audioContext.createGainNode(); + gainNode.gain.value = 1.0; + module.audioNode = gainNode; } -function initDefaultParam(param) { - var range = param.maxValue - param.minValue; - param.value = param.defaultValue; - return ((param.value - param.minValue) * 100) / range; +function createDelay() { + var module = createNewModule( "delay", true, true ); + addModuleSlider( module, "delay time", 0.5, 0.0, 10.0, 0.01, "sec", onUpdateDelay ); + + // after adding sliders, walk up to the module to store the audioNode. + module = module.parentNode; + + var delayNode = audioContext.createDelayNode(); + delayNode.delayTime.value = 0.5; + module.audioNode = delayNode; } -function createBiquadFilterNode() { - var e=createNewNode("biquadFilter", true, true ); - var filter = audioContext.createBiquadFilter(); - e.audioNode = filter; - filter.type=0; +function createAudioBufferSource( buffer ) { + var module = createNewModule( "audiobuffersource", false, true ); + + var play = document.createElement("img"); + play.src = "img/ico-play.gif"; + play.style.marginTop = "10px"; + play.alt = "play"; + play.onclick = onPlayABSource; + module.appendChild( play ); + + module = module.parentNode; + module.className += " has-footer has-loop"; + + // Add footer element + var footer = document.createElement("footer"); - var ctl=document.createElement("select"); - ctl.type="select"; - ctl.value="type"; - ctl.addEventListener( "change", changeType ); + var looper = document.createElement("div"); + looper.className = "loop"; + var label = document.createElement("label"); + var check = document.createElement("input"); + check.type = "checkbox"; + check.onchange = onToggleLoop; + label.appendChild(check); + label.appendChild(document.createTextNode(" Loop")); + looper.appendChild(label); + footer.appendChild(looper); + var sel = document.createElement("select"); + sel.className = "ab-source"; var opt = document.createElement("option"); - opt.setAttribute("value", "lowpass"); - opt.appendChild(document.createTextNode("Low Pass")); - opt.setAttribute("selected"); - ctl.appendChild(opt); - opt = document.createElement("option"); - opt.setAttribute("value", "highpass"); - opt.appendChild(document.createTextNode("High Pass")); - ctl.appendChild(opt); - opt = document.createElement("option"); - opt.setAttribute("value", "bandpass"); - opt.appendChild(document.createTextNode("Band Pass")); - ctl.appendChild(opt); - opt = document.createElement("option"); - opt.setAttribute("value", "lowshelf"); - opt.appendChild(document.createTextNode("Low Shelf")); - ctl.appendChild(opt); + + if (buffer) { // TODO: NASTY HACK! USING A GLOBAL! + opt.appendChild( document.createTextNode( lastBufferLoaded )); + sel.appendChild( opt ); + opt = document.createElement("option"); + } + + opt.appendChild( document.createTextNode("drums.ogg")); + sel.appendChild( opt ); opt = document.createElement("option"); - opt.setAttribute("value", "highshelf"); - opt.appendChild(document.createTextNode("High Shelf")); - ctl.appendChild(opt); + opt.appendChild( document.createTextNode("glass.ogg")); + sel.appendChild( opt ); opt = document.createElement("option"); - opt.setAttribute("value", "peaking"); - opt.appendChild(document.createTextNode("Peaking")); - ctl.appendChild(opt); + opt.appendChild( document.createTextNode("voice.ogg")); + sel.appendChild( opt ); opt = document.createElement("option"); - opt.setAttribute("value", "notch"); - opt.appendChild(document.createTextNode("Notch")); - ctl.appendChild(opt); + opt.appendChild( document.createTextNode("bass.ogg")); + sel.appendChild( opt ); opt = document.createElement("option"); - opt.setAttribute("value", "allpass"); - opt.appendChild(document.createTextNode("All Pass")); - ctl.appendChild(opt); + opt.appendChild( document.createTextNode("guitar.ogg")); + sel.appendChild( opt ); + footer.appendChild( sel ); + module.appendChild( footer ); - ctl.selectedIndex="0"; - e.appendChild(ctl); + // Add select element and type options + var sourceNode = audioContext.createBufferSource(); + sourceNode.buffer = buffer ? buffer : drumsBuffer; + module.audioNode = sourceNode; + return module; +} - ctl = createNewDialControl( 50, initDefaultParam(filter.Q) ); - ctl.style.left = "10px"; - ctl.style.top = "40px"; - ctl.onUpdateDial = onUpdateFilterQ; - e.appendChild( ctl ); - ctl = createNewDialControl( 50, initDefaultParam(filter.frequency) ); - ctl.style.left = "70px"; - ctl.style.top = "40px"; - ctl.onUpdateDial = onUpdateFrequency; - e.appendChild( ctl ); +function createDynamicsCompressor() { + var module = createNewModule( "dynamicscompressor", true, true ); + addModuleSlider( module, "threshold", -24.0, -36.0, 0.0, 0.01, "Db", onUpdateThreshold ); + addModuleSlider( module, "knee", 30.0, 0.0, 40.0, 0.01, "Db", onUpdateKnee ); + addModuleSlider( module, "ratio", 12.0, 1.0, 50.0, 0.1, "", onUpdateRatio ); + addModuleSlider( module, "attack", 0.003, 0.0, 1.0, 0.001, "sec", onUpdateAttack ); + addModuleSlider( module, "release", 0.25, 0.0, 2.0, 0.01, "sec", onUpdateRelease ); + + // after adding sliders, walk up to the module to store the audioNode. + module = module.parentNode; + + var audioNode = audioContext.createDynamicsCompressor(); + audioNode.threshold.value = -24.0; + audioNode.knee.value = 20.0; + audioNode.ratio.value = 12.0; + audioNode.attack.value = 0.003; + audioNode.release.value = 0.25; + module.audioNode = audioNode; +} - ctl = createNewDialControl( 50, initDefaultParam(filter.gain) ); - ctl.style.left = "10px"; - ctl.style.top = "100px"; - ctl.onUpdateDial = onUpdateFilterGain; - e.appendChild( ctl ); +function createConvolver() { + var module = createNewModule( "convolver", true, true ); -} + module = module.parentNode; + module.className += " has-footer has-loop"; -function createOscillatorNode() { - var e=createNewNode("Oscillator", true, true ); - var osc = audioContext.createOscillator(); - e.audioNode = osc; - osc.type=osc.SINE; + // Add footer element + var footer = document.createElement("footer"); - var ctl=document.createElement("select"); - ctl.type="select"; - ctl.value="type"; - ctl.addEventListener( "change", changeType ); + var looper = document.createElement("div"); + looper.className = "loop"; + var label = document.createElement("label"); + var check = document.createElement("input"); + check.type = "checkbox"; + check.onchange = onToggleNormalize; + label.appendChild(check); + label.appendChild(document.createTextNode(" norm")); + looper.appendChild(label); + footer.appendChild(looper); + var sel = document.createElement("select"); + sel.className = "ab-source"; var opt = document.createElement("option"); - opt.setAttribute("value", "sine"); - opt.appendChild(document.createTextNode("Sine")); - opt.setAttribute("selected"); - ctl.appendChild(opt); - opt = document.createElement("option"); - opt.setAttribute("value", "square"); - opt.appendChild(document.createTextNode("Square")); - ctl.appendChild(opt); + opt.appendChild( document.createTextNode("irHall.ogg")); + sel.appendChild( opt ); opt = document.createElement("option"); - opt.setAttribute("value", "sawtooth"); - opt.appendChild(document.createTextNode("Sawtooth")); - ctl.appendChild(opt); + opt.appendChild( document.createTextNode("irRoom.ogg")); + sel.appendChild( opt ); opt = document.createElement("option"); - opt.setAttribute("value", "triangle"); - opt.appendChild(document.createTextNode("Triangle")); - ctl.appendChild(opt); + opt.appendChild( document.createTextNode("irParkingGarage.ogg")); + sel.appendChild( opt ); + footer.appendChild( sel ); + module.appendChild( footer ); - ctl.selectedIndex="0"; - e.appendChild(ctl); + // Add select element and type options + var audioNode = audioContext.createConvolver(); + audioNode.buffer = irHallBuffer; + module.audioNode = audioNode; +} + +function createAnalyser() { + var module = createNewModule( "analyser", true, true ); - ctl=document.createElement("button"); - ctl.appendChild(document.createTextNode("note on")); - ctl.onclick = oscNoteOn; - e.appendChild(ctl); + var canvas = document.createElement( "canvas" ); + canvas.height = "140"; + canvas.width = "240"; + canvas.className = "analyserCanvas"; + canvas.style.webkitBoxReflect = "below 0px -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(0.9, transparent), to(white))" + canvas.style.backgroundImage = "url('img/analyser-bg.png')"; + module.appendChild( canvas ); - ctl=document.createElement("button"); - ctl.appendChild(document.createTextNode("note off")); - ctl.onclick = hitstop; - e.appendChild(ctl); + // after adding sliders, walk up to the module to store the audioNode. + module = module.parentNode; - ctl = createNewDialControl( 100, initDefaultParam(osc.frequency) ); - ctl.style.left = "10px"; - ctl.style.top = "55px"; - ctl.onUpdateDial = onUpdateFrequency; - e.appendChild( ctl ); + var audioNode = audioContext.createAnalyser(); + module.audioNode = audioNode; + + audioNode.smoothingTimeConstant = "0.25"; // not much smoothing + audioNode.fftSize = 512; + audioNode.maxDecibels = 0; + module.onConnectInput = onAnalyserConnectInput; + analysers.push(module); // Add to the list of analysers in the animation loop + module.drawingContext = canvas.getContext('2d'); } @@ -349,111 +521,68 @@ function onAnalyserConnectInput() { } } -function createDelayNode() { - var e=createNewNode("delay", true, true ); - var delayNode = audioContext.createDelayNode(); - e.audioNode = delayNode; +function createBiquadFilter() { + var module = createNewModule( "biquadfilter", true, true ); + addModuleSlider( module, "frequency", 440, 0, 20000, 1, "Hz", onUpdateFrequency ); + addModuleSlider( module, "Q", 1, 1, 100, 0.1, "", onUpdateQ ); + var gainSlider = addModuleSlider( module, "gain", 1.0, 0.0, 10.0, 0.01, "", onUpdateGain ); - delayNode.delayTime.value = 0.5; - var ctl = createNewDialControl( 100, 5 ); - ctl.style.left = "25px"; - ctl.style.top = "50px"; - ctl.onUpdateDial = onUpdateDelayTime; - e.appendChild( ctl ); -} - -function createAnalyserNode() { - var e=createNewNode("analyser", true, true ); - var analyser = audioContext.createAnalyser(); - analyser.smoothingTimeConstant = "0.25"; // not much smoothing - e.audioNode = analyser; - var canvas = document.createElement( "canvas" ); - canvas.style.left = "10px"; - canvas.style.top = "35px"; - canvas.style.position = "absolute"; - canvas.height = "120"; - canvas.width = "150"; - canvas.className = "analyserCanvas" - canvas.style.webkitBoxReflect = "below 0px -webkit-gradient(linear, left top, left bottom, from(transparent), color-stop(0.9, transparent), to(white))" - - e.appendChild( canvas ); - - e.onConnectInput = onAnalyserConnectInput; - analysers.push(e); // Add to the list of analysers in the animation loop - e.drawingContext = canvas.getContext('2d'); -// e.drawingContext.fillStyle = "blue"; -// e.drawingContext.fillRect(0,0,150, 120); -} + module = module.parentNode; + module.className += " has-footer"; -function createGainNode() { - var e=createNewNode("gain", true, true ); - var gainNode = audioContext.createGainNode(); - e.audioNode = gainNode; - - gainNode.gain.value = 1.0; - var ctl = createNewDialControl( 100, 100 ); - ctl.style.left = "25px"; - ctl.style.top = "50px"; - ctl.onUpdateDial = onUpdateABSNGain; - e.appendChild( ctl ); -} + // The gain slider should be disabled by default + $( gainSlider ).slider( "option", "disabled", true ); + // cache the gain slider for later use + module.gainSlider = gainSlider; -function createAudioNodeFromBuffer( buffer ) { - var e=createNewNode("audioBufferSource", false, true ); - - e.buffer=buffer; + // Add footer element + var footer = document.createElement("footer"); + var sel = document.createElement("select"); + sel.className = "filter-type"; + var opt = document.createElement("option"); + opt.appendChild( document.createTextNode("lowpass")); + sel.appendChild( opt ); + opt = document.createElement("option"); + opt.appendChild( document.createTextNode("highpass")); + sel.appendChild( opt ); + opt = document.createElement("option"); + opt.appendChild( document.createTextNode("bandpass")); + sel.appendChild( opt ); + opt = document.createElement("option"); + opt.appendChild( document.createTextNode("lowshelf")); + sel.appendChild( opt ); + opt = document.createElement("option"); + opt.appendChild( document.createTextNode("highshelf")); + sel.appendChild( opt ); + opt = document.createElement("option"); + opt.appendChild( document.createTextNode("peaking")); + sel.appendChild( opt ); + opt = document.createElement("option"); + opt.appendChild( document.createTextNode("notch")); + sel.appendChild( opt ); + opt = document.createElement("option"); + opt.appendChild( document.createTextNode("allpass")); + sel.appendChild( opt ); + sel.onchange = switchFilterTypes; + footer.appendChild( sel ); + module.appendChild( footer ); - var ctl=document.createElement("button"); - ctl.appendChild(document.createTextNode("play")); - ctl.onclick = hitplay; - e.appendChild(ctl); - - ctl=document.createElement("button"); - ctl.appendChild(document.createTextNode("stop")); - ctl.onclick = hitstop; - e.appendChild(ctl); - - ctl=document.createElement("input"); - ctl.type="checkbox"; - ctl.name="loop"; - ctl.value="loop"; - ctl.addEventListener( "onclick", flipLoop ); - e.appendChild(ctl); - e.appendChild(document.createTextNode("loop ")); - - e.gain = 1.0; - var ctl = createNewDialControl( 100, 100 ); - ctl.style.left = "25px"; - ctl.style.top = "50px"; - ctl.onUpdateDial = onUpdateABSNGain; - e.appendChild( ctl ); - - + // Add select element and type options + var audioNode = audioContext.createBiquadFilter(); + audioNode.frequency.value = 440.0; + audioNode.Q.value = 1.0; + audioNode.gain.value = 1.0; + module.audioNode = audioNode; } -function flipNormalize(e) { - e.target.checked = !e.target.checked; - if (e.target.parentNode.audioNode) - e.target.parentNode.audioNode.normalize = e.target.checked; -} +function deleteModule() { + var moduleElement = this.parentNode; -function createConvolverNodeFromBuffer( buffer ) { - var e=createNewNode("convolverNode", true, true ); - var convolver = audioContext.createConvolver(); - - e.audioNode = convolver; - e.buffer=buffer; - convolver.buffer = buffer; - - ctl=document.createElement("input"); - ctl.type="checkbox"; - ctl.name="normalize"; - ctl.value="normalize"; - ctl.addEventListener( "onclick", flipNormalize ); - e.appendChild(document.createElement("br")); - e.appendChild(ctl); - e.appendChild(document.createTextNode("normalize")); + // First disconnect the audio + disconnectNode( moduleElement ); + // Then delete the visual element + moduleElement.parentNode.removeChild( moduleElement ); } function downloadAudioFromURL( url ){ @@ -461,6 +590,8 @@ function downloadAudioFromURL( url ){ request.open('GET', url, true); request.responseType = 'arraybuffer'; + lastBufferLoaded = url; // TODO: get last bit after the last / + // Decode asynchronously request.onload = function() { audioContext.decodeAudioData( request.response, function(buffer) { @@ -487,8 +618,9 @@ function downloadImpulseFromURL( url ){ // Set up the page as a drop site for audio files. When an audio file is // dropped on the page, it will be auto-loaded as an AudioBufferSourceNode. function initDragDropOfAudioFiles() { - window.ondragover = function () { this.className = 'hover'; return false; }; - window.ondragend = function () { this.className = ''; return false; }; +// TODO: might want this indicator back +// window.ondragover = function () { this.className = 'hover'; return false; }; +// window.ondragend = function () { this.className = ''; return false; }; window.ondrop = function (e) { this.className = ''; e.preventDefault(); @@ -496,18 +628,26 @@ function initDragDropOfAudioFiles() { var reader = new FileReader(); reader.onload = function (event) { audioContext.decodeAudioData( event.target.result, function(buffer) { - createAudioNodeFromBuffer(buffer); + createAudioBufferSource(buffer); }, function(){alert("error loading!");} ); }; reader.onerror = function (event) { alert("Error: " + reader.error ); }; + lastBufferLoaded = e.dataTransfer.files[0].name; reader.readAsArrayBuffer(e.dataTransfer.files[0]); return false; }; } +var drumsBuffer, + bassBuffer, + voiceBuffer, + irHallBuffer, + irDrumRoomBuffer, + irParkingGarageBuffer; + // Initialization function for the page. function init() { try { @@ -522,6 +662,25 @@ function init() { initDragDropOfAudioFiles(); // set up page as a drop site for audio files + + var drumsRequest = new XMLHttpRequest(); + drumsRequest.open("GET", "sounds/drums.ogg", true); + drumsRequest.responseType = "arraybuffer"; + drumsRequest.onload = function() { + audioContext.decodeAudioData( drumsRequest.response, function(buffer) { + drumsBuffer = buffer; } ); + } + drumsRequest.send(); + var irHallRequest = new XMLHttpRequest(); + irHallRequest.open("GET", "sounds/irHall.ogg", true); + irHallRequest.responseType = "arraybuffer"; + irHallRequest.onload = function() { + audioContext.decodeAudioData( irHallRequest.response, function(buffer) { + irHallBuffer = buffer; } ); + } + irHallRequest.send(); + + // create the one-and-only destination node for the context var dest = document.getElementById("output"); dest.audioNode = audioContext.destination; diff --git a/audio/drums.ogg b/sounds/drums.ogg similarity index 100% rename from audio/drums.ogg rename to sounds/drums.ogg diff --git a/audio/hall.ogg b/sounds/irHall.ogg similarity index 100% rename from audio/hall.ogg rename to sounds/irHall.ogg diff --git a/audio/440Hz.ogg b/sounds/sine-440Hz.ogg similarity index 100% rename from audio/440Hz.ogg rename to sounds/sine-440Hz.ogg diff --git a/audio/youre-on-the-right-track.ogg b/sounds/youre-on-the-right-track.ogg similarity index 100% rename from audio/youre-on-the-right-track.ogg rename to sounds/youre-on-the-right-track.ogg