diff --git a/build/litegraph.js b/build/litegraph.js index 5f3d9b4cd..6471d303e 100644 --- a/build/litegraph.js +++ b/build/litegraph.js @@ -2512,6 +2512,7 @@ function LGraphCanvas( canvas, graph, options ) this.show_info = true; this.allow_dragcanvas = true; this.allow_dragnodes = true; + this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc this.always_render_background = false; this.render_connections_shadows = false; //too much cpu @@ -2987,7 +2988,7 @@ LGraphCanvas.prototype.processMouseDown = function(e) //when clicked on top of a node //and it is not interactive - if(n) + if(n && this.allow_interaction ) { if(!this.live_mode && !n.flags.pinned) this.bringToFront(n); //if it wasnt selected? @@ -3150,7 +3151,7 @@ LGraphCanvas.prototype.processMouseMove = function(e) this.dirty_canvas = true; this.dirty_bgcanvas = true; } - else + else if(this.allow_interaction) { if(this.connecting_node) this.dirty_canvas = true; @@ -3514,7 +3515,7 @@ LGraphCanvas.prototype.processDrop = function(e) return; } - if(node.onDropFile) + if( node.onDropFile || node.onDropData ) { var files = e.dataTransfer.files; if(files && files.length) @@ -3526,22 +3527,28 @@ LGraphCanvas.prototype.processDrop = function(e) var ext = LGraphCanvas.getFileExtension( filename ); //console.log(file); - //prepare reader - var reader = new FileReader(); - reader.onload = function (event) { - //console.log(event.target); - var data = event.target.result; - node.onDropFile( data, filename, file ); - }; + if(node.onDropFile) + node.onDropFile(file); - //read data - var type = file.type.split("/")[0]; - if(type == "text" || type == "") - reader.readAsText(file); - else if (type == "image") - reader.readAsDataURL(file); - else - reader.readAsArrayBuffer(file); + if(node.onDropData) + { + //prepare reader + var reader = new FileReader(); + reader.onload = function (event) { + //console.log(event.target); + var data = event.target.result; + node.onDropData( data, filename, file ); + }; + + //read data + var type = file.type.split("/")[0]; + if(type == "text" || type == "") + reader.readAsText(file); + else if (type == "image") + reader.readAsDataURL(file); + else + reader.readAsArrayBuffer(file); + } } } } @@ -3992,7 +3999,7 @@ LGraphCanvas.prototype.drawBackCanvas = function() if(this.background_image && this.scale > 0.5) { ctx.globalAlpha = (1.0 - 0.5 / this.scale) * this.editor_alpha; - ctx.webkitImageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false; + ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false; if(!this._bg_img || this._bg_img.name != this.background_image) { this._bg_img = new Image(); @@ -4021,7 +4028,7 @@ LGraphCanvas.prototype.drawBackCanvas = function() } ctx.globalAlpha = 1.0; - ctx.webkitImageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true; } if(this.onBackgroundRender) @@ -4862,12 +4869,14 @@ LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) for (var i in node.properties) { var value = node.properties[i] !== undefined ? node.properties[i] : " "; + //value could contain invalid html characters, clean that + value = LGraphCanvas.decodeHTML(value); entries.push({content: "" + i + "" + "" + value + "", value: i}); } if(!entries.length) return; - var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu},ref_window); + var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu, allow_html: true },ref_window); function inner_clicked( v, e, prev ) { @@ -4879,6 +4888,70 @@ LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) return false; } +LGraphCanvas.decodeHTML = function( str ) +{ + var e = document.createElement("div"); + e.innerText = str; + return e.innerHTML; +} + +LGraphCanvas.onShowTitleEditor = function( node, event ) +{ + var input_html = ""; + + var dialog = document.createElement("div"); + dialog.className = "graphdialog"; + dialog.innerHTML = "Title"; + var input = dialog.querySelector("input"); + if(input) + { + input.value = node.title; + input.addEventListener("keydown", function(e){ + if(e.keyCode != 13) + return; + inner(); + e.preventDefault(); + e.stopPropagation(); + }); + } + + var rect = this.canvas.getClientRects()[0]; + var offsetx = -20; + var offsety = -20; + if(rect) + { + offsetx -= rect.left; + offsety -= rect.top; + } + + if( event ) + { + dialog.style.left = (event.pageX + offsetx) + "px"; + dialog.style.top = (event.pageY + offsety)+ "px"; + } + else + { + dialog.style.left = (this.canvas.width * 0.5 + offsetx) + "px"; + dialog.style.top = (this.canvas.height * 0.5 + offsety) + "px"; + } + + var button = dialog.querySelector("button"); + button.addEventListener("click", inner ); + this.canvas.parentNode.appendChild( dialog ); + + function inner() + { + setValue( input.value ); + } + + function setValue(value) + { + node.title = value; + dialog.parentNode.removeChild( dialog ); + node.setDirtyCanvas(true,true); + } +} + LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) { if(!node || node.properties[ property ] === undefined ) @@ -5148,6 +5221,7 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node) null, {content:"Properties", is_menu: true, callback: LGraphCanvas.onShowMenuNodeProperties }, null, + {content:"Title", callback: LGraphCanvas.onShowTitleEditor }, {content:"Mode", is_menu: true, callback: LGraphCanvas.onMenuNodeMode }, {content:"Collapse", callback: LGraphCanvas.onMenuNodeCollapse }, {content:"Pin", callback: LGraphCanvas.onMenuNodePin }, @@ -5362,7 +5436,7 @@ function num2hex(triplet) { /* LiteGraph GUI elements *************************************/ -LiteGraph.createContextMenu = function(values,options, ref_window) +LiteGraph.createContextMenu = function(values, options, ref_window) { options = options || {}; this.options = options; @@ -5435,10 +5509,16 @@ LiteGraph.createContextMenu = function(values,options, ref_window) element.style.cursor = "pointer"; element.dataset["value"] = typeof(item) == "string" ? item : item.value; element.data = item; + + var content = ""; if(typeof(item) == "string") - element.innerHTML = values.constructor == Array ? values[i] : i; + content = values.constructor == Array ? values[i] : i; else - element.innerHTML = item.content ? item.content : i; + content = item.content ? item.content : i; + if(options.allow_html) + element.innerHTML = content; + else + element.innerText = content; element.addEventListener("click", on_click ); root.appendChild(element); @@ -7205,6 +7285,47 @@ MathClamp.prototype.getCode = function(lang) LiteGraph.registerNodeType("math/clamp", MathClamp ); + +//Math ABS +function MathLerp() +{ + this.properties = { f: 0.5 }; + this.addInput("A","number"); + this.addInput("B","number"); + + this.addOutput("out","number"); +} + +MathLerp.title = "Lerp"; +MathLerp.desc = "Linear Interpolation"; + +MathLerp.prototype.onExecute = function() +{ + var v1 = this.getInputData(0); + if(v1 == null) + v1 = 0; + var v2 = this.getInputData(1); + if(v2 == null) + v2 = 0; + + var f = this.properties.f; + + var _f = this.getInputData(2); + if(_f !== undefined) + f = _f; + + this.setOutputData(0, v1 * (1-f) + v2 * f ); +} + +MathLerp.prototype.onGetInputs = function() +{ + return [["f","number"]]; +} + +LiteGraph.registerNodeType("math/lerp", MathLerp); + + + //Math ABS function MathAbs() { @@ -12153,13 +12274,74 @@ LGAudio.onConnectionsChange = function( connection, slot, connected, link_info ) LGAudio.disconnect( local_audionode, target_audionode ); } +//this function helps creating wrappers to existing classes +LGAudio.createAudioNodeWrapper = function( class_object ) +{ + class_object.prototype.onPropertyChanged = function(name, value) + { + if(!this.audionode) + return; + + if( this.audionode[ name ] === undefined ) + return; + + if( this.audionode[ name ].value !== undefined ) + this.audionode[ name ].value = value; + else + this.audionode[ name ] = value; + } + + class_object.prototype.onConnectionsChange = LGAudio.onConnectionsChange; +} + + +LGAudio.cached_audios = {}; + +LGAudio.loadSound = function( url, on_complete, on_error ) +{ + if( LGAudio.cached_audios[ url ] && url.indexOf("blob:") == -1 ) + { + if(on_complete) + on_complete( LGAudio.cached_audios[ url ] ); + return; + } + + //load new sample + var request = new XMLHttpRequest(); + request.open('GET', url, true); + request.responseType = 'arraybuffer'; + + var context = LGAudio.getAudioContext(); + + // Decode asynchronously + request.onload = function() { + console.log("AudioSource loaded"); + context.decodeAudioData( request.response, function( buffer ) { + console.log("AudioSource decoded"); + LGAudio.cached_audios[ url ] = buffer; + if(on_complete) + on_complete( buffer ); + }, onError); + } + request.send(); + + function onError(err) + { + console.log("Audio loading sample error:",err); + if(on_error) + on_error(err); + } + + return request; +} + //**************************************************** function LGAudioSource() { this.properties = { - src: "demodata/audio.wav", + src: "", gain: 0.5, loop: true, autoplay: true, @@ -12212,6 +12394,8 @@ LGAudioSource.prototype.onStop = function() LGAudioSource.prototype.onRemoved = function() { this.stopAllSounds(); + if(this._dropped_url) + URL.revokeObjectURL( this._url ); } LGAudioSource.prototype.stopAllSounds = function() @@ -12235,7 +12419,6 @@ LGAudioSource.prototype.onExecute = function() var input = this.inputs[i]; if(!input.link) continue; - var v = this.getInputData(i); if( v === undefined ) continue; @@ -12311,36 +12494,19 @@ LGAudioSource.prototype.loadSound = function( url ) if(!url) return; - //load new sample - var request = new XMLHttpRequest(); - request.open('GET', url, true); - request.responseType = 'arraybuffer'; + this._request = LGAudio.loadSound( url, inner ); + this._loading_audio = true; - this._request = request; + this.boxcolor = "#AA4"; - var context = LGAudio.getAudioContext(); - - // Decode asynchronously - request.onload = function() { - context.decodeAudioData( request.response, function(buffer) { - that._audio_buffer = buffer; - if(that._url) - { - URL.revokeObjectURL( that._url ); - that._url = null; - } - - that._loading_audio = false; - //if is playing, then play it - if(that.graph && that.graph.status === LGraph.STATUS_RUNNING) - that.onStart(); - }, onError); - } - request.send(); - - function onError(err) + function inner( buffer ) { - console.log("Audio loading sample error:",err); + this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR; + that._audio_buffer = buffer; + that._loading_audio = false; + //if is playing, then play it + if(that.graph && that.graph.status === LGraph.STATUS_RUNNING) + that.onStart(); //this controls the autoplay already } } @@ -12359,11 +12525,12 @@ LGAudioSource.prototype.onGetOutputs = function() LGAudioSource.prototype.onDropFile = function(file) { - if(this._url) - URL.revokeObjectURL( this._url ); - this._url = URL.createObjectURL( file ); - this.properties.src = this._url; - this.loadSound( this._url ); + if(this._dropped_url) + URL.revokeObjectURL( this._dropped_url ); + var url = URL.createObjectURL( file ); + this.properties.src = url; + this.loadSound( url ); + this._dropped_url = url; } @@ -12417,15 +12584,24 @@ LGAudioAnalyser.prototype.onExecute = function() this.setOutputData(0,this._freq_bin); } + //send analyzer + if(this.isOutputConnected(1)) + this.setOutputData(1,this.audionode); + + //properties for(var i = 1; i < this.inputs.length; ++i) { var input = this.inputs[i]; + if(!input.link) + continue; var v = this.getInputData(i); if (v !== undefined) this.audionode[ input.name ].value = v; } + + //time domain //this.audionode.getFloatTimeDomainData( dataArray ); } @@ -12435,33 +12611,17 @@ LGAudioAnalyser.prototype.onGetInputs = function() return [["minDecibels","number"],["maxDecibels","number"],["smoothingTimeConstant","number"]]; } +/* +LGAudioAnalyser.prototype.onGetOutputs = function() +{ + return [["Analyzer","analyzer"]]; +} +*/ LGAudioAnalyser.title = "Analyser"; LGAudioAnalyser.desc = "Audio Analyser"; LiteGraph.registerNodeType( "audio/analyser", LGAudioAnalyser ); - - -//***************************************************** - -//this function helps creating wrappers to existing classes -function createAudioNodeWrapper( class_object ) -{ - class_object.prototype.onPropertyChanged = function(name, value) - { - if( this.audionode[ name ] === undefined ) - return; - - if( this.audionode[ name ].value !== undefined ) - this.audionode[ name ].value = value; - else - this.audionode[ name ] = value; - } - - class_object.prototype.onConnectionsChange = LGAudio.onConnectionsChange; -} - - //***************************************************** function LGAudioGain() @@ -12491,14 +12651,128 @@ LGAudioGain.prototype.onExecute = function() } } -createAudioNodeWrapper( LGAudioGain ); +LGAudio.createAudioNodeWrapper( LGAudioGain ); LGAudioGain.title = "Gain"; LGAudioGain.desc = "Audio gain"; LiteGraph.registerNodeType("audio/gain", LGAudioGain); +function LGAudioConvolver() +{ + //default + this.properties = { + impulse_src:"", + normalize: true + }; + this.audionode = LGAudio.getAudioContext().createConvolver(); + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +LGAudio.createAudioNodeWrapper( LGAudioConvolver ); + +LGAudioConvolver.prototype.onRemove = function() +{ + if(this._dropped_url) + URL.revokeObjectURL( this._dropped_url ); +} + +LGAudioConvolver.prototype.onPropertyChanged = function( name, value ) +{ + if( name == "impulse_src" ) + this.loadImpulse( value ); + else if( name == "normalize" ) + this.audionode.normalize = value; +} + +LGAudioConvolver.prototype.onDropFile = function(file) +{ + if(this._dropped_url) + URL.revokeObjectURL( this._dropped_url ); + this._dropped_url = URL.createObjectURL( file ); + this.properties.impulse_src = this._dropped_url; + this.loadImpulse( this._dropped_url ); +} + +LGAudioConvolver.prototype.loadImpulse = function( url ) +{ + var that = this; + + //kill previous load + if(this._request) + { + this._request.abort(); + this._request = null; + } + + this._impulse_buffer = null; + this._loading_impulse = false; + + if(!url) + return; + + //load new sample + this._request = LGAudio.loadSound( url, inner ); + this._loading_impulse = true; + + // Decode asynchronously + function inner( buffer ) { + that._impulse_buffer = buffer; + that.audionode.buffer = buffer; + console.log("Impulse signal set"); + that._loading_impulse = false; + } +} + +LGAudioConvolver.title = "Convolver"; +LGAudioConvolver.desc = "Convolves the signal (used for reverb)"; +LiteGraph.registerNodeType("audio/convolver", LGAudioConvolver); + + +function LGAudioDynamicsCompressor() +{ + //default + this.properties = { + threshold: -50, + knee: 40, + ratio: 12, + reduction: -20, + attack: 0, + release: 0.25 + }; + + this.audionode = LGAudio.getAudioContext().createDynamicsCompressor(); + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +LGAudio.createAudioNodeWrapper( LGAudioDynamicsCompressor ); + +LGAudioDynamicsCompressor.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + if(!input.link) + continue; + var v = this.getInputData(i); + if(v !== undefined) + this.audionode[ input.name ].value = v; + } +} + +LGAudioDynamicsCompressor.prototype.onGetInputs = function() +{ + return [["threshold","number"],["knee","number"],["ratio","number"],["reduction","number"],["attack","number"],["release","number"]]; +} + +LGAudioDynamicsCompressor.title = "DynamicsCompressor"; +LGAudioDynamicsCompressor.desc = "Dynamics Compressor"; +LiteGraph.registerNodeType("audio/dynamicsCompressor", LGAudioDynamicsCompressor); function LGAudioWaveShaper() @@ -12528,7 +12802,7 @@ LGAudioWaveShaper.prototype.setWaveShape = function(shape) this.audionode.curve = shape; } -createAudioNodeWrapper( LGAudioWaveShaper ); +LGAudio.createAudioNodeWrapper( LGAudioWaveShaper ); /* disabled till I dont find a way to do a wave shape LGAudioWaveShaper.title = "WaveShaper"; @@ -12578,7 +12852,8 @@ LGAudioMixer.prototype.onExecute = function() for(var i = 1; i < this.inputs.length; ++i) { var input = this.inputs[i]; - if(input.type == "audio") + + if(!input.link || input.type == "audio") continue; var v = this.getInputData(i); @@ -12592,7 +12867,7 @@ LGAudioMixer.prototype.onExecute = function() } } -createAudioNodeWrapper( LGAudioMixer ); +LGAudio.createAudioNodeWrapper( LGAudioMixer ); LGAudioMixer.title = "Mixer"; LGAudioMixer.desc = "Audio mixer"; @@ -12613,7 +12888,7 @@ function LGAudioDelay() this.addOutput("out","audio"); } -createAudioNodeWrapper( LGAudioDelay ); +LGAudio.createAudioNodeWrapper( LGAudioDelay ); LGAudioDelay.prototype.onExecute = function() { @@ -12653,6 +12928,8 @@ LGAudioBiquadFilter.prototype.onExecute = function() for(var i = 1; i < this.inputs.length; ++i) { var input = this.inputs[i]; + if(!input.link) + continue; var v = this.getInputData(i); if(v !== undefined) this.audionode[ input.name ].value = v; @@ -12664,7 +12941,7 @@ LGAudioBiquadFilter.prototype.onGetInputs = function() return [["frequency","number"],["detune","number"],["Q","number"]]; } -createAudioNodeWrapper( LGAudioBiquadFilter ); +LGAudio.createAudioNodeWrapper( LGAudioBiquadFilter ); LGAudioBiquadFilter.title = "BiquadFilter"; LGAudioBiquadFilter.desc = "Audio filter"; @@ -12679,10 +12956,12 @@ LiteGraph.registerNodeType("audio/biquadfilter", LGAudioBiquadFilter); function LGAudioVisualization() { this.properties = { - continuous: true + continuous: true, + mark: -1 }; this.addInput("freqs","array"); + this.addInput("mark","number"); this.size = [300,200]; this._last_buffer = null; } @@ -12690,6 +12969,10 @@ function LGAudioVisualization() LGAudioVisualization.prototype.onExecute = function() { this._last_buffer = this.getInputData(0); + var v = this.getInputData(1); + if(v !== undefined) + this.properties.mark = v; + this.setDirtyCanvas(true,false); } LGAudioVisualization.prototype.onDrawForeground = function(ctx) @@ -12699,6 +12982,7 @@ LGAudioVisualization.prototype.onDrawForeground = function(ctx) var buffer = this._last_buffer; + //delta represents how many samples we advance per pixel var delta = buffer.length / this.size[0]; var h = this.size[1]; @@ -12721,12 +13005,26 @@ LGAudioVisualization.prototype.onDrawForeground = function(ctx) { for(var i = 0; i < buffer.length; i+= delta) { - ctx.moveTo(x,h); - ctx.lineTo(x,h - (buffer[i|0]/255) * h); + ctx.moveTo(x+0.5,h); + ctx.lineTo(x+0.5,h - (buffer[i|0]/255) * h); x++; } } ctx.stroke(); + + if(this.properties.mark >= 0) + { + var samplerate = LGAudio.getAudioContext().sampleRate; + var binfreq = samplerate / buffer.length; + var x = 2 * (this.properties.mark / binfreq) / delta; + if(x >= this.size[0]) + x = this.size[0]-1; + ctx.strokeStyle = "red"; + ctx.beginPath(); + ctx.moveTo(x,h); + ctx.lineTo(x,0); + ctx.stroke(); + } } LGAudioVisualization.title = "Visualization"; @@ -12734,6 +13032,59 @@ LGAudioVisualization.desc = "Audio Visualization"; LiteGraph.registerNodeType("audio/visualization", LGAudioVisualization); +function LGAudioBandSignal() +{ + //default + this.properties = { + band: 440, + amplitude: 1 + }; + + this.addInput("freqs","array"); + this.addOutput("signal","number"); +} + +LGAudioBandSignal.prototype.onExecute = function() +{ + this._freqs = this.getInputData(0); + if( !this._freqs ) + return; + + var band = this.properties.band; + var v = this.getInputData(1); + if(v !== undefined) + band = v; + + var samplerate = LGAudio.getAudioContext().sampleRate; + var binfreq = samplerate / this._freqs.length; + var index = 2 * (band / binfreq); + var v = 0; + if( index < 0 ) + v = this._freqs[ 0 ]; + if( index >= this._freqs.length ) + v = this._freqs[ this._freqs.length - 1]; + else + { + var pos = index|0; + var v0 = this._freqs[ pos ]; + var v1 = this._freqs[ pos+1 ]; + var f = index - pos; + v = v0 * (1-f) + v1 * f; + } + + this.setOutputData( 0, (v/255) * this.properties.amplitude ); +} + +LGAudioBandSignal.prototype.onGetInputs = function() +{ + return [["band","number"]]; +} + +LGAudioBandSignal.title = "Signal"; +LGAudioBandSignal.desc = "extract the signal of some frequency"; +LiteGraph.registerNodeType("audio/signal", LGAudioBandSignal); + + function LGAudioDestination() { diff --git a/demo/code.js b/demo/code.js index 2ef7b1ce2..97ecb45e9 100755 --- a/demo/code.js +++ b/demo/code.js @@ -33,5 +33,6 @@ function addDemo( name, url ) //some examples addDemo("Audio", "examples/audio.json"); addDemo("Audio Delay", "examples/audio_delay.json"); +addDemo("Audio Reverb", "examples/audio_reverb.json"); diff --git a/demo/demodata/impulse.wav b/demo/demodata/impulse.wav new file mode 100644 index 000000000..3ce6a1b04 Binary files /dev/null and b/demo/demodata/impulse.wav differ diff --git a/demo/examples/audio.json b/demo/examples/audio.json index 8a6d853ca..3f577d12a 100644 --- a/demo/examples/audio.json +++ b/demo/examples/audio.json @@ -1 +1,689 @@ -{"iteration":55277,"last_node_id":11,"last_link_id":10,"links":{"0":{"id":0,"origin_id":0,"origin_slot":0,"target_id":2,"target_slot":0,"data":null},"1":{"id":1,"origin_id":2,"origin_slot":0,"target_id":1,"target_slot":0,"data":null},"2":{"id":2,"origin_id":2,"origin_slot":0,"target_id":3,"target_slot":0,"data":null},"3":{"id":3,"origin_id":3,"origin_slot":0,"target_id":4,"target_slot":0,"data":null},"4":{"id":4,"origin_id":5,"origin_slot":0,"target_id":2,"target_slot":1,"data":null},"5":{"id":5,"origin_id":6,"origin_slot":0,"target_id":0,"target_slot":0,"data":null},"6":{"id":6,"origin_id":7,"origin_slot":0,"target_id":0,"target_slot":1,"data":null},"7":{"id":7,"origin_id":8,"origin_slot":0,"target_id":0,"target_slot":2,"data":null},"8":{"id":8,"origin_id":9,"origin_slot":0,"target_id":0,"target_slot":3,"data":null},"9":{"id":9,"origin_id":9,"origin_slot":0,"target_id":10,"target_slot":0,"data":null}},"config":{},"nodes":[{"id":1,"title":"Destination","type":"audio/destination","pos":[673,171],"size":{"0":140,"1":20},"flags":{},"inputs":[{"name":"in","type":"audio","link":1}],"mode":0,"properties":{}},{"id":2,"title":"BiquadFilter","type":"audio/biquadfilter","pos":[437,251],"size":{"0":140,"1":34},"flags":{},"inputs":[{"name":"in","type":"audio","link":0},{"name":"frequency","type":"number","link":4}],"outputs":[{"name":"out","type":"audio","links":[1,2]}],"mode":0,"properties":{"frequency":350,"detune":0,"Q":1,"type":"lowpass"}},{"id":3,"title":"Analyser","type":"audio/analyser","pos":[672,306],"size":{"0":140,"1":20},"flags":{},"inputs":[{"name":"in","type":"audio","link":2}],"outputs":[{"name":"freqs","type":"array","links":[3]}],"mode":0,"properties":{"fftSize":2048,"minDecibels":-100,"maxDecibels":-10,"smoothingTimeConstant":0.5}},{"id":6,"title":"Knob","type":"widget/knob","pos":[120,183],"size":[54,74],"flags":{},"outputs":[{"name":"","type":"number","links":[5]}],"mode":0,"properties":{"min":0,"max":1,"value":0.5099999999999996,"wcolor":"#7AF","size":50},"boxcolor":"rgba(128,128,128,1.0)"},{"id":0,"title":"Source","type":"audio/source","pos":[251,196],"size":{"0":140,"1":62},"flags":{},"inputs":[{"name":"gain","type":"number","link":5},{"name":"Play","type":-1,"link":6},{"name":"Stop","type":-1,"link":7},{"name":"playbackRate","type":"number","link":8}],"outputs":[{"name":"out","type":"audio","links":[0]}],"mode":0,"properties":{"src":"demodata/audio.wav","gain":0.5,"loop":false,"autoplay":true,"playbackRate":1.0000000000000002}},{"id":8,"title":"Button","type":"widget/button","pos":[274,106],"size":[128,43],"flags":{},"outputs":[{"name":"clicked","type":-1,"links":[7]}],"mode":0,"properties":{"text":"Stop","font":"40px Arial","message":""}},{"id":5,"title":"Knob","type":"widget/knob","pos":[125,293],"size":[54,74],"flags":{},"outputs":[{"name":"","type":"number","links":[4]}],"mode":0,"properties":{"min":0,"max":20000,"value":14800.00000000001,"wcolor":"#7AF","size":50},"boxcolor":"rgba(189,189,189,1.0)"},{"id":10,"title":"Watch","type":"basic/watch","pos":[516,123],"size":{"0":140,"1":20},"flags":{},"inputs":[{"name":"value","type":0,"link":9,"label":"1.000"}],"outputs":[{"name":"value","type":0,"links":null,"label":""}],"mode":0,"properties":{"value":1.0000000000000002}},{"id":9,"title":"Knob","type":"widget/knob","pos":[429,119],"size":[64,84],"flags":{},"outputs":[{"name":"","type":"number","links":[8,9]}],"mode":0,"properties":{"min":0,"max":4,"value":1.0000000000000002,"wcolor":"#7AF","size":50},"boxcolor":"rgba(64,64,64,1.0)"},{"id":7,"title":"Button","type":"widget/button","pos":[114,103],"size":[128,43],"flags":{},"outputs":[{"name":"clicked","type":-1,"links":[6]}],"mode":0,"properties":{"text":"Play","font":"40px Arial","message":""}},{"id":4,"title":"Visualization","type":"audio/visualization","pos":[132,394],"size":[662,180],"flags":{},"inputs":[{"name":"freqs","type":"array","link":3}],"mode":0,"properties":{"continuous":true}}]} \ No newline at end of file +{ + "iteration": 139049, + "last_node_id": 16, + "last_link_id": 16, + "links": { + "0": { + "id": 0, + "origin_id": 0, + "origin_slot": 0, + "target_id": 2, + "target_slot": 0, + "data": null + }, + "1": { + "id": 1, + "origin_id": 2, + "origin_slot": 0, + "target_id": 1, + "target_slot": 0, + "data": null + }, + "2": { + "id": 2, + "origin_id": 2, + "origin_slot": 0, + "target_id": 3, + "target_slot": 0, + "data": null + }, + "3": { + "id": 3, + "origin_id": 3, + "origin_slot": 0, + "target_id": 4, + "target_slot": 0, + "data": null + }, + "4": { + "id": 4, + "origin_id": 5, + "origin_slot": 0, + "target_id": 2, + "target_slot": 1, + "data": null + }, + "5": { + "id": 5, + "origin_id": 6, + "origin_slot": 0, + "target_id": 0, + "target_slot": 0, + "data": null + }, + "6": { + "id": 6, + "origin_id": 7, + "origin_slot": 0, + "target_id": 0, + "target_slot": 1, + "data": null + }, + "7": { + "id": 7, + "origin_id": 8, + "origin_slot": 0, + "target_id": 0, + "target_slot": 2, + "data": null + }, + "8": { + "id": 8, + "origin_id": 9, + "origin_slot": 0, + "target_id": 0, + "target_slot": 3, + "data": null + }, + "9": { + "id": 9, + "origin_id": 9, + "origin_slot": 0, + "target_id": 10, + "target_slot": 0, + "data": null + }, + "10": { + "id": 10, + "origin_id": 3, + "origin_slot": 0, + "target_id": 11, + "target_slot": 0, + "data": null + }, + "11": { + "id": 11, + "origin_id": 12, + "origin_slot": 0, + "target_id": 11, + "target_slot": 1, + "data": null + }, + "12": { + "id": 12, + "origin_id": 11, + "origin_slot": 0, + "target_id": 13, + "target_slot": 0, + "data": null + }, + "13": { + "id": 13, + "origin_id": 12, + "origin_slot": 0, + "target_id": 4, + "target_slot": 1, + "data": null + }, + "14": { + "id": 14, + "origin_id": 13, + "origin_slot": 0, + "target_id": 14, + "target_slot": 0, + "data": null + }, + "15": { + "id": 15, + "origin_id": 12, + "origin_slot": 0, + "target_id": 15, + "target_slot": 0, + "data": null + } + }, + "config": {}, + "nodes": [ + { + "id": 1, + "title": "Destination", + "type": "audio/destination", + "pos": [ + 673, + 171 + ], + "size": { + "0": 140, + "1": 20 + }, + "flags": {}, + "inputs": [ + { + "name": "in", + "type": "audio", + "link": 1 + } + ], + "mode": 0, + "properties": {} + }, + { + "id": 2, + "title": "BiquadFilter", + "type": "audio/biquadfilter", + "pos": [ + 437, + 251 + ], + "size": { + "0": 140, + "1": 34 + }, + "flags": {}, + "inputs": [ + { + "name": "in", + "type": "audio", + "link": 0 + }, + { + "name": "frequency", + "type": "number", + "link": 4 + } + ], + "outputs": [ + { + "name": "out", + "type": "audio", + "links": [ + 1, + 2 + ] + } + ], + "mode": 0, + "properties": { + "frequency": 350, + "detune": 0, + "Q": 1, + "type": "lowpass" + } + }, + { + "id": 6, + "title": "Knob", + "type": "widget/knob", + "pos": [ + 120, + 183 + ], + "size": [ + 54, + 74 + ], + "flags": {}, + "outputs": [ + { + "name": "", + "type": "number", + "links": [ + 5 + ] + } + ], + "mode": 0, + "properties": { + "min": 0, + "max": 1, + "value": 0.5099999999999996, + "wcolor": "#7AF", + "size": 50 + }, + "boxcolor": "rgba(128,128,128,1.0)" + }, + { + "id": 0, + "title": "Source", + "type": "audio/source", + "pos": [ + 251, + 196 + ], + "size": { + "0": 140, + "1": 62 + }, + "flags": {}, + "inputs": [ + { + "name": "gain", + "type": "number", + "link": 5 + }, + { + "name": "Play", + "type": -1, + "link": 6 + }, + { + "name": "Stop", + "type": -1, + "link": 7 + }, + { + "name": "playbackRate", + "type": "number", + "link": 8 + } + ], + "outputs": [ + { + "name": "out", + "type": "audio", + "links": [ + 0 + ] + } + ], + "mode": 0, + "properties": { + "src": "demodata/audio.wav", + "gain": 0.5, + "loop": true, + "autoplay": true, + "playbackRate": 0.24000000000000002 + } + }, + { + "id": 5, + "title": "Knob", + "type": "widget/knob", + "pos": [ + 125, + 293 + ], + "size": [ + 54, + 74 + ], + "flags": {}, + "outputs": [ + { + "name": "", + "type": "number", + "links": [ + 4 + ] + } + ], + "mode": 0, + "properties": { + "min": 0, + "max": 20000, + "value": 14800.00000000001, + "wcolor": "#7AF", + "size": 50 + }, + "boxcolor": "rgba(128,128,128,1.0)" + }, + { + "id": 10, + "title": "Watch", + "type": "basic/watch", + "pos": [ + 516, + 123 + ], + "size": { + "0": 140, + "1": 20 + }, + "flags": {}, + "inputs": [ + { + "name": "value", + "type": 0, + "link": 9, + "label": "0.240" + } + ], + "outputs": [ + { + "name": "value", + "type": 0, + "links": null, + "label": "" + } + ], + "mode": 0, + "properties": { + "value": 0.24000000000000002 + } + }, + { + "id": 8, + "title": "Button", + "type": "widget/button", + "pos": [ + 274, + 106 + ], + "size": [ + 128, + 43 + ], + "flags": {}, + "outputs": [ + { + "name": "clicked", + "type": -1, + "links": [ + 7 + ] + } + ], + "mode": 0, + "properties": { + "text": "Stop", + "font": "40px Arial", + "message": "" + } + }, + { + "id": 9, + "title": "Knob", + "type": "widget/knob", + "pos": [ + 429, + 119 + ], + "size": [ + 54, + 74 + ], + "flags": {}, + "outputs": [ + { + "name": "", + "type": "number", + "links": [ + 8, + 9 + ] + } + ], + "mode": 0, + "properties": { + "min": 0, + "max": 4, + "value": 0.24000000000000002, + "wcolor": "#7AF", + "size": 50 + }, + "boxcolor": "rgba(128,128,128,1.0)" + }, + { + "id": 7, + "title": "Button", + "type": "widget/button", + "pos": [ + 114, + 103 + ], + "size": [ + 128, + 43 + ], + "flags": {}, + "outputs": [ + { + "name": "clicked", + "type": -1, + "links": [ + 6 + ] + } + ], + "mode": 0, + "properties": { + "text": "Play", + "font": "40px Arial", + "message": "" + } + }, + { + "id": 3, + "title": "Analyser", + "type": "audio/analyser", + "pos": [ + 672, + 306 + ], + "size": { + "0": 140, + "1": 20 + }, + "flags": {}, + "inputs": [ + { + "name": "in", + "type": "audio", + "link": 2 + } + ], + "outputs": [ + { + "name": "freqs", + "type": "array", + "links": [ + 3, + 10 + ] + } + ], + "mode": 0, + "properties": { + "fftSize": 2048, + "minDecibels": -100, + "maxDecibels": -10, + "smoothingTimeConstant": 0.5 + } + }, + { + "id": 11, + "title": "Signal", + "type": "audio/signal", + "pos": [ + 241, + 391 + ], + "size": { + "0": 140, + "1": 34 + }, + "flags": {}, + "inputs": [ + { + "name": "freqs", + "type": "array", + "link": 10 + }, + { + "name": "band", + "type": "number", + "link": 11 + } + ], + "outputs": [ + { + "name": "signal", + "type": "number", + "links": [ + 12 + ] + } + ], + "mode": 0, + "properties": { + "band": 440, + "amplitude": 1, + "samplerate": 44100 + } + }, + { + "id": 14, + "title": "Progress", + "type": "widget/progress", + "pos": [ + 91, + 568 + ], + "size": { + "0": 140, + "1": 20 + }, + "flags": {}, + "inputs": [ + { + "name": "", + "type": "number", + "link": 14 + } + ], + "mode": 0, + "properties": { + "min": 0, + "max": 1, + "value": 0.3843137254901945, + "wcolor": "#AAF" + } + }, + { + "id": 13, + "title": "Max. Signal", + "type": "basic/watch", + "pos": [ + 92, + 527 + ], + "size": { + "0": 140, + "1": 20 + }, + "flags": {}, + "inputs": [ + { + "name": "value", + "type": 0, + "link": 12, + "label": "0.396" + } + ], + "outputs": [ + { + "name": "value", + "type": 0, + "links": [ + 14 + ], + "label": "" + } + ], + "mode": 0, + "properties": { + "value": 0.3843137254901945 + } + }, + { + "id": 4, + "title": "Visualization", + "type": "audio/visualization", + "pos": [ + 253, + 497 + ], + "size": [ + 662, + 180 + ], + "flags": {}, + "inputs": [ + { + "name": "freqs", + "type": "array", + "link": 3 + }, + { + "name": "mark", + "type": "number", + "link": 13 + } + ], + "mode": 0, + "properties": { + "continuous": true, + "mark": 12000.000000000005, + "samplerate": 44100 + } + }, + { + "id": 15, + "title": "Watch", + "type": "basic/watch", + "pos": [ + 93, + 398 + ], + "size": { + "0": 87, + "1": 20 + }, + "flags": {}, + "inputs": [ + { + "name": "value", + "type": 0, + "link": 15, + "label": "12000.000" + } + ], + "outputs": [ + { + "name": "value", + "type": 0, + "links": null, + "label": "" + } + ], + "mode": 0, + "properties": { + "value": 12000.000000000005 + } + }, + { + "id": 12, + "title": "Knob", + "type": "widget/knob", + "pos": [ + 92, + 435 + ], + "size": [ + 54, + 74 + ], + "flags": {}, + "outputs": [ + { + "name": "", + "type": "number", + "links": [ + 11, + 13, + 15 + ] + } + ], + "mode": 0, + "properties": { + "min": 0, + "max": 24000, + "value": 12000.000000000005, + "wcolor": "#7AF", + "size": 50 + }, + "boxcolor": "rgba(128,128,128,1.0)" + } + ] +} \ No newline at end of file diff --git a/demo/examples/audio_reverb.json b/demo/examples/audio_reverb.json new file mode 100644 index 000000000..511a4d1bb --- /dev/null +++ b/demo/examples/audio_reverb.json @@ -0,0 +1 @@ +{"iteration":48488,"last_node_id":8,"last_link_id":9,"links":{"0":{"id":0,"origin_id":0,"origin_slot":0,"target_id":1,"target_slot":0,"data":null},"1":{"id":1,"origin_id":0,"origin_slot":0,"target_id":2,"target_slot":0,"data":null},"3":{"id":3,"origin_id":1,"origin_slot":0,"target_id":3,"target_slot":0,"data":null},"4":{"id":4,"origin_id":4,"origin_slot":0,"target_id":1,"target_slot":1,"data":null},"5":{"id":5,"origin_id":5,"origin_slot":0,"target_id":1,"target_slot":3,"data":null},"6":{"id":6,"origin_id":6,"origin_slot":0,"target_id":2,"target_slot":1,"data":null},"7":{"id":7,"origin_id":0,"origin_slot":0,"target_id":7,"target_slot":0,"data":null},"8":{"id":8,"origin_id":7,"origin_slot":0,"target_id":1,"target_slot":2,"data":null}},"config":{},"nodes":[{"id":1,"title":"Mixer","type":"audio/mixer","pos":[655,183],"size":{"0":140,"1":62},"flags":{},"inputs":[{"name":"in1","type":"audio","link":0},{"name":"in1 gain","type":"number","link":4},{"name":"in2","type":"audio","link":8},{"name":"in2 gain","type":"number","link":5}],"outputs":[{"name":"out","type":"audio","links":[3]}],"mode":0,"properties":{"gain1":0.5,"gain2":0.8}},{"id":3,"title":"Destination","type":"audio/destination","pos":[911,180],"size":{"0":140,"1":20},"flags":{},"inputs":[{"name":"in","type":"audio","link":3}],"mode":0,"properties":{}},{"id":0,"title":"Source","type":"audio/source","pos":[195,187],"size":{"0":140,"1":20},"flags":{},"inputs":[{"name":"gain","type":"number","link":null}],"outputs":[{"name":"out","type":"audio","links":[0,7]}],"mode":0,"properties":{"src":"demodata/audio.wav","gain":0.5,"loop":true,"autoplay":true,"playbackRate":1}},{"id":7,"title":"Convolver","type":"audio/convolver","pos":[421,253],"size":{"0":140,"1":20},"flags":{},"inputs":[{"name":"in","type":"audio","link":7}],"outputs":[{"name":"out","type":"audio","links":[8]}],"mode":0,"properties":{"impulse_src":"demodata/impulse.wav","normalize":true}},{"id":5,"title":"Reverb","type":"widget/knob","pos":[398,311],"size":[84,100],"flags":{},"outputs":[{"name":"","type":"number","links":[5]}],"mode":0,"properties":{"min":0,"max":1,"value":0.4099999999999999,"wcolor":"#7AF","size":50},"boxcolor":"rgba(128,128,128,1.0)"},{"id":4,"title":"Main","type":"widget/knob","pos":[408,59],"size":[81,93],"flags":{},"outputs":[{"name":"","type":"number","links":[4]}],"mode":0,"properties":{"min":0,"max":1,"value":0.21000000000000002,"wcolor":"#7AF","size":50},"boxcolor":"rgba(128,128,128,1.0)"}]} \ No newline at end of file diff --git a/imgs/elephant.gif b/imgs/elephant.gif new file mode 100644 index 000000000..3bd28df3c Binary files /dev/null and b/imgs/elephant.gif differ diff --git a/imgs/webglstudio.gif b/imgs/webglstudio.gif new file mode 100644 index 000000000..7ba42947a Binary files /dev/null and b/imgs/webglstudio.gif differ diff --git a/src/litegraph-editor.js b/src/litegraph-editor.js index 9d121d1c3..8df40aab6 100755 --- a/src/litegraph-editor.js +++ b/src/litegraph-editor.js @@ -22,8 +22,8 @@ function Editor( container_id, options ) graph.onAfterExecute = function() { graphcanvas.draw(true) }; //add stuff - this.addToolsButton("loadsession_button","Load","imgs/icon-load.png", this.onLoadButton.bind(this), ".tools-left" ); - this.addToolsButton("savesession_button","Save","imgs/icon-save.png", this.onSaveButton.bind(this), ".tools-left" ); + //this.addToolsButton("loadsession_button","Load","imgs/icon-load.png", this.onLoadButton.bind(this), ".tools-left" ); + //this.addToolsButton("savesession_button","Save","imgs/icon-save.png", this.onSaveButton.bind(this), ".tools-left" ); this.addLoadCounter(); this.addToolsButton("playnode_button","Play","imgs/icon-play.png", this.onPlayButton.bind(this), ".tools-right" ); this.addToolsButton("playstepnode_button","Step","imgs/icon-playstep.png", this.onPlayStepButton.bind(this), ".tools-right" ); @@ -186,8 +186,14 @@ Editor.prototype.addMiniWindow = function(w,h) var graphcanvas = new LGraphCanvas(canvas, this.graph); graphcanvas.background_image = "imgs/grid.png"; graphcanvas.scale = 0.25; + graphcanvas.allow_dragnodes = false; + graphcanvas.allow_interaction = false; this.miniwindow_graphcanvas = graphcanvas; - graphcanvas.onClear = function() { graphcanvas.scale = 0.25; }; + graphcanvas.onClear = function() { + graphcanvas.scale = 0.25; + graphcanvas.allow_dragnodes = false; + graphcanvas.allow_interaction = false; + }; miniwindow.style.position = "absolute"; miniwindow.style.top = "4px"; diff --git a/src/litegraph.js b/src/litegraph.js index 173a31222..98326af92 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -2511,6 +2511,7 @@ function LGraphCanvas( canvas, graph, options ) this.show_info = true; this.allow_dragcanvas = true; this.allow_dragnodes = true; + this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc this.always_render_background = false; this.render_connections_shadows = false; //too much cpu @@ -2986,7 +2987,7 @@ LGraphCanvas.prototype.processMouseDown = function(e) //when clicked on top of a node //and it is not interactive - if(n) + if(n && this.allow_interaction ) { if(!this.live_mode && !n.flags.pinned) this.bringToFront(n); //if it wasnt selected? @@ -3149,7 +3150,7 @@ LGraphCanvas.prototype.processMouseMove = function(e) this.dirty_canvas = true; this.dirty_bgcanvas = true; } - else + else if(this.allow_interaction) { if(this.connecting_node) this.dirty_canvas = true; @@ -3513,7 +3514,7 @@ LGraphCanvas.prototype.processDrop = function(e) return; } - if(node.onDropFile) + if( node.onDropFile || node.onDropData ) { var files = e.dataTransfer.files; if(files && files.length) @@ -3525,22 +3526,28 @@ LGraphCanvas.prototype.processDrop = function(e) var ext = LGraphCanvas.getFileExtension( filename ); //console.log(file); - //prepare reader - var reader = new FileReader(); - reader.onload = function (event) { - //console.log(event.target); - var data = event.target.result; - node.onDropFile( data, filename, file ); - }; + if(node.onDropFile) + node.onDropFile(file); - //read data - var type = file.type.split("/")[0]; - if(type == "text" || type == "") - reader.readAsText(file); - else if (type == "image") - reader.readAsDataURL(file); - else - reader.readAsArrayBuffer(file); + if(node.onDropData) + { + //prepare reader + var reader = new FileReader(); + reader.onload = function (event) { + //console.log(event.target); + var data = event.target.result; + node.onDropData( data, filename, file ); + }; + + //read data + var type = file.type.split("/")[0]; + if(type == "text" || type == "") + reader.readAsText(file); + else if (type == "image") + reader.readAsDataURL(file); + else + reader.readAsArrayBuffer(file); + } } } } @@ -3991,7 +3998,7 @@ LGraphCanvas.prototype.drawBackCanvas = function() if(this.background_image && this.scale > 0.5) { ctx.globalAlpha = (1.0 - 0.5 / this.scale) * this.editor_alpha; - ctx.webkitImageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false; + ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false; if(!this._bg_img || this._bg_img.name != this.background_image) { this._bg_img = new Image(); @@ -4020,7 +4027,7 @@ LGraphCanvas.prototype.drawBackCanvas = function() } ctx.globalAlpha = 1.0; - ctx.webkitImageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true; } if(this.onBackgroundRender) @@ -4861,12 +4868,14 @@ LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) for (var i in node.properties) { var value = node.properties[i] !== undefined ? node.properties[i] : " "; + //value could contain invalid html characters, clean that + value = LGraphCanvas.decodeHTML(value); entries.push({content: "" + i + "" + "" + value + "", value: i}); } if(!entries.length) return; - var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu},ref_window); + var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu, allow_html: true },ref_window); function inner_clicked( v, e, prev ) { @@ -4878,6 +4887,70 @@ LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) return false; } +LGraphCanvas.decodeHTML = function( str ) +{ + var e = document.createElement("div"); + e.innerText = str; + return e.innerHTML; +} + +LGraphCanvas.onShowTitleEditor = function( node, event ) +{ + var input_html = ""; + + var dialog = document.createElement("div"); + dialog.className = "graphdialog"; + dialog.innerHTML = "Title"; + var input = dialog.querySelector("input"); + if(input) + { + input.value = node.title; + input.addEventListener("keydown", function(e){ + if(e.keyCode != 13) + return; + inner(); + e.preventDefault(); + e.stopPropagation(); + }); + } + + var rect = this.canvas.getClientRects()[0]; + var offsetx = -20; + var offsety = -20; + if(rect) + { + offsetx -= rect.left; + offsety -= rect.top; + } + + if( event ) + { + dialog.style.left = (event.pageX + offsetx) + "px"; + dialog.style.top = (event.pageY + offsety)+ "px"; + } + else + { + dialog.style.left = (this.canvas.width * 0.5 + offsetx) + "px"; + dialog.style.top = (this.canvas.height * 0.5 + offsety) + "px"; + } + + var button = dialog.querySelector("button"); + button.addEventListener("click", inner ); + this.canvas.parentNode.appendChild( dialog ); + + function inner() + { + setValue( input.value ); + } + + function setValue(value) + { + node.title = value; + dialog.parentNode.removeChild( dialog ); + node.setDirtyCanvas(true,true); + } +} + LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) { if(!node || node.properties[ property ] === undefined ) @@ -5147,6 +5220,7 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node) null, {content:"Properties", is_menu: true, callback: LGraphCanvas.onShowMenuNodeProperties }, null, + {content:"Title", callback: LGraphCanvas.onShowTitleEditor }, {content:"Mode", is_menu: true, callback: LGraphCanvas.onMenuNodeMode }, {content:"Collapse", callback: LGraphCanvas.onMenuNodeCollapse }, {content:"Pin", callback: LGraphCanvas.onMenuNodePin }, @@ -5361,7 +5435,7 @@ function num2hex(triplet) { /* LiteGraph GUI elements *************************************/ -LiteGraph.createContextMenu = function(values,options, ref_window) +LiteGraph.createContextMenu = function(values, options, ref_window) { options = options || {}; this.options = options; @@ -5434,10 +5508,16 @@ LiteGraph.createContextMenu = function(values,options, ref_window) element.style.cursor = "pointer"; element.dataset["value"] = typeof(item) == "string" ? item : item.value; element.data = item; + + var content = ""; if(typeof(item) == "string") - element.innerHTML = values.constructor == Array ? values[i] : i; + content = values.constructor == Array ? values[i] : i; else - element.innerHTML = item.content ? item.content : i; + content = item.content ? item.content : i; + if(options.allow_html) + element.innerHTML = content; + else + element.innerText = content; element.addEventListener("click", on_click ); root.appendChild(element); diff --git a/src/nodes/audio.js b/src/nodes/audio.js index c7440a0cc..b4fb99307 100644 --- a/src/nodes/audio.js +++ b/src/nodes/audio.js @@ -151,13 +151,74 @@ LGAudio.onConnectionsChange = function( connection, slot, connected, link_info ) LGAudio.disconnect( local_audionode, target_audionode ); } +//this function helps creating wrappers to existing classes +LGAudio.createAudioNodeWrapper = function( class_object ) +{ + class_object.prototype.onPropertyChanged = function(name, value) + { + if(!this.audionode) + return; + + if( this.audionode[ name ] === undefined ) + return; + + if( this.audionode[ name ].value !== undefined ) + this.audionode[ name ].value = value; + else + this.audionode[ name ] = value; + } + + class_object.prototype.onConnectionsChange = LGAudio.onConnectionsChange; +} + + +LGAudio.cached_audios = {}; + +LGAudio.loadSound = function( url, on_complete, on_error ) +{ + if( LGAudio.cached_audios[ url ] && url.indexOf("blob:") == -1 ) + { + if(on_complete) + on_complete( LGAudio.cached_audios[ url ] ); + return; + } + + //load new sample + var request = new XMLHttpRequest(); + request.open('GET', url, true); + request.responseType = 'arraybuffer'; + + var context = LGAudio.getAudioContext(); + + // Decode asynchronously + request.onload = function() { + console.log("AudioSource loaded"); + context.decodeAudioData( request.response, function( buffer ) { + console.log("AudioSource decoded"); + LGAudio.cached_audios[ url ] = buffer; + if(on_complete) + on_complete( buffer ); + }, onError); + } + request.send(); + + function onError(err) + { + console.log("Audio loading sample error:",err); + if(on_error) + on_error(err); + } + + return request; +} + //**************************************************** function LGAudioSource() { this.properties = { - src: "demodata/audio.wav", + src: "", gain: 0.5, loop: true, autoplay: true, @@ -210,6 +271,8 @@ LGAudioSource.prototype.onStop = function() LGAudioSource.prototype.onRemoved = function() { this.stopAllSounds(); + if(this._dropped_url) + URL.revokeObjectURL( this._url ); } LGAudioSource.prototype.stopAllSounds = function() @@ -233,7 +296,6 @@ LGAudioSource.prototype.onExecute = function() var input = this.inputs[i]; if(!input.link) continue; - var v = this.getInputData(i); if( v === undefined ) continue; @@ -309,36 +371,19 @@ LGAudioSource.prototype.loadSound = function( url ) if(!url) return; - //load new sample - var request = new XMLHttpRequest(); - request.open('GET', url, true); - request.responseType = 'arraybuffer'; + this._request = LGAudio.loadSound( url, inner ); + this._loading_audio = true; - this._request = request; + this.boxcolor = "#AA4"; - var context = LGAudio.getAudioContext(); - - // Decode asynchronously - request.onload = function() { - context.decodeAudioData( request.response, function(buffer) { - that._audio_buffer = buffer; - if(that._url) - { - URL.revokeObjectURL( that._url ); - that._url = null; - } - - that._loading_audio = false; - //if is playing, then play it - if(that.graph && that.graph.status === LGraph.STATUS_RUNNING) - that.onStart(); - }, onError); - } - request.send(); - - function onError(err) + function inner( buffer ) { - console.log("Audio loading sample error:",err); + this.boxcolor = LiteGraph.NODE_DEFAULT_BOXCOLOR; + that._audio_buffer = buffer; + that._loading_audio = false; + //if is playing, then play it + if(that.graph && that.graph.status === LGraph.STATUS_RUNNING) + that.onStart(); //this controls the autoplay already } } @@ -357,11 +402,12 @@ LGAudioSource.prototype.onGetOutputs = function() LGAudioSource.prototype.onDropFile = function(file) { - if(this._url) - URL.revokeObjectURL( this._url ); - this._url = URL.createObjectURL( file ); - this.properties.src = this._url; - this.loadSound( this._url ); + if(this._dropped_url) + URL.revokeObjectURL( this._dropped_url ); + var url = URL.createObjectURL( file ); + this.properties.src = url; + this.loadSound( url ); + this._dropped_url = url; } @@ -415,15 +461,24 @@ LGAudioAnalyser.prototype.onExecute = function() this.setOutputData(0,this._freq_bin); } + //send analyzer + if(this.isOutputConnected(1)) + this.setOutputData(1,this.audionode); + + //properties for(var i = 1; i < this.inputs.length; ++i) { var input = this.inputs[i]; + if(!input.link) + continue; var v = this.getInputData(i); if (v !== undefined) this.audionode[ input.name ].value = v; } + + //time domain //this.audionode.getFloatTimeDomainData( dataArray ); } @@ -433,33 +488,17 @@ LGAudioAnalyser.prototype.onGetInputs = function() return [["minDecibels","number"],["maxDecibels","number"],["smoothingTimeConstant","number"]]; } +/* +LGAudioAnalyser.prototype.onGetOutputs = function() +{ + return [["Analyzer","analyzer"]]; +} +*/ LGAudioAnalyser.title = "Analyser"; LGAudioAnalyser.desc = "Audio Analyser"; LiteGraph.registerNodeType( "audio/analyser", LGAudioAnalyser ); - - -//***************************************************** - -//this function helps creating wrappers to existing classes -function createAudioNodeWrapper( class_object ) -{ - class_object.prototype.onPropertyChanged = function(name, value) - { - if( this.audionode[ name ] === undefined ) - return; - - if( this.audionode[ name ].value !== undefined ) - this.audionode[ name ].value = value; - else - this.audionode[ name ] = value; - } - - class_object.prototype.onConnectionsChange = LGAudio.onConnectionsChange; -} - - //***************************************************** function LGAudioGain() @@ -489,14 +528,128 @@ LGAudioGain.prototype.onExecute = function() } } -createAudioNodeWrapper( LGAudioGain ); +LGAudio.createAudioNodeWrapper( LGAudioGain ); LGAudioGain.title = "Gain"; LGAudioGain.desc = "Audio gain"; LiteGraph.registerNodeType("audio/gain", LGAudioGain); +function LGAudioConvolver() +{ + //default + this.properties = { + impulse_src:"", + normalize: true + }; + this.audionode = LGAudio.getAudioContext().createConvolver(); + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +LGAudio.createAudioNodeWrapper( LGAudioConvolver ); + +LGAudioConvolver.prototype.onRemove = function() +{ + if(this._dropped_url) + URL.revokeObjectURL( this._dropped_url ); +} + +LGAudioConvolver.prototype.onPropertyChanged = function( name, value ) +{ + if( name == "impulse_src" ) + this.loadImpulse( value ); + else if( name == "normalize" ) + this.audionode.normalize = value; +} + +LGAudioConvolver.prototype.onDropFile = function(file) +{ + if(this._dropped_url) + URL.revokeObjectURL( this._dropped_url ); + this._dropped_url = URL.createObjectURL( file ); + this.properties.impulse_src = this._dropped_url; + this.loadImpulse( this._dropped_url ); +} + +LGAudioConvolver.prototype.loadImpulse = function( url ) +{ + var that = this; + + //kill previous load + if(this._request) + { + this._request.abort(); + this._request = null; + } + + this._impulse_buffer = null; + this._loading_impulse = false; + + if(!url) + return; + + //load new sample + this._request = LGAudio.loadSound( url, inner ); + this._loading_impulse = true; + + // Decode asynchronously + function inner( buffer ) { + that._impulse_buffer = buffer; + that.audionode.buffer = buffer; + console.log("Impulse signal set"); + that._loading_impulse = false; + } +} + +LGAudioConvolver.title = "Convolver"; +LGAudioConvolver.desc = "Convolves the signal (used for reverb)"; +LiteGraph.registerNodeType("audio/convolver", LGAudioConvolver); + + +function LGAudioDynamicsCompressor() +{ + //default + this.properties = { + threshold: -50, + knee: 40, + ratio: 12, + reduction: -20, + attack: 0, + release: 0.25 + }; + + this.audionode = LGAudio.getAudioContext().createDynamicsCompressor(); + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +LGAudio.createAudioNodeWrapper( LGAudioDynamicsCompressor ); + +LGAudioDynamicsCompressor.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + if(!input.link) + continue; + var v = this.getInputData(i); + if(v !== undefined) + this.audionode[ input.name ].value = v; + } +} + +LGAudioDynamicsCompressor.prototype.onGetInputs = function() +{ + return [["threshold","number"],["knee","number"],["ratio","number"],["reduction","number"],["attack","number"],["release","number"]]; +} + +LGAudioDynamicsCompressor.title = "DynamicsCompressor"; +LGAudioDynamicsCompressor.desc = "Dynamics Compressor"; +LiteGraph.registerNodeType("audio/dynamicsCompressor", LGAudioDynamicsCompressor); function LGAudioWaveShaper() @@ -526,7 +679,7 @@ LGAudioWaveShaper.prototype.setWaveShape = function(shape) this.audionode.curve = shape; } -createAudioNodeWrapper( LGAudioWaveShaper ); +LGAudio.createAudioNodeWrapper( LGAudioWaveShaper ); /* disabled till I dont find a way to do a wave shape LGAudioWaveShaper.title = "WaveShaper"; @@ -576,7 +729,8 @@ LGAudioMixer.prototype.onExecute = function() for(var i = 1; i < this.inputs.length; ++i) { var input = this.inputs[i]; - if(input.type == "audio") + + if(!input.link || input.type == "audio") continue; var v = this.getInputData(i); @@ -590,7 +744,7 @@ LGAudioMixer.prototype.onExecute = function() } } -createAudioNodeWrapper( LGAudioMixer ); +LGAudio.createAudioNodeWrapper( LGAudioMixer ); LGAudioMixer.title = "Mixer"; LGAudioMixer.desc = "Audio mixer"; @@ -611,7 +765,7 @@ function LGAudioDelay() this.addOutput("out","audio"); } -createAudioNodeWrapper( LGAudioDelay ); +LGAudio.createAudioNodeWrapper( LGAudioDelay ); LGAudioDelay.prototype.onExecute = function() { @@ -651,6 +805,8 @@ LGAudioBiquadFilter.prototype.onExecute = function() for(var i = 1; i < this.inputs.length; ++i) { var input = this.inputs[i]; + if(!input.link) + continue; var v = this.getInputData(i); if(v !== undefined) this.audionode[ input.name ].value = v; @@ -662,7 +818,7 @@ LGAudioBiquadFilter.prototype.onGetInputs = function() return [["frequency","number"],["detune","number"],["Q","number"]]; } -createAudioNodeWrapper( LGAudioBiquadFilter ); +LGAudio.createAudioNodeWrapper( LGAudioBiquadFilter ); LGAudioBiquadFilter.title = "BiquadFilter"; LGAudioBiquadFilter.desc = "Audio filter"; @@ -677,10 +833,12 @@ LiteGraph.registerNodeType("audio/biquadfilter", LGAudioBiquadFilter); function LGAudioVisualization() { this.properties = { - continuous: true + continuous: true, + mark: -1 }; this.addInput("freqs","array"); + this.addInput("mark","number"); this.size = [300,200]; this._last_buffer = null; } @@ -688,6 +846,10 @@ function LGAudioVisualization() LGAudioVisualization.prototype.onExecute = function() { this._last_buffer = this.getInputData(0); + var v = this.getInputData(1); + if(v !== undefined) + this.properties.mark = v; + this.setDirtyCanvas(true,false); } LGAudioVisualization.prototype.onDrawForeground = function(ctx) @@ -697,6 +859,7 @@ LGAudioVisualization.prototype.onDrawForeground = function(ctx) var buffer = this._last_buffer; + //delta represents how many samples we advance per pixel var delta = buffer.length / this.size[0]; var h = this.size[1]; @@ -719,12 +882,26 @@ LGAudioVisualization.prototype.onDrawForeground = function(ctx) { for(var i = 0; i < buffer.length; i+= delta) { - ctx.moveTo(x,h); - ctx.lineTo(x,h - (buffer[i|0]/255) * h); + ctx.moveTo(x+0.5,h); + ctx.lineTo(x+0.5,h - (buffer[i|0]/255) * h); x++; } } ctx.stroke(); + + if(this.properties.mark >= 0) + { + var samplerate = LGAudio.getAudioContext().sampleRate; + var binfreq = samplerate / buffer.length; + var x = 2 * (this.properties.mark / binfreq) / delta; + if(x >= this.size[0]) + x = this.size[0]-1; + ctx.strokeStyle = "red"; + ctx.beginPath(); + ctx.moveTo(x,h); + ctx.lineTo(x,0); + ctx.stroke(); + } } LGAudioVisualization.title = "Visualization"; @@ -732,6 +909,59 @@ LGAudioVisualization.desc = "Audio Visualization"; LiteGraph.registerNodeType("audio/visualization", LGAudioVisualization); +function LGAudioBandSignal() +{ + //default + this.properties = { + band: 440, + amplitude: 1 + }; + + this.addInput("freqs","array"); + this.addOutput("signal","number"); +} + +LGAudioBandSignal.prototype.onExecute = function() +{ + this._freqs = this.getInputData(0); + if( !this._freqs ) + return; + + var band = this.properties.band; + var v = this.getInputData(1); + if(v !== undefined) + band = v; + + var samplerate = LGAudio.getAudioContext().sampleRate; + var binfreq = samplerate / this._freqs.length; + var index = 2 * (band / binfreq); + var v = 0; + if( index < 0 ) + v = this._freqs[ 0 ]; + if( index >= this._freqs.length ) + v = this._freqs[ this._freqs.length - 1]; + else + { + var pos = index|0; + var v0 = this._freqs[ pos ]; + var v1 = this._freqs[ pos+1 ]; + var f = index - pos; + v = v0 * (1-f) + v1 * f; + } + + this.setOutputData( 0, (v/255) * this.properties.amplitude ); +} + +LGAudioBandSignal.prototype.onGetInputs = function() +{ + return [["band","number"]]; +} + +LGAudioBandSignal.title = "Signal"; +LGAudioBandSignal.desc = "extract the signal of some frequency"; +LiteGraph.registerNodeType("audio/signal", LGAudioBandSignal); + + function LGAudioDestination() { diff --git a/src/nodes/math.js b/src/nodes/math.js index 93b768d69..0ac8cdbda 100755 --- a/src/nodes/math.js +++ b/src/nodes/math.js @@ -216,6 +216,47 @@ MathClamp.prototype.getCode = function(lang) LiteGraph.registerNodeType("math/clamp", MathClamp ); + +//Math ABS +function MathLerp() +{ + this.properties = { f: 0.5 }; + this.addInput("A","number"); + this.addInput("B","number"); + + this.addOutput("out","number"); +} + +MathLerp.title = "Lerp"; +MathLerp.desc = "Linear Interpolation"; + +MathLerp.prototype.onExecute = function() +{ + var v1 = this.getInputData(0); + if(v1 == null) + v1 = 0; + var v2 = this.getInputData(1); + if(v2 == null) + v2 = 0; + + var f = this.properties.f; + + var _f = this.getInputData(2); + if(_f !== undefined) + f = _f; + + this.setOutputData(0, v1 * (1-f) + v2 * f ); +} + +MathLerp.prototype.onGetInputs = function() +{ + return [["f","number"]]; +} + +LiteGraph.registerNodeType("math/lerp", MathLerp); + + + //Math ABS function MathAbs() {