From dcf49304669f634e11e2e4571f8716b4358d06b9 Mon Sep 17 00:00:00 2001 From: tamat Date: Fri, 9 Sep 2016 20:24:39 +0200 Subject: [PATCH] fixes --- build/litegraph.js | 1737 ++++++++++++++++++++++++++++++++++----- build/litegraph.min.js | 330 ++++---- css/litegraph.css | 54 ++ demo/code.js | 1 + demo/demo.js | 12 +- demo/index.html | 2 + src/litegraph.js | 770 +++++++++++++---- src/nodes/base.js | 35 +- src/nodes/events.js | 51 ++ src/nodes/gltextures.js | 35 +- src/nodes/interface.js | 59 ++ src/nodes/math.js | 171 +++- src/nodes/midi.js | 614 ++++++++++++++ utils/deploy_files.txt | 2 + 14 files changed, 3328 insertions(+), 545 deletions(-) create mode 100644 src/nodes/events.js create mode 100644 src/nodes/midi.js diff --git a/build/litegraph.js b/build/litegraph.js index fafb3e5ef..0866faae7 100644 --- a/build/litegraph.js +++ b/build/litegraph.js @@ -29,6 +29,17 @@ var LiteGraph = { DEFAULT_POSITION: [100,100],//default node position node_images_path: "", + //enums + INPUT: 1, + OUTPUT: 2, + + EVENT: -1, //for outputs + ACTION: -1, //for inputs + + ALWAYS: 0, + ON_EVENT: 1, + NEVER: 2, + proxy: null, //used to redirect calls debug: false, @@ -67,6 +78,10 @@ var LiteGraph = { this.registered_node_types[ type ] = base_class; if(base_class.constructor.name) this.Nodes[ base_class.constructor.name ] = base_class; + + //warnings + if(base_class.prototype.onPropertyChange) + console.warn("LiteGraph node class " + type + " has onPropertyChange method, it must be called onPropertyChanged with d at the end"); }, /** @@ -90,7 +105,7 @@ var LiteGraph = { * @param {Object} options to set options */ - createNode: function(type, title, options) + createNode: function( type, title, options ) { var base_class = this.registered_node_types[type]; if (!base_class) @@ -109,9 +124,11 @@ var LiteGraph = { node.type = type; if(!node.title) node.title = title; if(!node.properties) node.properties = {}; + if(!node.properties_info) node.properties_info = []; if(!node.flags) node.flags = {}; if(!node.size) node.size = node.computeSize(); if(!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat(); + if(!node.mode) node.mode = LiteGraph.ALWAYS; //extra options if(options) @@ -228,6 +245,16 @@ var LiteGraph = { for(var i in r) target[i] = r[i]; return target; + }, + + isValidConnection: function( type_a, type_b ) + { + if( !type_a || //generic output + !type_b || //generic input + type_a == type_a || //same type (is valid for triggers) + (type_a !== LiteGraph.EVENT && type_b !== LiteGraph.EVENT && type_a.toLowerCase() == type_b.toLowerCase()) ) //same type + return true; + return false; } }; @@ -413,11 +440,22 @@ LGraph.prototype.runStep = function(num) var start = LiteGraph.getTime(); this.globaltime = 0.001 * (start - this.starttime); + var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; + if(!nodes) + return; + try { + //iterations for(var i = 0; i < num; i++) { - this.sendEventToAllNodes("onExecute"); + for( var j = 0, l = nodes.length; j < l; ++j ) + { + var node = nodes[j]; + if( node.mode == LiteGraph.ALWAYS && node.onExecute ) + node.onExecute(); + } + this.fixedtime += this.fixedtime_lapse; if( this.onExecuteStep ) this.onExecuteStep(); @@ -438,7 +476,8 @@ LGraph.prototype.runStep = function(num) } var elapsed = LiteGraph.getTime() - start; - if (elapsed == 0) elapsed = 1; + if (elapsed == 0) + elapsed = 1; this.elapsed_time = 0.001 * elapsed; this.globaltime += 0.001 * elapsed; this.iteration += 1; @@ -529,13 +568,13 @@ LGraph.prototype.computeExecutionOrder = function() //the remaining ones (loops) for(var i in M) - L.push(M[i]); + L.push( M[i] ); - if(L.length != this._nodes.length && LiteGraph.debug) - console.log("something went wrong, nodes missing"); + if( L.length != this._nodes.length && LiteGraph.debug ) + console.warn("something went wrong, nodes missing"); //save order number in the node - for(var i in L) + for(var i = 0; i < L.length; ++i) L[i].order = i; return L; @@ -583,8 +622,10 @@ LGraph.prototype.getElapsedTime = function() * @param {Array} params parameters in array format */ -LGraph.prototype.sendEventToAllNodes = function(eventname, params) +LGraph.prototype.sendEventToAllNodes = function( eventname, params, mode ) { + mode = mode || LiteGraph.ALWAYS; + var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; if(!nodes) return; @@ -592,12 +633,12 @@ LGraph.prototype.sendEventToAllNodes = function(eventname, params) for( var j = 0, l = nodes.length; j < l; ++j ) { var node = nodes[j]; - if(node[eventname]) + if(node[eventname] && node.mode == mode ) { if(params === undefined) node[eventname](); else if(params && params.constructor === Array) - node[eventname].apply(M[j], params); + node[eventname].apply( node, params ); else node[eventname](params); } @@ -647,7 +688,7 @@ LGraph.prototype.add = function(node, skip_compute_order) */ if(node.onAdded) - node.onAdded(); + node.onAdded( this ); if(this.config.align_to_grid) node.alignToGrid(); @@ -1088,8 +1129,11 @@ LGraph.prototype.serialize = function() //remove data from links, we dont want to store it for(var i in this.links) //links is an OBJECT - this.links[i].data = null; - + { + var link = this.links[i]; + link.data = null; + delete link._last_time; + } var data = { // graph: this.graph, @@ -1185,7 +1229,7 @@ LGraph.prototype.onNodeTrace = function(node, msg, color) + onMouseEnter + onMouseLeave + onExecute: execute the node - + onPropertyChange: when a property is changed in the panel (return true to skip default behaviour) + + onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour) + onGetInputs: returns an array of possible inputs + onGetOutputs: returns an array of possible outputs + onDblClick @@ -1195,6 +1239,7 @@ LGraph.prototype.onNodeTrace = function(node, msg, color) + onDropItem : DOM item dropped over the node + onDropFile : file dropped over the node + onConnectInput : if returns false the incoming connection will be canceled + + onConnectionsChange : a connection changed (new one or removed) */ /** @@ -1240,7 +1285,9 @@ LGraphNode.prototype._ctor = function( title ) this.connections = []; //local data - this.properties = {}; + this.properties = {}; //for the values + this.properties_info = []; //for the info + this.data = null; //persistent local data this.flags = { //skip_title_render: true, @@ -1256,13 +1303,18 @@ LGraphNode.prototype.configure = function(info) { for (var j in info) { - if(j == "console") continue; + if(j == "console") + continue; if(j == "properties") { //i dont want to clone properties, I want to reuse the old container for(var k in info.properties) + { this.properties[k] = info.properties[k]; + if(this.onPropertyChanged) + this.onPropertyChanged(k,info.properties[k]); + } continue; } @@ -1279,6 +1331,9 @@ LGraphNode.prototype.configure = function(info) this[j] = info[j]; } + if(this.onConnectionsChange) + this.onConnectionsChange(); + //FOR LEGACY, PLEASE REMOVE ON NEXT VERSION for(var i in this.inputs) { @@ -1323,7 +1378,8 @@ LGraphNode.prototype.serialize = function() data: this.data, flags: LiteGraph.cloneObject(this.flags), inputs: this.inputs, - outputs: this.outputs + outputs: this.outputs, + mode: this.mode }; if(this.properties) @@ -1353,8 +1409,23 @@ LGraphNode.prototype.clone = function() { var node = LiteGraph.createNode(this.type); - var data = this.serialize(); + //we clone it because serialize returns shared containers + var data = LiteGraph.cloneObject( this.serialize() ); + + //remove links + if(data.inputs) + for(var i = 0; i < data.inputs.length; ++i) + data.inputs[i].link = null; + + if(data.outputs) + for(var i = 0; i < data.outputs.length; ++i) + { + if(data.outputs[i].links) + data.outputs[i].links.length = 0; + } + delete data["id"]; + //remove links node.configure(data); return node; @@ -1413,13 +1484,30 @@ LGraphNode.prototype.setOutputData = function(slot,data) * @param {number} slot * @return {*} data or if it is not connected returns undefined */ -LGraphNode.prototype.getInputData = function(slot) +LGraphNode.prototype.getInputData = function( slot, force_update ) { if(!this.inputs) return; //undefined; - if(slot < this.inputs.length && this.inputs[slot].link != null) - return this.graph.links[ this.inputs[slot].link ].data; - return; //undefined; + + if(slot >= this.inputs.length || this.inputs[slot].link == null) + return; + + var link_id = this.inputs[slot].link; + var link = this.graph.links[ link_id ]; + + if(!force_update) + return link.data; + + var node = this.graph.getNodeById( link.origin_id ); + if(!node) + return link.data; + + if(node.updateOutputData) + node.updateOutputData( link.origin_slot ); + else if(node.onExecute) + node.onExecute(); + + return link.data; } /** @@ -1500,13 +1588,80 @@ LGraphNode.prototype.getOutputNodes = function(slot) return null; } -LGraphNode.prototype.triggerOutput = function(slot,param) +/** +* Triggers an event in this node, this will trigger any output with the same name +* @method trigger +* @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all +* @param {*} param +*/ +LGraphNode.prototype.trigger = function( action, param ) { - var n = this.getOutputNode(slot); - if(n && n.onTrigger) - n.onTrigger(param); + if( !this.outputs || !this.outputs.length ) + return; + + if(this.graph) + this.graph._last_trigger_time = LiteGraph.getTime(); + + for(var i = 0; i < this.outputs.length; ++i) + { + var output = this.outputs[i]; + if(output.type !== LiteGraph.EVENT || (action && output.name != action) ) + continue; + + var links = output.links; + if(!links || !links.length) + continue; + + //for every link attached here + for(var k = 0; k < links.length; ++k) + { + var link_info = this.graph.links[ links[k] ]; + if(!link_info) //not connected + continue; + var node = this.graph.getNodeById( link_info.target_id ); + if(!node) //node not found? + continue; + + //used to mark events in graph + link_info._last_time = LiteGraph.getTime(); + + var target_connection = node.inputs[ link_info.target_slot ]; + + if(node.onAction) + node.onAction( target_connection.name, param ); + else if(node.mode === LiteGraph.ON_TRIGGER) + { + if(node.onExecute) + node.onExecute(param); + } + } + } } +/** +* add a new property to this node +* @method addProperty +* @param {string} name +* @param {*} default_value +* @param {string} type string defining the output type ("vec3","number",...) +* @param {Object} extra_info this can be used to have special properties of the property (like values, etc) +*/ +LGraphNode.prototype.addProperty = function( name, default_value, type, extra_info ) +{ + var o = { name: name, type: type, default_value: default_value }; + if(extra_info) + for(var i in extra_info) + o[i] = extra_info[i]; + if(!this.properties_info) + this.properties_info = []; + this.properties_info.push(o); + if(!this.properties) + this.properties = {}; + this.properties[ name ] = default_value; + return o; +} + + //connections /** @@ -1518,16 +1673,18 @@ LGraphNode.prototype.triggerOutput = function(slot,param) */ LGraphNode.prototype.addOutput = function(name,type,extra_info) { - var o = {name:name,type:type,links:null}; + var o = { name: name, type: type, links: null }; if(extra_info) for(var i in extra_info) o[i] = extra_info[i]; - if(!this.outputs) this.outputs = []; + if(!this.outputs) + this.outputs = []; this.outputs.push(o); if(this.onOutputAdded) this.onOutputAdded(o); this.size = this.computeSize(); + return o; } /** @@ -1590,6 +1747,7 @@ LGraphNode.prototype.addInput = function(name,type,extra_info) this.size = this.computeSize(); if(this.onInputAdded) this.onInputAdded(o); + return o; } /** @@ -1641,7 +1799,15 @@ LGraphNode.prototype.removeInput = function(slot) */ LGraphNode.prototype.addConnection = function(name,type,pos,direction) { - this.connections.push( {name:name,type:type,pos:pos,direction:direction,links:null}); + var o = { + name: name, + type: type, + pos: pos, + direction: direction, + links: null + }; + this.connections.push( o ); + return o; } /** @@ -1650,10 +1816,10 @@ LGraphNode.prototype.addConnection = function(name,type,pos,direction) * @param {number} minHeight * @return {number} the total size */ -LGraphNode.prototype.computeSize = function(minHeight) +LGraphNode.prototype.computeSize = function( minHeight, out ) { var rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1); - var size = new Float32Array([0,0]); + var size = out || new Float32Array([0,0]); rows = Math.max(rows, 1); size[1] = rows * 14 + 6; @@ -1795,10 +1961,10 @@ LGraphNode.prototype.findOutputSlot = function(name) * @method connect * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) * @param {LGraphNode} node the target node -* @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot) +* @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) * @return {boolean} if it was connected succesfully */ -LGraphNode.prototype.connect = function(slot, node, target_slot) +LGraphNode.prototype.connect = function( slot, node, target_slot ) { target_slot = target_slot || 0; @@ -1830,6 +1996,7 @@ LGraphNode.prototype.connect = function(slot, node, target_slot) return false; //if( node.constructor != LGraphNode ) throw ("LGraphNode.connect: node is not of type LGraphNode"); + //you can specify the slot by name if(target_slot.constructor === String) { target_slot = node.findInputSlot(target_slot); @@ -1840,7 +2007,18 @@ LGraphNode.prototype.connect = function(slot, node, target_slot) return false; } } - else if(!node.inputs || target_slot >= node.inputs.length) + else if( target_slot === LiteGraph.EVENT ) + { + //search for first slot with event? + /* + //create input for trigger + var input = node.addInput("onTrigger", LiteGraph.EVENT ); + target_slot = node.inputs.length - 1; //last one is the one created + node.mode = LiteGraph.ON_TRIGGER; + */ + return false; + } + else if( !node.inputs || target_slot >= node.inputs.length ) { if(LiteGraph.debug) console.log("Connect: Error, slot number not found"); @@ -1848,41 +2026,46 @@ LGraphNode.prototype.connect = function(slot, node, target_slot) } //if there is something already plugged there, disconnect - if(target_slot != -1 && node.inputs[target_slot].link != null) - node.disconnectInput(target_slot); + if(node.inputs[ target_slot ].link != null ) + node.disconnectInput( target_slot ); //why here?? this.setDirtyCanvas(false,true); this.graph.connectionChange( this ); - //special case: -1 means node-connection, used for triggers var output = this.outputs[slot]; - //allows nodes to block connection even if all test passes + //allows nodes to block connection if(node.onConnectInput) if( node.onConnectInput( target_slot, output.type, output ) === false) return false; - if(target_slot == -1) + var input = node.inputs[target_slot]; + + if( LiteGraph.isValidConnection( output.type, input.type) ) { - if( output.links == null ) - output.links = []; - output.links.push({id:node.id, slot: -1}); - } - else if( !output.type || //generic output - !node.inputs[target_slot].type || //generic input - output.type.toLowerCase() == node.inputs[target_slot].type.toLowerCase() ) //same type - { - //info: link structure => [ 0:link_id, 1:start_node_id, 2:start_slot, 3:end_node_id, 4:end_slot ] - //var link = [ this.graph.last_link_id++, this.id, slot, node.id, target_slot ]; - var link = { id: this.graph.last_link_id++, origin_id: this.id, origin_slot: slot, target_id: node.id, target_slot: target_slot }; + var link = { + id: this.graph.last_link_id++, + origin_id: this.id, + origin_slot: slot, + target_id: node.id, + target_slot: target_slot + }; + + //add to graph links list this.graph.links[ link.id ] = link; - //connect - if( output.links == null ) output.links = []; + //connect in output + if( output.links == null ) + output.links = []; output.links.push( link.id ); + //connect in input node.inputs[target_slot].link = link.id; + if(this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT, slot ); + if(node.onConnectionsChange) + node.onConnectionsChange( LiteGraph.OUTPUT, target_slot ); } this.setDirtyCanvas(false,true); @@ -1994,6 +2177,7 @@ LGraphNode.prototype.disconnectInput = function(slot) var input = this.inputs[slot]; if(!input) return false; + var link_id = this.inputs[slot].link; this.inputs[slot].link = null; @@ -2020,6 +2204,11 @@ LGraphNode.prototype.disconnectInput = function(slot) break; } } + + if(this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT ); + if(node.onConnectionsChange) + node.onConnectionsChange( LiteGraph.INPUT); } this.setDirtyCanvas(false,true); @@ -2103,6 +2292,7 @@ LGraphNode.prototype.loadImage = function(url) } //safe LGraphNode action execution (not sure if safe) +/* LGraphNode.prototype.executeAction = function(action) { if(action == "") return false; @@ -2138,6 +2328,7 @@ LGraphNode.prototype.executeAction = function(action) return true; } +*/ /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ LGraphNode.prototype.captureInput = function(v) @@ -2220,6 +2411,30 @@ function LGraphCanvas( canvas, graph, options ) this.max_zoom = 10; this.min_zoom = 0.1; + this.title_text_font = "bold 14px Arial"; + this.inner_text_font = "normal 12px Arial"; + this.default_link_color = "#AAC"; + + this.highquality_render = true; + this.editor_alpha = 1; //used for transition + this.pause_rendering = false; + this.render_shadows = true; + this.clear_background = true; + + this.render_only_selected = true; + this.live_mode = false; + this.show_info = true; + this.allow_dragcanvas = true; + this.allow_dragnodes = true; + + this.always_render_background = false; + this.render_connections_shadows = false; //too much cpu + this.render_connections_border = true; + this.render_curved_connections = true; + this.render_connection_arrows = true; + + this.connections_width = 4; + //link canvas and graph if(graph) graph.attachCanvas(this); @@ -2233,7 +2448,7 @@ function LGraphCanvas( canvas, graph, options ) this.autoresize = options.autoresize; } -LGraphCanvas.link_type_colors = {'number':"#AAC",'node':"#DCA"}; +LGraphCanvas.link_type_colors = {"-1":"#F85",'number':"#AAC","node":"#DCA"}; /** @@ -2257,18 +2472,6 @@ LGraphCanvas.prototype.clear = function() this.node_capturing_input = null; this.connecting_node = null; - this.highquality_render = true; - this.editor_alpha = 1; //used for transition - this.pause_rendering = false; - this.render_shadows = true; - this.clear_background = true; - - this.render_only_selected = true; - this.live_mode = false; - this.show_info = true; - this.allow_dragcanvas = true; - this.allow_dragnodes = true; - this.dirty_canvas = true; this.dirty_bgcanvas = true; this.dirty_area = null; @@ -2278,17 +2481,8 @@ LGraphCanvas.prototype.clear = function() this.last_mouse = [0,0]; this.last_mouseclick = 0; - this.title_text_font = "bold 14px Arial"; - this.inner_text_font = "normal 12px Arial"; - - this.render_connections_shadows = false; //too much cpu - this.render_connections_border = true; - this.render_curved_connections = true; - this.render_connection_arrows = true; - - this.connections_width = 4; - - if(this.onClear) this.onClear(); + if(this.onClear) + this.onClear(); //this.UIinit(); } @@ -2619,6 +2813,8 @@ LGraphCanvas.prototype.setDirty = function(fgcanvas,bgcanvas) */ LGraphCanvas.prototype.getCanvasWindow = function() { + if(!this.canvas) + return window; var doc = this.canvas.ownerDocument; return doc.defaultView || doc.parentWindow; } @@ -2683,11 +2879,10 @@ LGraphCanvas.prototype.processMouseDown = function(e) var n = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); var skip_dragging = false; - LiteGraph.closeAllContextualMenus(); + LiteGraph.closeAllContextualMenus( ref_window ); if(e.which == 1) //left button mouse { - if(!e.shiftKey) //REFACTOR: integrate with function { //no node or another node selected @@ -2784,7 +2979,7 @@ LGraphCanvas.prototype.processMouseDown = function(e) //if do not capture mouse - if( n.onMouseDown && n.onMouseDown(e) ) + if( n.onMouseDown && n.onMouseDown(e, [e.canvasX - n.pos[0], e.canvasY - n.pos[1]] ) ) block_drag_node = true; else if(this.live_mode) { @@ -2907,19 +3102,27 @@ LGraphCanvas.prototype.processMouseMove = function(e) if(n.onMouseMove) n.onMouseMove(e); - //ontop of input + //on top of input if(this.connecting_node) { - var pos = this._highlight_input || [0,0]; - var slot = this.isOverNodeInput(n, e.canvasX, e.canvasY, pos); - if(slot != -1 && n.inputs[slot]) - { - var slot_type = n.inputs[slot].type; - if( !this.connecting_output.type || !slot_type || slot_type.toLowerCase() == this.connecting_output.type.toLowerCase() ) - this._highlight_input = pos; + var pos = this._highlight_input || [0,0]; //to store the output of isOverNodeInput + + if( this.isOverNodeBox( n, e.canvasX, e.canvasY ) ) + { + //mouse on top of the corner box, dont know what to do } else - this._highlight_input = null; + { + var slot = this.isOverNodeInput( n, e.canvasX, e.canvasY, pos ); + if(slot != -1 && n.inputs[slot]) + { + var slot_type = n.inputs[slot].type; + if( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) + this._highlight_input = pos; + } + else + this._highlight_input = null; + } } //Search for corner @@ -3011,15 +3214,14 @@ LGraphCanvas.prototype.processMouseUp = function(e) this.dirty_canvas = true; this.dirty_bgcanvas = true; - var node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes); + var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); //node below mouse if(node) { - - if(this.connecting_output.type == 'node') + if( this.connecting_output.type == LiteGraph.EVENT && this.isOverNodeBox( node, e.canvasX, e.canvasY ) ) { - this.connecting_node.connect(this.connecting_slot, node, -1); + this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT ); } else { @@ -3032,9 +3234,12 @@ LGraphCanvas.prototype.processMouseUp = function(e) else { //not on top of an input var input = node.getInputInfo(0); - //simple connect - if(input && !input.link && input.type == this.connecting_output.type) //toLowerCase missing - this.connecting_node.connect(this.connecting_slot, node, 0); + //auto connect + if(this.connecting_output.type == LiteGraph.EVENT) + this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT ); + else + if(input && !input.link && input.type == this.connecting_output.type) //toLowerCase missing + this.connecting_node.connect(this.connecting_slot, node, 0); } } } @@ -3067,9 +3272,9 @@ LGraphCanvas.prototype.processMouseUp = function(e) this.dragging_canvas = false; if( this.node_over && this.node_over.onMouseUp ) - this.node_over.onMouseUp(e); + this.node_over.onMouseUp(e, [e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1]] ); if( this.node_capturing_input && this.node_capturing_input.onMouseUp ) - this.node_capturing_input.onMouseUp(e); + this.node_capturing_input.onMouseUp(e, [e.canvasX - this.node_capturing_input.pos[0], e.canvasY - this.node_capturing_input.pos[1]] ); } } else if (e.which == 2) //middle button @@ -3127,7 +3332,15 @@ LGraphCanvas.prototype.processMouseWheel = function(e) return false; // prevent default } -LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos) +LGraphCanvas.prototype.isOverNodeBox = function( node, canvasx, canvasy ) +{ + var title_height = LiteGraph.NODE_TITLE_HEIGHT; + if( isInsideRectangle( canvasx, canvasy, node.pos[0] + 2, node.pos[1] + 2 - title_height, title_height - 4,title_height - 4) ) + return true; + return false; +} + +LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos ) { if(node.inputs) for(var i = 0, l = node.inputs.length; i < l; ++i) @@ -3136,7 +3349,11 @@ LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_p var link_pos = node.getConnectionPos(true,i); if( isInsideRectangle(canvasx, canvasy, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) { - if(slot_pos) { slot_pos[0] = link_pos[0]; slot_pos[1] = link_pos[1] }; + if(slot_pos) + { + slot_pos[0] = link_pos[0]; + slot_pos[1] = link_pos[1]; + } return i; } } @@ -3455,6 +3672,9 @@ LGraphCanvas.prototype.computeVisibleNodes = function() LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { + if(!this.canvas) + return; + //fps counting var now = LiteGraph.getTime(); this.render_time = (now - this.last_draw_time)*0.001; @@ -3467,7 +3687,7 @@ LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) this.visible_area = new Float32Array([start[0],start[1],end[0],end[1]]); } - if(this.dirty_bgcanvas || force_bgcanvas) + if(this.dirty_bgcanvas || force_bgcanvas || this.always_render_background || (this.graph && this.graph._last_trigger_time && (now - this.graph._last_trigger_time) < 1000) ) this.drawBackCanvas(); if(this.dirty_canvas || force_canvas) @@ -3559,11 +3779,22 @@ LGraphCanvas.prototype.drawFrontCanvas = function() if(this.connecting_pos != null) { ctx.lineWidth = this.connections_width; - var link_color = this.connecting_output.type == 'node' ? "#F85" : "#AFA"; + var link_color = null; + switch( this.connecting_output.type ) + { + case LiteGraph.EVENT: link_color = "#F85"; break; + default: + link_color = "#AFA"; + } this.renderLink(ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]], link_color ); ctx.beginPath(); - ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2); + + if( this.connecting_output.type === LiteGraph.EVENT ) + ctx.rect( (this.connecting_pos[0] - 6) + 0.5, (this.connecting_pos[1] - 5) + 0.5,14,10); + else + ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2); + /* if( this.connecting_output.round) ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2); @@ -3665,7 +3896,7 @@ LGraphCanvas.prototype.drawBackCanvas = function() } var pattern = null; - if(this._bg_img != this._pattern_img && this._bg_img.width > 0) + if(this._pattern == null && this._bg_img.width > 0) { pattern = ctx.createPattern( this._bg_img, 'repeat' ); this._pattern_img = this._bg_img; @@ -3826,6 +4057,8 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) var render_text = this.scale > 0.6; + var out_slot = this.connecting_output; + //render inputs and outputs if(!node.flags.collapsed) { @@ -3836,7 +4069,8 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) var slot = node.inputs[i]; ctx.globalAlpha = editor_alpha; - if (this.connecting_node != null && this.connecting_output.type && node.inputs[i].type && this.connecting_output.type.toLowerCase() != node.inputs[i].type.toLowerCase() ) + //change opacity of incompatible slots + if ( this.connecting_node && LiteGraph.isValidConnection( slot.type && out_slot.type ) ) ctx.globalAlpha = 0.4 * editor_alpha; ctx.fillStyle = slot.link != null ? "#7F7" : "#AAA"; @@ -3847,10 +4081,10 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) ctx.beginPath(); - if (1 || slot.round) + if (slot.type === LiteGraph.EVENT) + ctx.rect((pos[0] - 6) + 0.5, (pos[1] - 5) + 0.5,14,10); + else ctx.arc(pos[0],pos[1],4,0,Math.PI*2); - //else - // ctx.rect((pos[0] - 6) + 0.5, (pos[1] - 5) + 0.5,14,10); ctx.fill(); @@ -3887,10 +4121,10 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) ctx.beginPath(); //ctx.rect( node.size[0] - 14,i*14,10,10); - if (1 || slot.round) - ctx.arc(pos[0],pos[1],4,0,Math.PI*2); - //else - // ctx.rect((pos[0] - 6) + 0.5,(pos[1] - 5) + 0.5,14,10); + if (slot.type === LiteGraph.EVENT) + ctx.rect((pos[0] - 6) + 0.5,(pos[1] - 5) + 0.5,14,10); + else + ctx.arc( pos[0],pos[1],4,0, Math.PI*2 ); //trigger //if(slot.node_id != null && slot.slot == -1) @@ -3984,7 +4218,7 @@ LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolo if(node.onDrawBackground) node.onDrawBackground(ctx); - //title bg + //title bg (remember, it is rendered ABOVE the node if(!no_title) { ctx.fillStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; @@ -4005,7 +4239,7 @@ LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolo //ctx.stroke(); } - //box + //title box ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; ctx.beginPath(); if (shape == "round") @@ -4080,10 +4314,11 @@ LGraphCanvas.prototype.drawNodeCollapsed = function(node, ctx, fgcolor, bgcolor) } } -LGraphCanvas.link_colors = ["#AAC","#ACA","#CAA"]; - +//OPTIMIZE THIS: precatch connections position instead of recomputing them every time LGraphCanvas.prototype.drawConnections = function(ctx) { + var now = LiteGraph.getTime(); + //draw connections ctx.lineWidth = this.connections_width; @@ -4103,7 +4338,8 @@ LGraphCanvas.prototype.drawConnections = function(ctx) continue; var link_id = input.link; var link = this.graph.links[ link_id ]; - if(!link) continue; + if(!link) + continue; var start_node = this.graph.getNodeById( link.origin_id ); if(start_node == null) continue; @@ -4115,16 +4351,22 @@ LGraphCanvas.prototype.drawConnections = function(ctx) else start_node_slotpos = start_node.getConnectionPos(false, start_node_slot); - var color = LGraphCanvas.link_type_colors[node.inputs[i].type]; - if(color == null) - color = LGraphCanvas.link_colors[node.id % LGraphCanvas.link_colors.length]; + var color = LGraphCanvas.link_type_colors[ node.inputs[i].type ] || this.default_link_color; + this.renderLink(ctx, start_node_slotpos, node.getConnectionPos(true,i), color ); + + if(link && link._last_time && now - link._last_time < 1000 ) + { + var f = 2.0 - (now - link._last_time) * 0.002; + var color = "rgba(255,255,255, " + f.toFixed(2) + ")"; + this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i) , color, true, f ); + } } } ctx.globalAlpha = 1; } -LGraphCanvas.prototype.renderLink = function(ctx,a,b,color) +LGraphCanvas.prototype.renderLink = function(ctx,a,b,color, skip_border, flow ) { if(!this.highquality_render) { @@ -4157,7 +4399,7 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color) ctx.lineTo(b[0]-10,b[1]); } - if(this.render_connections_border && this.scale > 0.6) + if(this.render_connections_border && this.scale > 0.6 && !skip_border) { ctx.strokeStyle = "rgba(0,0,0,0.5)"; ctx.stroke(); @@ -4167,12 +4409,17 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color) ctx.fillStyle = ctx.strokeStyle = color; ctx.stroke(); + //no symbols + if(!this.render_connection_arrows || this.scale < 0.6) + return; + //render arrow if(this.render_connection_arrows && this.scale > 0.6) { - //get two points in the bezier curve var pos = this.computeConnectionPoint(a,b,0.5); var pos2 = this.computeConnectionPoint(a,b,0.51); + + //get two points in the bezier curve var angle = 0; if(this.render_curved_connections) angle = -Math.atan2( pos2[0] - pos[0], pos2[1] - pos[1]); @@ -4189,6 +4436,18 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color) ctx.fill(); ctx.restore(); } + + if(flow) + { + for(var i = 0; i < 5; ++i) + { + var f = (LiteGraph.getTime() * 0.001 + (i * 0.2)) % 1; + var pos = this.computeConnectionPoint(a,b,f); + ctx.beginPath(); + ctx.arc(pos[0],pos[1],5,0,2*Math.PI); + ctx.fill(); + } + } } LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t) @@ -4320,7 +4579,7 @@ LGraphCanvas.prototype.touchHandler = function(event) LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) { - var window = canvas.getCanvasWindow(); + var ref_window = canvas.getCanvasWindow(); var values = LiteGraph.getNodeTypesCategories(); var entries = {}; @@ -4328,7 +4587,7 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) if(values[i]) entries[ i ] = { value: values[i], content: values[i] , is_menu: true }; - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, window); + var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); function inner_clicked(v, e) { @@ -4338,7 +4597,7 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) for(var i in node_types) values.push( { content: node_types[i].title, value: node_types[i].type }); - LiteGraph.createContextualMenu(values, {event: e, callback: inner_create, from: menu}, window); + LiteGraph.createContextualMenu(values, {event: e, callback: inner_create, from: menu}, ref_window); return false; } @@ -4366,16 +4625,20 @@ LGraphCanvas.onMenuNodeEdit = function() } -LGraphCanvas.onMenuNodeInputs = function(node, e, prev_menu) +LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) { - if(!node) return; + if(!node) + return; + + var that = this; + var ref_window = this.getCanvasWindow(); var options = node.optional_inputs; if(node.onGetInputs) options = node.onGetInputs(); + + var entries = []; if(options) - { - var entries = []; for (var i in options) { var entry = options[i]; @@ -4384,50 +4647,86 @@ LGraphCanvas.onMenuNodeInputs = function(node, e, prev_menu) label = entry[2].label; entries.push({content: label, value: entry}); } - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); - } - function inner_clicked(v) + if(this.onMenuNodeInputs) + entries = this.onMenuNodeInputs( entries ); + + if(!entries.length) + return; + + var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + + function inner_clicked(v, e, prev) { - if(!node) return; - node.addInput(v.value[0],v.value[1], v.value[2]); + if(!node) + return; + + if(v.callback) + v.callback.call(that, node, v, e, prev); + + if(v.value) + node.addInput(v.value[0],v.value[1], v.value[2]); } return false; } -LGraphCanvas.onMenuNodeOutputs = function(node, e, prev_menu) +LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) { - if(!node) return; + if(!node) + return; + + var that = this; + var ref_window = this.getCanvasWindow(); var options = node.optional_outputs; if(node.onGetOutputs) options = node.onGetOutputs(); + + var entries = []; if(options) - { - var entries = []; for (var i in options) { var entry = options[i]; + if(!entry) //separator? + { + entries.push(null); + continue; + } + if(node.findOutputSlot(entry[0]) != -1) continue; //skip the ones already on var label = entry[0]; if(entry[2] && entry[2].label) label = entry[2].label; - entries.push({content: label, value: entry}); + var data = {content: label, value: entry}; + if(entry[1] == LiteGraph.EVENT) + data.className = "event"; + entries.push(data); } - if(entries.length) - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); - } - function inner_clicked(v) + if(this.onMenuNodeOutputs) + entries = this.onMenuNodeOutputs( entries ); + + if(!entries.length) + return; + + var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + + function inner_clicked( v, e, prev ) { if(!node) return; + if(v.callback) + v.callback.call(that, node, v, e, prev); + + if(!v.value) + return; + var value = v.value[1]; - if(value && (value.constructor === Object || value.constructor === Array)) //submenu + if(value && (value.constructor === Object || value.constructor === Array)) //submenu why? { var entries = []; for(var i in value) @@ -4442,6 +4741,137 @@ LGraphCanvas.onMenuNodeOutputs = function(node, e, prev_menu) return false; } +LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) +{ + if(!node || !node.properties) + return; + + var that = this; + var ref_window = this.getCanvasWindow(); + + var entries = []; + for (var i in node.properties) + { + var value = node.properties[i] !== undefined ? node.properties[i] : " "; + entries.push({content: "" + i + "" + "" + value + "", value: i}); + } + if(!entries.length) + return; + + var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu},ref_window); + + function inner_clicked( v, e, prev ) + { + if(!node) + return; + that.showEditPropertyValue( node, v.value, { event: e }); + } + + return false; +} + +LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) +{ + if(!node || node.properties[ property ] === undefined ) + return; + + options = options || {}; + var that = this; + + var type = "string"; + + if(node.properties[ property ] !== null) + type = typeof(node.properties[ property ]); + + var info = null; + if(node.getPropertyInfo) + info = node.getPropertyInfo(property); + if(info.type) + type = info.type; + + var input_html = ""; + + if(type == "string" || type == "number") + input_html = ""; + else if(type == "enum" && info.values) + { + input_html = ""; + } + + + var dialog = document.createElement("div"); + dialog.className = "graphdialog"; + dialog.innerHTML = "" + property + ""+input_html+""; + + if(type == "enum" && info.values) + { + var input = dialog.querySelector("select"); + input.addEventListener("change", function(e){ + var index = e.target.value; + setValue( e.options[e.selectedIndex].value ); + }); + } + else + { + var input = dialog.querySelector("input"); + input.value = node.properties[ property ] !== undefined ? node.properties[ property ] : ""; + 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( options.event ) + { + dialog.style.left = (options.event.pageX + offsetx) + "px"; + dialog.style.top = (options.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) + { + if(typeof( node.properties[ property ] ) == "number") + node.properties[ property ] = Number(value); + else + node.properties[ property ] = value; + dialog.parentNode.removeChild( dialog ); + node.setDirtyCanvas(true,true); + } +} + LGraphCanvas.onMenuNodeCollapse = function(node) { node.flags.collapsed = !node.flags.collapsed; @@ -4453,6 +4883,27 @@ LGraphCanvas.onMenuNodePin = function(node) node.pin(); } +LGraphCanvas.onMenuNodeMode = function(node, e, prev_menu) +{ + LiteGraph.createContextualMenu(["Always","On Event","Never"], {event: e, callback: inner_clicked, from: prev_menu}); + + function inner_clicked(v) + { + if(!node) + return; + switch(v) + { + case "On Event": node.mode = LiteGraph.ON_EVENT; break; + case "Never": node.mode = LiteGraph.NEVER; break; + case "Always": + default: + node.mode = LiteGraph.ALWAYS; break; + } + } + + return false; +} + LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) { var values = []; @@ -4535,12 +4986,9 @@ LGraphCanvas.prototype.getCanvasMenuOptions = function() if(this.getExtraMenuOptions) { - var extra = this.getExtraMenuOptions(this); + var extra = this.getExtraMenuOptions(this,options); if(extra) - { - extra.push(null); - options = extra.concat( options ); - } + options = options.concat( extra ); } return options; @@ -4554,9 +5002,12 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node) options = node.getMenuOptions(this); else options = [ - {content:"Inputs", is_menu: true, disabled:true, callback: LGraphCanvas.onMenuNodeInputs }, - {content:"Outputs", is_menu: true, disabled:true, callback: LGraphCanvas.onMenuNodeOutputs }, + {content:"Inputs", is_menu: true, disabled:true, callback: LGraphCanvas.showMenuNodeInputs }, + {content:"Outputs", is_menu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOutputs }, null, + {content:"Properties", is_menu: true, callback: LGraphCanvas.onShowMenuNodeProperties }, + null, + {content:"Mode", is_menu: true, callback: LGraphCanvas.onMenuNodeMode }, {content:"Collapse", callback: LGraphCanvas.onMenuNodeCollapse }, {content:"Pin", callback: LGraphCanvas.onMenuNodePin }, {content:"Colors", is_menu: true, callback: LGraphCanvas.onMenuNodeColors }, @@ -4613,6 +5064,8 @@ LGraphCanvas.prototype.processContextualMenu = function(node, event) { menu_info = slot.locked ? [ "Cannot remove" ] : { "Remove Slot": slot }; options.title = slot.input ? slot.input.type : slot.output.type; + if(slot.input && slot.input.type == LiteGraph.EVENT) + options.title = "Event"; } else menu_info = node ? this.getNodeMenuOptions(node) : this.getCanvasMenuOptions(); @@ -4639,7 +5092,7 @@ LGraphCanvas.prototype.processContextualMenu = function(node, event) } if(v.callback) - return v.callback(node, e, menu, that, event ); + return v.callback.call(that, node, e, menu, that, event ); } } @@ -4777,7 +5230,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) ref_window = ref_window || window; if (!options.from) - LiteGraph.closeAllContextualMenus(); + LiteGraph.closeAllContextualMenus( ref_window ); else { //closing submenus var menus = document.querySelectorAll(".graphcontextualmenu"); @@ -4823,7 +5276,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) if(item == null) { - element.className = "graphmenu-entry separator"; + element.className += " separator"; root.appendChild(element); continue; } @@ -4834,6 +5287,9 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) if(item.disabled) element.className += " disabled"; + if(item.className) + element.className += " " + item.className; + element.style.cursor = "pointer"; element.dataset["value"] = typeof(item) == "string" ? item : item.value; element.data = item; @@ -4912,7 +5368,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) } if(close) - LiteGraph.closeAllContextualMenus(); + LiteGraph.closeAllContextualMenus( ref_window ); //root.closeMenu(); } @@ -4931,9 +5387,11 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) return root; } -LiteGraph.closeAllContextualMenus = function() +LiteGraph.closeAllContextualMenus = function( ref_window ) { - var elements = document.querySelectorAll(".graphcontextualmenu"); + ref_window = ref_window || window; + + var elements = ref_window.document.querySelectorAll(".graphcontextualmenu"); if(!elements.length) return; var result = []; @@ -5146,11 +5604,11 @@ function GlobalInput() this.addOutput(input_name, null ); - this.properties = {name: input_name, type: null }; + this.properties = { name: input_name, type: null }; var that = this; - Object.defineProperty(this.properties, "name", { + Object.defineProperty( this.properties, "name", { get: function() { return input_name; }, @@ -5169,7 +5627,7 @@ function GlobalInput() enumerable: true }); - Object.defineProperty(this.properties, "type", { + Object.defineProperty( this.properties, "type", { get: function() { return that.outputs[0].type; }, set: function(v) { that.outputs[0].type = v; @@ -5268,7 +5726,7 @@ LiteGraph.registerNodeType("graph/output", GlobalOutput); function Constant() { this.addOutput("value","number"); - this.properties = { value:1.0 }; + this.addProperty( "value", 1.0 ); this.editable = { property:"value", type:"number" }; } @@ -5309,7 +5767,7 @@ function Watch() this.size = [60,20]; this.addInput("value",0,{label:""}); this.addOutput("value",0,{label:""}); - this.properties = { value:"" }; + this.addProperty( "value", "" ); } Watch.title = "Watch"; @@ -5344,25 +5802,156 @@ LiteGraph.registerNodeType("basic/watch", Watch); //Show value inside the debug console function Console() { + this.mode = LiteGraph.ON_EVENT; this.size = [60,20]; - this.addInput("data",0); + this.addProperty( "msg", "" ); + this.addInput("log", LiteGraph.EVENT); + this.addInput("msg",0); } Console.title = "Console"; Console.desc = "Show value inside the console"; +Console.prototype.onAction = function(action, param) +{ + if(action == "log") + console.log( param ); + else if(action == "warn") + console.warn( param ); + else if(action == "error") + console.error( param ); +} + Console.prototype.onExecute = function() { - console.log( this.getInputData(0) ); + var msg = this.getInputData(0); + if(msg !== null) + this.properties.msg = msg; + console.log(msg); +} + +Console.prototype.onGetInputs = function() +{ + return [["log",LiteGraph.ACTION],["warn",LiteGraph.ACTION],["error",LiteGraph.ACTION]]; } LiteGraph.registerNodeType("basic/console", Console ); +})(); +//event related nodes +(function(){ + +//Show value inside the debug console +function DelayEvent() +{ + this.size = [60,20]; + this.addProperty( "time", 1000 ); + this.addInput("event", LiteGraph.ACTION); + this.addOutput("on_time", LiteGraph.EVENT); + + this._pending = []; +} + +DelayEvent.title = "Delay"; +DelayEvent.desc = "Delays one event"; + +DelayEvent.prototype.onAction = function(action, param) +{ + this._pending.push([ this.properties.time, param ]); +} + +DelayEvent.prototype.onExecute = function() +{ + var dt = this.graph.elapsed_time * 1000; //in ms + + for(var i = 0; i < this._pending.length; ++i) + { + var action = this._pending[i]; + action[0] -= dt; + if( action[0] > 0 ) + continue; + + //remove + this._pending.splice(i,1); + --i; + + //trigger + this.trigger(null, action[1]); + } +} + +DelayEvent.prototype.onGetInputs = function() +{ + return [["event",LiteGraph.ACTION]]; +} + +LiteGraph.registerNodeType("events/delay", DelayEvent ); + + })(); //widgets (function(){ + /* Button ****************/ + + function WidgetButton() + { + this.addOutput( "clicked", LiteGraph.EVENT ); + this.addProperty( "text","" ); + this.addProperty( "font","40px Arial" ); + this.addProperty( "message", "" ); + this.size = [64,84]; + } + + WidgetButton.title = "Button"; + WidgetButton.desc = "Triggers an event"; + + WidgetButton.prototype.onDrawForeground = function(ctx) + { + if(this.flags.collapsed) + return; + + //ctx.font = "40px Arial"; + //ctx.textAlign = "center"; + ctx.fillStyle = "black"; + ctx.fillRect(1,1,this.size[0] - 3, this.size[1] - 3); + ctx.fillStyle = "#AAF"; + ctx.fillRect(0,0,this.size[0] - 3, this.size[1] - 3); + ctx.fillStyle = this.clicked ? "white" : (this.mouseOver ? "#668" : "#334"); + ctx.fillRect(1,1,this.size[0] - 4, this.size[1] - 4); + + if( this.properties.text || this.properties.text === 0 ) + { + ctx.textAlign = "center"; + ctx.fillStyle = this.clicked ? "black" : "white"; + if( this.properties.font ) + ctx.font = this.properties.font; + ctx.fillText(this.properties.text, this.size[0] * 0.5, this.size[1] * 0.85 ); + ctx.textAlign = "left"; + } + } + + WidgetButton.prototype.onMouseDown = function(e, local_pos) + { + if(local_pos[0] > 1 && local_pos[1] > 1 && local_pos[0] < (this.size[0] - 2) && local_pos[1] < (this.size[1] - 2) ) + { + this.clicked = true; + this.trigger( "clicked", this.properties.message ); + return true; + } + } + + WidgetButton.prototype.onMouseUp = function(e) + { + this.clicked = false; + } + + + LiteGraph.registerNodeType("widget/button", WidgetButton ); + + /* Knob ****************/ + function WidgetKnob() { this.addOutput("",'number'); @@ -6324,7 +6913,12 @@ function MathRange() { this.addInput("in","number",{locked:true}); this.addOutput("out","number",{locked:true}); - this.properties = { "in": 0, in_min:0, in_max:1, out_min: 0, out_max: 1 }; + + this.addProperty( "in", 0 ); + this.addProperty( "in_min", 0 ); + this.addProperty( "in_max", 1 ); + this.addProperty( "out_min", 0 ); + this.addProperty( "out_max", 1 ); } MathRange.title = "Range"; @@ -6375,7 +6969,8 @@ LiteGraph.registerNodeType("math/range", MathRange); function MathRand() { this.addOutput("value","number"); - this.properties = { min:0, max:1 }; + this.addProperty( "min", 0 ); + this.addProperty( "max", 1 ); this.size = [60,20]; } @@ -6421,7 +7016,8 @@ function MathClamp() this.addInput("in","number"); this.addOutput("out","number"); this.size = [60,20]; - this.properties = {min:0, max:1}; + this.addProperty( "min", 0 ); + this.addProperty( "max", 1 ); } MathClamp.title = "Clamp"; @@ -6549,7 +7145,7 @@ function MathScale() this.addInput("in","number",{label:""}); this.addOutput("out","number",{label:""}); this.size = [60,20]; - this.properties = {"factor":1}; + this.addProperty( "factor", 1 ); } MathScale.title = "Scale"; @@ -6565,18 +7161,73 @@ MathScale.prototype.onExecute = function() LiteGraph.registerNodeType("math/scale", MathScale ); +//Math clamp +function MathAverageFilter() +{ + this.addInput("in","number"); + this.addOutput("out","number"); + this.size = [60,20]; + this.addProperty( "samples", 10 ); + this._values = new Float32Array(10); + this._current = 0; +} + +MathAverageFilter.title = "Average"; +MathAverageFilter.desc = "Average Filter"; + +MathAverageFilter.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) + v = 0; + + var num_samples = this._values.length; + + this._values[ this._current % num_samples ] = v; + this._current += 1; + if(this._current > num_samples) + this._current = 0; + + var avr = 0; + for(var i = 0; i < num_samples; ++i) + avr += this._values[i]; + + this.setOutputData( 0, avr / num_samples ); +} + +MathAverageFilter.prototype.onPropertyChanged = function( name, value ) +{ + if(value < 1) + value = 1; + this.properties.samples = Math.round(value); + var old = this._values; + + this._values = new Float32Array( this.properties.samples ); + if(old.length <= this._values.length ) + this._values.set(old); + else + this._values.set( old.subarray( 0, this._values.length ) ); +} + +LiteGraph.registerNodeType("math/average", MathAverageFilter ); + + //Math operation function MathOperation() { this.addInput("A","number"); this.addInput("B","number"); this.addOutput("=","number"); - this.properties = {A:1.0, B:1.0, OP:"+"}; + this.addProperty( "A", 1 ); + this.addProperty( "B", 1 ); + this.addProperty( "OP", "+", "string", { values: MathOperation.values } ); } +MathOperation.values = ["+","-","*","/","%","^"]; + MathOperation.title = "Operation"; MathOperation.desc = "Easy math operators"; -MathOperation["@OP"] = { type:"enum", title: "operation", values:["+","-","*","/","%","^"]}; +MathOperation["@OP"] = { type:"enum", title: "operation", values: MathOperation.values }; MathOperation.prototype.setValue = function(v) @@ -6604,16 +7255,28 @@ MathOperation.prototype.onExecute = function() { case '+': result = A+B; break; case '-': result = A-B; break; + case 'x': + case 'X': + case '*': result = A*B; break; case '/': result = A/B; break; case '%': result = A%B; break; case '^': result = Math.pow(A,B); break; + default: + console.warn("Unknown operation: " + this.properties.OP); } this.setOutputData(0, result ); } MathOperation.prototype.onDrawBackground = function(ctx) { - this.outputs[0].label = "A" + this.properties.OP + "B"; + if(this.flags.collapsed) + return; + + ctx.font = "40px Arial"; + ctx.fillStyle = "black"; + ctx.textAlign = "center"; + ctx.fillText(this.properties.OP, this.size[0] * 0.5, this.size[1] * 0.5 + LiteGraph.NODE_TITLE_HEIGHT ); + ctx.textAlign = "left"; } LiteGraph.registerNodeType("math/operation", MathOperation ); @@ -6626,7 +7289,8 @@ function MathCompare() this.addInput( "B","number" ); this.addOutput("A==B","boolean"); this.addOutput("A!=B","boolean"); - this.properties = {A:0,B:0}; + this.addProperty( "A", 0 ); + this.addProperty( "B", 0 ); } MathCompare.title = "Compare"; @@ -6676,11 +7340,15 @@ function MathCondition() this.addInput("A","number"); this.addInput("B","number"); this.addOutput("out","boolean"); - this.properties = { A:0, B:1, OP:">" }; + this.addProperty( "A", 1 ); + this.addProperty( "B", 1 ); + this.addProperty( "OP", ">", "string", { values: MathCondition.values } ); + this.size = [60,40]; } -MathCondition["@OP"] = { type:"enum", title: "operation", values:[">","<","==","!=","<=",">="]}; +MathCondition.values = [">","<","==","!=","<=",">="]; +MathCondition["@OP"] = { type:"enum", title: "operation", values: MathCondition.values }; MathCondition.title = "Condition"; MathCondition.desc = "evaluates condition between A and B"; @@ -6720,7 +7388,8 @@ function MathAccumulate() { this.addInput("inc","number"); this.addOutput("total","number"); - this.properties = { increment: 0, value: 0 }; + this.addProperty( "increment", 1 ); + this.addProperty( "value", 0 ); } MathAccumulate.title = "Accumulate"; @@ -6728,6 +7397,9 @@ MathAccumulate.desc = "Increments a value every time"; MathAccumulate.prototype.onExecute = function() { + if(this.properties.value === null) + this.properties.value = 0; + var inc = this.getInputData(0); if(inc !== null) this.properties.value += inc; @@ -6743,7 +7415,9 @@ function MathTrigonometry() { this.addInput("v","number"); this.addOutput("sin","number"); - this.properties = {amplitude:1.0, offset: 0}; + + this.addProperty( "amplitude", 1 ); + this.addProperty( "offset", 0 ); this.bgImageUrl = "nodes/imgs/icon-sin.png"; } @@ -6754,6 +7428,8 @@ MathTrigonometry.filter = "shader"; MathTrigonometry.prototype.onExecute = function() { var v = this.getInputData(0); + if(v == null) + v = 0; var amplitude = this.properties["amplitude"]; var slot = this.findInputSlot("amplitude"); if(slot != -1) @@ -7017,12 +7693,35 @@ LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4 ); if(window.glMatrix) { + function Math3DQuaternion() + { + this.addOutput("quat","quat"); + this.properties = { x:0, y:0, z:0, w: 1 }; + this._value = quat.create(); + } + + Math3DQuaternion.title = "Quaternion"; + Math3DQuaternion.desc = "quaternion"; + + Math3DQuaternion.prototype.onExecute = function() + { + this._value[0] = this.properties.x; + this._value[1] = this.properties.y; + this._value[2] = this.properties.z; + this._value[3] = this.properties.w; + this.setOutputData( 0, this._value ); + } + + LiteGraph.registerNodeType("math3d/quaternion", Math3DQuaternion ); + function Math3DRotation() { this.addInputs([["degrees","number"],["axis","vec3"]]); this.addOutput("quat","quat"); this.properties = { angle:90.0, axis: vec3.fromValues(0,1,0) }; + + this._value = quat.create(); } Math3DRotation.title = "Rotation"; @@ -7035,7 +7734,7 @@ if(window.glMatrix) var axis = this.getInputData(1); if(axis == null) axis = this.properties.axis; - var R = quat.setAxisAngle(quat.create(), axis, angle * 0.0174532925 ); + var R = quat.setAxisAngle( this._value, axis, angle * 0.0174532925 ); this.setOutputData( 0, R ); } @@ -7073,6 +7772,8 @@ if(window.glMatrix) { this.addInputs( [["A","quat"],["B","quat"]] ); this.addOutput( "A*B","quat" ); + + this._value = quat.create(); } Math3DMultQuat.title = "Mult. Quat"; @@ -7085,12 +7786,43 @@ if(window.glMatrix) var B = this.getInputData(1); if(B == null) return; - var R = quat.multiply(quat.create(), A,B); + var R = quat.multiply( this._value, A, B ); this.setOutputData( 0, R ); } LiteGraph.registerNodeType("math3d/mult-quat", Math3DMultQuat ); + + function Math3DQuatSlerp() + { + this.addInputs( [["A","quat"],["B","quat"],["factor","number"]] ); + this.addOutput( "slerp","quat" ); + this.addProperty( "factor", 0.5 ); + + this._value = quat.create(); + } + + Math3DQuatSlerp.title = "Quat Slerp"; + Math3DQuatSlerp.desc = "quaternion spherical interpolation"; + + Math3DQuatSlerp.prototype.onExecute = function() + { + var A = this.getInputData(0); + if(A == null) + return; + var B = this.getInputData(1); + if(B == null) + return; + var factor = this.properties.factor; + if( this.getInputData(2) != null ) + factor = this.getInputData(2); + + var R = quat.slerp( this._value, A, B, factor ); + this.setOutputData( 0, R ); + } + + LiteGraph.registerNodeType("math3d/quat-slerp", Math3DQuatSlerp ); + } //glMatrix })(); @@ -8144,6 +8876,9 @@ if(typeof(LiteGraph) != "undefined") if(this.flags.collapsed) return; + if(!ctx.webgl) + return; //not working well + var tex = this.getInputData(0); if(!tex) return; @@ -8668,7 +9403,7 @@ if(typeof(LiteGraph) != "undefined") function LGraphTextureToViewport() { this.addInput("Texture","Texture"); - this.properties = { additive: false, antialiasing: false, disable_alpha: false, gamma: 1.0 }; + this.properties = { additive: false, antialiasing: false, filter: true, disable_alpha: false, gamma: 1.0 }; this.size[0] = 130; } @@ -8697,6 +9432,7 @@ if(typeof(LiteGraph) != "undefined") if( this.isInputConnected(1) ) gamma = this.getInputData(1); + tex.setParameter( gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST ); if(this.properties.antialiasing) { @@ -9609,38 +10345,23 @@ if(typeof(LiteGraph) != "undefined") this.properties.intensity = intensity; } - gl.disable( gl.BLEND ); - gl.disable( gl.DEPTH_TEST ); - var mesh = Mesh.getScreenQuad(); - var shader = LGraphTextureBlur._shader; - var scale = this.properties.scale || [1,1]; - //blur sometimes needs an aspect correction var aspect = LiteGraph.camera_aspect; if(!aspect && window.gl !== undefined) aspect = gl.canvas.height / gl.canvas.width; if(!aspect) aspect = 1; - - //iterate - var start_texture = tex; aspect = this.properties.preserve_aspect ? aspect : 1; + + var start_texture = tex; + var scale = this.properties.scale || [1,1]; + var origin = start_texture; for(var i = 0; i < iterations; ++i) { - this._temp_texture.drawTo( function() { - start_texture.bind(0); - shader.uniforms({u_texture:0, u_intensity: 1, u_offset: [0, 1/start_texture.height * scale[1] ] }) - .draw(mesh); - }); - - this._temp_texture.bind(0); - this._final_texture.drawTo( function() { - shader.uniforms({u_texture:0, u_intensity: intensity, u_offset: [aspect/start_texture.width * scale[0], 0] }) - .draw(mesh); - }); - start_texture = this._final_texture; + origin.applyBlur( aspect * scale[0] * i, scale[1] * i, intensity, this._temp_texture, this._final_texture ); + origin = this._final_texture; } - + this.setOutputData(0, this._final_texture); } @@ -10403,3 +11124,617 @@ if(typeof(LiteGraph) != "undefined") LiteGraph.registerNodeType("fx/vigneting", LGraphFXVigneting ); window.LGraphFXVigneting = LGraphFXVigneting; } +(function( global ) +{ + +function MIDIEvent( data ) +{ + this.channel = 0; + this.cmd = 0; + + if(data) + this.setup(data) + else + this.data = [0,0,0]; +} + +MIDIEvent.prototype.setup = function( raw_data ) +{ + this.data = raw_data; + + var midiStatus = raw_data[0]; + this.status = midiStatus; + + var midiCommand = midiStatus & 0xF0; + + if(midiStatus >= 0xF0) + this.cmd = midiStatus; + else + this.cmd = midiCommand; + + if(this.cmd == MIDIEvent.NOTEON && this.velocity == 0) + this.cmd = MIDIEvent.NOTEOFF; + + this.cmd_str = MIDIEvent.commands[ this.cmd ] || ""; + + if ( midiCommand >= MIDIEvent.NOTEON || midiCommand <= MIDIEvent.NOTEOFF ) { + this.channel = midiStatus & 0x0F; + } +} + +Object.defineProperty( MIDIEvent.prototype, "velocity", { + get: function() { + if(this.cmd == MIDIEvent.NOTEON) + return this.data[2]; + return -1; + }, + set: function(v) { + this.data[2] = v; // v / 127; + }, + enumerable: true +}); + +MIDIEvent.notes = ["A","A#","B","C","C#","D","D#","E","F","F#","G","G#"]; + +//returns HZs +MIDIEvent.prototype.getPitch = function() +{ + return Math.pow(2, (this.data[1] - 69) / 12 ) * 440; +} + +MIDIEvent.computePitch = function( note ) +{ + return Math.pow(2, (note - 69) / 12 ) * 440; +} + + +//not tested, there is a formula missing here +MIDIEvent.prototype.getPitchBend = function() +{ + return this.data[1] + (this.data[2] << 7) - 8192; +} + +MIDIEvent.computePitchBend = function(v1,v2) +{ + return v1 + (v2 << 7) - 8192; +} + +MIDIEvent.prototype.setCommandFromString = function( str ) +{ + this.cmd = MIDIEvent.computeCommandFromString(str); +} + +MIDIEvent.computeCommandFromString = function( str ) +{ + if(!str) + return 0; + + if(str && str.constructor === Number) + return str; + + str = str.toUpperCase(); + switch( str ) + { + case "NOTE ON": + case "NOTEON": return MIDIEvent.NOTEON; break; + case "NOTE OFF": + case "NOTEOFF": return MIDIEvent.NOTEON; break; + case "KEY PRESSURE": + case "KEYPRESSURE": return MIDIEvent.KEYPRESSURE; break; + case "CONTROLLER CHANGE": + case "CONTROLLERCHANGE": + case "CC": return MIDIEvent.CONTROLLERCHANGE; break; + case "PROGRAM CHANGE": + case "PROGRAMCHANGE": + case "PC": return MIDIEvent.PROGRAMCHANGE; break; + case "CHANNEL PRESSURE": + case "CHANNELPRESSURE": return MIDIEvent.CHANNELPRESSURE; break; + case "PITCH BEND": + case "PITCHBEND": return MIDIEvent.PITCHBEND; break; + case "TIME TICK": + case "TIMETICK": return MIDIEvent.TIMETICK; break; + default: return Number(str); //asume its a hex code + } +} + +MIDIEvent.toNoteString = function(d) +{ + var note = d - 21; + var octave = d - 24; + note = note % 12; + if(note < 0) + note = 12 + note; + return MIDIEvent.notes[ note ] + Math.floor(octave / 12 + 1); +} + +MIDIEvent.prototype.toString = function() +{ + var str = "" + this.channel + ". " ; + switch( this.cmd ) + { + case MIDIEvent.NOTEON: str += "NOTEON " + MIDIEvent.toNoteString( this.data[1] ); break; + case MIDIEvent.NOTEOFF: str += "NOTEOFF " + MIDIEvent.toNoteString( this.data[1] ); break; + case MIDIEvent.CONTROLLERCHANGE: str += "CC " + this.data[1] + " " + this.data[2]; break; + case MIDIEvent.PROGRAMCHANGE: str += "PC " + this.data[1]; break; + case MIDIEvent.PITCHBEND: str += "PITCHBEND " + this.getPitchBend(); break; + case MIDIEvent.KEYPRESSURE: str += "KEYPRESS " + this.data[1]; break; + } + + return str; +} + +MIDIEvent.prototype.toHexString = function() +{ + var str = ""; + for(var i = 0; i < this.data.length; i++) + str += this.data[i].toString(16) + " "; +} + +MIDIEvent.NOTEOFF = 0x80; +MIDIEvent.NOTEON = 0x90; +MIDIEvent.KEYPRESSURE = 0xA0; +MIDIEvent.CONTROLLERCHANGE = 0xB0; +MIDIEvent.PROGRAMCHANGE = 0xC0; +MIDIEvent.CHANNELPRESSURE = 0xD0; +MIDIEvent.PITCHBEND = 0xE0; +MIDIEvent.TIMETICK = 0xF8; + +MIDIEvent.commands = { + 0x80: "note off", + 0x90: "note on", + 0xA0: "key pressure", + 0xB0: "controller change", + 0xC0: "program change", + 0xD0: "channel pressure", + 0xE0: "pitch bend", + 0xF0: "system", + 0xF2: "Song pos", + 0xF3: "Song select", + 0xF6: "Tune request", + 0xF8: "time tick", + 0xFA: "Start Song", + 0xFB: "Continue Song", + 0xFC: "Stop Song", + 0xFE: "Sensing", + 0xFF: "Reset" +} + +//MIDI wrapper +function MIDIInterface( on_ready, on_error ) +{ + if(!navigator.requestMIDIAccess) + { + this.error = "not suppoorted"; + if(on_error) + on_error("Not supported"); + else + console.error("MIDI NOT SUPPORTED, enable by chrome://flags"); + return; + } + + this.on_ready = on_ready; + + navigator.requestMIDIAccess().then( this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this) ); +} + +MIDIInterface.MIDIEvent = MIDIEvent; + +MIDIInterface.prototype.onMIDISuccess = function(midiAccess) +{ + console.log( "MIDI ready!" ); + console.log( midiAccess ); + this.midi = midiAccess; // store in the global (in real usage, would probably keep in an object instance) + this.updatePorts(); + + if (this.on_ready) + this.on_ready(this); +} + +MIDIInterface.prototype.updatePorts = function() +{ + var midi = this.midi; + this.input_ports = midi.inputs; + var num = 0; + for (var i = 0; i < this.input_ports.size; ++i) { + var input = this.input_ports.get(i); + console.log( "Input port [type:'" + input.type + "'] id:'" + input.id + + "' manufacturer:'" + input.manufacturer + "' name:'" + input.name + + "' version:'" + input.version + "'" ); + num++; + } + this.num_input_ports = num; + + + num = 0; + this.output_ports = midi.outputs; + for (var i = 0; i < this.output_ports.size; ++i) { + var output = this.output_ports.get(i); + console.log( "Output port [type:'" + output.type + "'] id:'" + output.id + + "' manufacturer:'" + output.manufacturer + "' name:'" + output.name + + "' version:'" + output.version + "'" ); + num++; + } + this.num_output_ports = num; +} + +MIDIInterface.prototype.onMIDIFailure = function(msg) +{ + console.error( "Failed to get MIDI access - " + msg ); +} + +MIDIInterface.prototype.openInputPort = function( port, callback) +{ + var input_port = this.input_ports.get( port ); + if(!input_port) + return false; + + input_port.onmidimessage = function(a) { + var midi_event = new MIDIEvent(a.data); + if(callback) + callback(a.data, midi_event ); + if(MIDIInterface.on_message) + MIDIInterface.on_message( a.data, midi_event ); + } + console.log("port open: ", input_port); + return true; +} + +MIDIInterface.parseMsg = function(data) +{ + +} + +MIDIInterface.prototype.sendMIDI = function( port, midi_data ) +{ + if( !midi_data ) + return; + + var output_port = this.output_ports.get(port); + if(!output_port) + return; + + if( midi_data.constructor === MIDIEvent) + output_port.send( midi_data.data ); + else + output_port.send( midi_data ); +} + + + +function LGMIDIIn() +{ + this.addOutput( "on_midi", LiteGraph.EVENT ); + this.addOutput( "out", "midi" ); + this.properties = {port: 0}; + this._last_midi_event = null; + this._current_midi_event = null; + + var that = this; + new MIDIInterface( function( midi ){ + //open + that._midi = midi; + if(that._waiting) + that.onStart(); + that._waiting = false; + }); +} + +LGMIDIIn.MIDIInterface = MIDIInterface; + +LGMIDIIn.title = "MIDI Input"; +LGMIDIIn.desc = "Reads MIDI from a input port"; + +LGMIDIIn.prototype.getPropertyInfo = function(name) +{ + if(!this._midi) + return; + + if(name == "port") + { + var values = {}; + for (var i = 0; i < this._midi.input_ports.size; ++i) + { + var input = this._midi.input_ports.get(i); + values[i] = i + ".- " + input.name + " version:" + input.version; + } + return { type: "enum", values: values }; + } +} + +LGMIDIIn.prototype.onStart = function() +{ + if(this._midi) + this._midi.openInputPort( this.properties.port, this.onMIDIEvent.bind(this) ); + else + this._waiting = true; +} + +LGMIDIIn.prototype.onMIDIEvent = function( data, midi_event ) +{ + this._last_midi_event = midi_event; + + this.trigger( "on_midi", midi_event ); + if(midi_event.cmd == MIDIEvent.NOTEON) + this.trigger( "on_noteon", midi_event ); + else if(midi_event.cmd == MIDIEvent.NOTEOFF) + this.trigger( "on_noteoff", midi_event ); + else if(midi_event.cmd == MIDIEvent.CONTROLLERCHANGE) + this.trigger( "on_cc", midi_event ); + else if(midi_event.cmd == MIDIEvent.PROGRAMCHANGE) + this.trigger( "on_pc", midi_event ); + else if(midi_event.cmd == MIDIEvent.PITCHBEND) + this.trigger( "on_pitchbend", midi_event ); +} + +LGMIDIIn.prototype.onExecute = function() +{ + if(this.outputs) + { + var last = this._last_midi_event; + for(var i = 0; i < this.outputs.length; ++i) + { + var output = this.outputs[i]; + var v = null; + switch (output.name) + { + case "last_midi": v = last; break; + default: + continue; + } + this.setOutputData( i, v ); + } + } +} + +LGMIDIIn.prototype.onGetOutputs = function() { + return [ + ["last_midi","midi"], + ["on_midi",LiteGraph.EVENT], + ["on_noteon",LiteGraph.EVENT], + ["on_noteoff",LiteGraph.EVENT], + ["on_cc",LiteGraph.EVENT], + ["on_pc",LiteGraph.EVENT], + ["on_pitchbend",LiteGraph.EVENT] + ]; +} + +LiteGraph.registerNodeType("midi/input", LGMIDIIn); + + +function LGMIDIOut() +{ + this.addInput( "send", LiteGraph.EVENT ); + this.properties = {port: 0}; + + var that = this; + new MIDIInterface( function( midi ){ + that._midi = midi; + }); +} + +LGMIDIOut.MIDIInterface = MIDIInterface; + +LGMIDIOut.title = "MIDI Output"; +LGMIDIOut.desc = "Sends MIDI to output channel"; + +LGMIDIOut.prototype.getPropertyInfo = function(name) +{ + if(!this._midi) + return; + + if(name == "port") + { + var values = {}; + for (var i = 0; i < this._midi.output_ports.size; ++i) + { + var output = this._midi.output_ports.get(i); + values[i] = i + ".- " + output.name + " version:" + output.version; + } + return { type: "enum", values: values }; + } +} + + +LGMIDIOut.prototype.onAction = function(event, midi_event ) +{ + console.log(midi_event); + if(!this._midi) + return; + if(event == "send") + this._midi.sendMIDI( this.port, midi_event ); + this.trigger("midi",midi_event); +} + +LGMIDIOut.prototype.onGetInputs = function() { + return [["send",LiteGraph.ACTION]]; +} + +LGMIDIOut.prototype.onGetOutputs = function() { + return [["on_midi",LiteGraph.EVENT]]; +} + +LiteGraph.registerNodeType("midi/output", LGMIDIOut); + + +function LGMIDIShow() +{ + this.addInput( "on_midi", LiteGraph.EVENT ); + this._str = ""; + this.size = [200,40] +} + +LGMIDIShow.title = "MIDI Show"; +LGMIDIShow.desc = "Shows MIDI in the graph"; + +LGMIDIShow.prototype.onAction = function(event, midi_event ) +{ + if(!midi_event) + return; + if(midi_event.constructor === MIDIEvent) + this._str = midi_event.toString(); + else + this._str = "???"; +} + +LGMIDIShow.prototype.onDrawForeground = function( ctx ) +{ + if( !this._str ) + return; + + ctx.font = "30px Arial"; + ctx.fillText( this._str, 10, this.size[1] * 0.8 ); +} + +LGMIDIShow.prototype.onGetInputs = function() { + return [["in",LiteGraph.ACTION]]; +} + +LGMIDIShow.prototype.onGetOutputs = function() { + return [["on_midi",LiteGraph.EVENT]]; +} + +LiteGraph.registerNodeType("midi/show", LGMIDIShow); + + + +function LGMIDIFilter() +{ + this.properties = { + channel: -1, + cmd: -1, + min_value: -1, + max_value: -1 + }; + + this.addInput( "in", LiteGraph.EVENT ); + this.addOutput( "on_midi", LiteGraph.EVENT ); +} + +LGMIDIFilter.title = "MIDI Filter"; +LGMIDIFilter.desc = "Filters MIDI messages"; + +LGMIDIFilter.prototype.onAction = function(event, midi_event ) +{ + if(!midi_event || midi_event.constructor !== MIDIEvent) + return; + + if( this.properties.channel != -1 && midi_event.channel != this.properties.channel) + return; + if(this.properties.cmd != -1 && midi_event.cmd != this.properties.cmd) + return; + if(this.properties.min_value != -1 && midi_event.data[1] < this.properties.min_value) + return; + if(this.properties.max_value != -1 && midi_event.data[1] > this.properties.max_value) + return; + this.trigger("on_midi",midi_event); +} + +LiteGraph.registerNodeType("midi/filter", LGMIDIFilter); + + +function LGMIDIEvent() +{ + this.properties = { + channel: 0, + cmd: "CC", + value1: 1, + value2: 1 + }; + + this.addInput( "send", LiteGraph.EVENT ); + this.addInput( "assign", LiteGraph.EVENT ); + this.addOutput( "on_midi", LiteGraph.EVENT ); +} + +LGMIDIEvent.title = "MIDIEvent"; +LGMIDIEvent.desc = "Create a MIDI Event"; + +LGMIDIEvent.prototype.onAction = function( event, midi_event ) +{ + if(event == "assign") + { + this.properties.channel = midi_event.channel; + this.properties.cmd = midi_event.cmd; + this.properties.value1 = midi_event.data[1]; + this.properties.value2 = midi_event.data[2]; + return; + } + + //send + var midi_event = new MIDIEvent(); + midi_event.channel = this.properties.channel; + if(this.properties.cmd && this.properties.cmd.constructor === String) + midi_event.setCommandFromString( this.properties.cmd ); + else + midi_event.cmd = this.properties.cmd; + midi_event.data[0] = midi_event.cmd | midi_event.channel; + midi_event.data[1] = Number(this.properties.value1); + midi_event.data[2] = Number(this.properties.value2); + this.trigger("on_midi",midi_event); +} + +LGMIDIEvent.prototype.onExecute = function() +{ + var props = this.properties; + + if(this.outputs) + { + for(var i = 0; i < this.outputs.length; ++i) + { + var output = this.outputs[i]; + var v = null; + switch (output.name) + { + case "midi": + v = new MIDIEvent(); + v.setup([ props.cmd, props.value1, props.value2 ]); + v.channel = props.channel; + break; + case "command": v = props.cmd; break; + case "note": v = (props.cmd == MIDIEvent.NOTEON || props.cmd == MIDIEvent.NOTEOFF) ? props.value1 : NULL; break; + case "velocity": v = props.cmd == MIDIEvent.NOTEON ? props.value2 : NULL; break; + case "pitch": v = props.cmd == MIDIEvent.NOTEON ? MIDIEvent.computePitch( props.value1 ) : null; break; + case "pitchbend": v = props.cmd == MIDIEvent.PITCHBEND ? MIDIEvent.computePitchBend( props.value1, props.value2 ) : null; break; + default: + continue; + } + if(v !== null) + this.setOutputData( i, v ); + } + } +} + +LGMIDIEvent.prototype.onPropertyChanged = function(name,value) +{ + if(name == "cmd") + this.properties.cmd = MIDIEvent.computeCommandFromString( value ); +} + + +LGMIDIEvent.prototype.onGetOutputs = function() { + return [ + ["midi","midi"], + ["on_midi",LiteGraph.EVENT], + ["command","number"], + ["note","number"], + ["velocity","number"], + ["pitch","number"], + ["pitchbend","number"] + ]; +} + + +LiteGraph.registerNodeType("midi/event", LGMIDIEvent); + + + + +function now() { return window.performance.now() } + + + + + + + +})( window ); diff --git a/build/litegraph.min.js b/build/litegraph.min.js index 621f7d06c..86e6b8f7c 100755 --- a/build/litegraph.min.js +++ b/build/litegraph.min.js @@ -1,19 +1,20 @@ -var LiteGraph={NODE_TITLE_HEIGHT:16,NODE_SLOT_HEIGHT:15,NODE_WIDTH:140,NODE_MIN_WIDTH:50,NODE_COLLAPSED_RADIUS:10,NODE_COLLAPSED_WIDTH:80,CANVAS_GRID_SIZE:10,NODE_TITLE_COLOR:"#222",NODE_DEFAULT_COLOR:"#999",NODE_DEFAULT_BGCOLOR:"#444",NODE_DEFAULT_BOXCOLOR:"#AEF",NODE_DEFAULT_SHAPE:"box",MAX_NUMBER_OF_NODES:1E3,DEFAULT_POSITION:[100,100],node_images_path:"",proxy:null,debug:!1,throw_errors:!0,registered_node_types:{},Nodes:{},registerNodeType:function(a,b){if(!b.prototype)throw"Cannot register a simple object, it must be a class with a prototype"; -b.type=a;LiteGraph.debug&&console.log("Node registered: "+a);a.split("/");var c=a.lastIndexOf("/");b.category=a.substr(0,c);if(b.prototype)for(var d in LGraphNode.prototype)b.prototype[d]||(b.prototype[d]=LGraphNode.prototype[d]);this.registered_node_types[a]=b;b.constructor.name&&(this.Nodes[b.constructor.name]=b)},addNodeMethod:function(a,b){LGraphNode.prototype[a]=b;for(var c in this.registered_node_types)this.registered_node_types[c].prototype[a]=b},createNode:function(a,b,c){var d=this.registered_node_types[a]; -if(!d)return LiteGraph.debug&&console.log('GraphNode type "'+a+'" not registered.'),null;b=b||d.title||a;d=new d(name);d.type=a;d.title||(d.title=b);d.properties||(d.properties={});d.flags||(d.flags={});d.size||(d.size=d.computeSize());d.pos||(d.pos=LiteGraph.DEFAULT_POSITION.concat());if(c)for(var e in c)d[e]=c[e];return d},getNodeType:function(a){return this.registered_node_types[a]},getNodeTypesInCategory:function(a){var b=[],c;for(c in this.registered_node_types)""==a?null==this.registered_node_types[c].category&& -b.push(this.registered_node_types[c]):this.registered_node_types[c].category==a&&b.push(this.registered_node_types[c]);return b},getNodeTypesCategories:function(){var a={"":1},b;for(b in this.registered_node_types)this.registered_node_types[b].category&&!this.registered_node_types[b].skip_list&&(a[this.registered_node_types[b].category]=1);var c=[];for(b in a)c.push(b);return c},reloadNodes:function(a){var b=document.getElementsByTagName("script"),c=[],d;for(d in b)c.push(b[d]);b=document.getElementsByTagName("head")[0]; -a=document.location.href+a;for(d in c){var e=c[d].src;if(e&&e.substr(0,a.length)==a)try{LiteGraph.debug&&console.log("Reloading: "+e);var f=document.createElement("script");f.type="text/javascript";f.src=e;b.appendChild(f);b.removeChild(c[d])}catch(g){if(LiteGraph.throw_errors)throw g;LiteGraph.debug&&console.log("Error while reloading "+e)}}LiteGraph.debug&&console.log("Nodes reloaded")},cloneObject:function(a,b){if(null==a)return null;var c=JSON.parse(JSON.stringify(a));if(!b)return c;for(var d in c)b[d]= -c[d];return b}};LiteGraph.getTime="undefined"!=typeof performance?function(){return performance.now()}:function(){return Date.now()};function LGraph(){LiteGraph.debug&&console.log("Graph created");this.list_of_graphcanvas=null;this.clear()}LGraph.supported_types=["number","string","boolean"];LGraph.prototype.getSupportedTypes=function(){return this.supported_types||LGraph.supported_types};LGraph.STATUS_STOPPED=1;LGraph.STATUS_RUNNING=2; +var LiteGraph={NODE_TITLE_HEIGHT:16,NODE_SLOT_HEIGHT:15,NODE_WIDTH:140,NODE_MIN_WIDTH:50,NODE_COLLAPSED_RADIUS:10,NODE_COLLAPSED_WIDTH:80,CANVAS_GRID_SIZE:10,NODE_TITLE_COLOR:"#222",NODE_DEFAULT_COLOR:"#999",NODE_DEFAULT_BGCOLOR:"#444",NODE_DEFAULT_BOXCOLOR:"#AEF",NODE_DEFAULT_SHAPE:"box",MAX_NUMBER_OF_NODES:1E3,DEFAULT_POSITION:[100,100],node_images_path:"",INPUT:1,OUTPUT:2,EVENT:-1,ACTION:-1,ALWAYS:0,ON_EVENT:1,NEVER:2,proxy:null,debug:!1,throw_errors:!0,registered_node_types:{},Nodes:{},registerNodeType:function(a, +b){if(!b.prototype)throw"Cannot register a simple object, it must be a class with a prototype";b.type=a;LiteGraph.debug&&console.log("Node registered: "+a);a.split("/");var c=a.lastIndexOf("/");b.category=a.substr(0,c);if(b.prototype)for(var d in LGraphNode.prototype)b.prototype[d]||(b.prototype[d]=LGraphNode.prototype[d]);this.registered_node_types[a]=b;b.constructor.name&&(this.Nodes[b.constructor.name]=b);b.prototype.onPropertyChange&&console.warn("LiteGraph node class "+a+" has onPropertyChange method, it must be called onPropertyChanged with d at the end")}, +addNodeMethod:function(a,b){LGraphNode.prototype[a]=b;for(var c in this.registered_node_types)this.registered_node_types[c].prototype[a]=b},createNode:function(a,b,c){var d=this.registered_node_types[a];if(!d)return LiteGraph.debug&&console.log('GraphNode type "'+a+'" not registered.'),null;b=b||d.title||a;d=new d(name);d.type=a;d.title||(d.title=b);d.properties||(d.properties={});d.properties_info||(d.properties_info=[]);d.flags||(d.flags={});d.size||(d.size=d.computeSize());d.pos||(d.pos=LiteGraph.DEFAULT_POSITION.concat()); +d.mode||(d.mode=LiteGraph.ALWAYS);if(c)for(var e in c)d[e]=c[e];return d},getNodeType:function(a){return this.registered_node_types[a]},getNodeTypesInCategory:function(a){var b=[],c;for(c in this.registered_node_types)""==a?null==this.registered_node_types[c].category&&b.push(this.registered_node_types[c]):this.registered_node_types[c].category==a&&b.push(this.registered_node_types[c]);return b},getNodeTypesCategories:function(){var a={"":1},b;for(b in this.registered_node_types)this.registered_node_types[b].category&& +!this.registered_node_types[b].skip_list&&(a[this.registered_node_types[b].category]=1);var c=[];for(b in a)c.push(b);return c},reloadNodes:function(a){var b=document.getElementsByTagName("script"),c=[],d;for(d in b)c.push(b[d]);b=document.getElementsByTagName("head")[0];a=document.location.href+a;for(d in c){var e=c[d].src;if(e&&e.substr(0,a.length)==a)try{LiteGraph.debug&&console.log("Reloading: "+e);var f=document.createElement("script");f.type="text/javascript";f.src=e;b.appendChild(f);b.removeChild(c[d])}catch(g){if(LiteGraph.throw_errors)throw g; +LiteGraph.debug&&console.log("Error while reloading "+e)}}LiteGraph.debug&&console.log("Nodes reloaded")},cloneObject:function(a,b){if(null==a)return null;var c=JSON.parse(JSON.stringify(a));if(!b)return c;for(var d in c)b[d]=c[d];return b},isValidConnection:function(a,b){return!a||!b||a==a||a!==LiteGraph.EVENT&&b!==LiteGraph.EVENT&&a.toLowerCase()==b.toLowerCase()?!0:!1}};LiteGraph.getTime="undefined"!=typeof performance?function(){return performance.now()}:function(){return Date.now()}; +function LGraph(){LiteGraph.debug&&console.log("Graph created");this.list_of_graphcanvas=null;this.clear()}LGraph.supported_types=["number","string","boolean"];LGraph.prototype.getSupportedTypes=function(){return this.supported_types||LGraph.supported_types};LGraph.STATUS_STOPPED=1;LGraph.STATUS_RUNNING=2; LGraph.prototype.clear=function(){this.stop();this.status=LGraph.STATUS_STOPPED;this.last_node_id=0;this._nodes=[];this._nodes_by_id={};this.last_link_id=0;this.links={};this.iteration=0;this.config={};this.fixedtime=this.runningtime=this.globaltime=0;this.elapsed_time=this.fixedtime_lapse=0.01;this.starttime=0;this.global_inputs={};this.global_outputs={};this.debug=!0;this.change();this.sendActionToCanvas("clear")}; LGraph.prototype.attachCanvas=function(a){if(a.constructor!=LGraphCanvas)throw"attachCanvas expects a LGraphCanvas instance";a.graph&&a.graph!=this&&a.graph.detachCanvas(a);a.graph=this;this.list_of_graphcanvas||(this.list_of_graphcanvas=[]);this.list_of_graphcanvas.push(a)};LGraph.prototype.detachCanvas=function(a){if(this.list_of_graphcanvas){var b=this.list_of_graphcanvas.indexOf(a);-1!=b&&(a.graph=null,this.list_of_graphcanvas.splice(b,1))}}; LGraph.prototype.start=function(a){if(this.status!=LGraph.STATUS_RUNNING){this.status=LGraph.STATUS_RUNNING;if(this.onPlayEvent)this.onPlayEvent();this.sendEventToAllNodes("onStart");this.starttime=LiteGraph.getTime();var b=this;this.execution_timer_id=setInterval(function(){b.runStep(1)},a||1)}}; LGraph.prototype.stop=function(){if(this.status!=LGraph.STATUS_STOPPED){this.status=LGraph.STATUS_STOPPED;if(this.onStopEvent)this.onStopEvent();null!=this.execution_timer_id&&clearInterval(this.execution_timer_id);this.execution_timer_id=null;this.sendEventToAllNodes("onStop")}}; -LGraph.prototype.runStep=function(a){a=a||1;var b=LiteGraph.getTime();this.globaltime=0.001*(b-this.starttime);try{for(var c=0;c=LiteGraph.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";if(null==a.id||-1==a.id)a.id=this.last_node_id++;a.graph=this;this._nodes.push(a);this._nodes_by_id[a.id]=a;if(a.onAdded)a.onAdded();this.config.align_to_grid&&a.alignToGrid();b||this.updateExecutionOrder();if(this.onNodeAdded)this.onNodeAdded(a);this.setDirtyCanvas(!0);this.change();return a}}; +LGraph.prototype.runStep=function(a){a=a||1;var b=LiteGraph.getTime();this.globaltime=0.001*(b-this.starttime);var c=this._nodes_in_order?this._nodes_in_order:this._nodes;if(c){try{for(var d=0;d=LiteGraph.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";if(null==a.id||-1==a.id)a.id=this.last_node_id++;a.graph=this;this._nodes.push(a);this._nodes_by_id[a.id]=a;if(a.onAdded)a.onAdded(this);this.config.align_to_grid&&a.alignToGrid();b||this.updateExecutionOrder();if(this.onNodeAdded)this.onNodeAdded(a);this.setDirtyCanvas(!0);this.change();return a}}; LGraph.prototype.remove=function(a){if(null!=this._nodes_by_id[a.id]&&!a.ignore_remove){if(a.inputs)for(var b=0;b!a.length||(this._pos[0]=a[0],this._pos[1]=a[1])},get:function(){return this._pos},enumerable:!0});this.id=-1;this.type=null;this.inputs=[];this.outputs=[];this.connections=[];this.properties={};this.data=null;this.flags={}}; -LGraphNode.prototype.configure=function(a){for(var b in a)if("console"!=b)if("properties"==b)for(var c in a.properties)this.properties[c]=a.properties[c];else null!=a[b]&&("object"==typeof a[b]?this[b]&&this[b].configure?this[b].configure(a[b]):this[b]=LiteGraph.cloneObject(a[b],this[b]):this[b]=a[b]);for(var d in this.inputs)c=this.inputs[d],c.link&&c.link.length&&(a=c.link,"object"==typeof a&&(c.link=a[0],this.graph.links[a[0]]={id:a[0],origin_id:a[1],origin_slot:a[2],target_id:a[3],target_slot:a[4]})); -for(d in this.outputs)if(c=this.outputs[d],c.links&&0!=c.links.length)for(b in c.links)a=c.links[b],"object"==typeof a&&(c.links[b]=a[0])}; -LGraphNode.prototype.serialize=function(){var a={id:this.id,title:this.title,type:this.type,pos:this.pos,size:this.size,data:this.data,flags:LiteGraph.cloneObject(this.flags),inputs:this.inputs,outputs:this.outputs};this.properties&&(a.properties=LiteGraph.cloneObject(this.properties));a.type||(a.type=this.constructor.type);this.color&&(a.color=this.color);this.bgcolor&&(a.bgcolor=this.bgcolor);this.boxcolor&&(a.boxcolor=this.boxcolor);this.shape&&(a.shape=this.shape);if(this.onSerialize)this.onSerialize(a); -return a};LGraphNode.prototype.clone=function(){var a=LiteGraph.createNode(this.type),b=this.serialize();delete b.id;a.configure(b);return a};LGraphNode.prototype.toString=function(){return JSON.stringify(this.serialize())};LGraphNode.prototype.getTitle=function(){return this.title||this.constructor.title}; -LGraphNode.prototype.setOutputData=function(a,b){if(this.outputs&&-1!a.length||(this._pos[0]=a[0],this._pos[1]=a[1])},get:function(){return this._pos},enumerable:!0});this.id=-1;this.type=null;this.inputs=[];this.outputs=[];this.connections=[];this.properties={};this.properties_info=[];this.data=null;this.flags={}}; +LGraphNode.prototype.configure=function(a){for(var b in a)if("console"!=b)if("properties"==b)for(var c in a.properties){if(this.properties[c]=a.properties[c],this.onPropertyChanged)this.onPropertyChanged(c,a.properties[c])}else null!=a[b]&&("object"==typeof a[b]?this[b]&&this[b].configure?this[b].configure(a[b]):this[b]=LiteGraph.cloneObject(a[b],this[b]):this[b]=a[b]);if(this.onConnectionsChange)this.onConnectionsChange();for(var d in this.inputs)c=this.inputs[d],c.link&&c.link.length&&(a=c.link, +"object"==typeof a&&(c.link=a[0],this.graph.links[a[0]]={id:a[0],origin_id:a[1],origin_slot:a[2],target_id:a[3],target_slot:a[4]}));for(d in this.outputs)if(c=this.outputs[d],c.links&&0!=c.links.length)for(b in c.links)a=c.links[b],"object"==typeof a&&(c.links[b]=a[0])}; +LGraphNode.prototype.serialize=function(){var a={id:this.id,title:this.title,type:this.type,pos:this.pos,size:this.size,data:this.data,flags:LiteGraph.cloneObject(this.flags),inputs:this.inputs,outputs:this.outputs,mode:this.mode};this.properties&&(a.properties=LiteGraph.cloneObject(this.properties));a.type||(a.type=this.constructor.type);this.color&&(a.color=this.color);this.bgcolor&&(a.bgcolor=this.bgcolor);this.boxcolor&&(a.boxcolor=this.boxcolor);this.shape&&(a.shape=this.shape);if(this.onSerialize)this.onSerialize(a); +return a};LGraphNode.prototype.clone=function(){var a=LiteGraph.createNode(this.type),b=LiteGraph.cloneObject(this.serialize());if(b.inputs)for(var c=0;c=this.inputs.length||null==this.inputs[a].link)){var c=this.graph.links[this.inputs[a].link];if(!b)return c.data;var d=this.graph.getNodeById(c.origin_id);if(!d)return c.data;if(d.updateOutputData)d.updateOutputData(c.origin_slot);else if(d.onExecute)d.onExecute();return c.data}};LGraphNode.prototype.isInputConnected=function(a){return this.inputs?aa&&this.pos[1]-d-cb)return!0;return!1}; LGraphNode.prototype.getSlotInPosition=function(a,b){if(this.inputs)for(var c=0,d=this.inputs.length;c=this.outputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;b&&b.constructor===Number&&(b=this.graph.getNodeById(b));if(!b)throw"Node not found";if(b==this)return!1;if(c.constructor===String){if(c=b.findInputSlot(c),-1==c)return LiteGraph.debug&& -console.log("Connect: Error, no slot of name "+c),!1}else if(!b.inputs||c>=b.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;-1!=c&&null!=b.inputs[c].link&&b.disconnectInput(c);this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);var d=this.outputs[a];if(b.onConnectInput&&!1===b.onConnectInput(c,d.type,d))return!1;-1==c?(null==d.links&&(d.links=[]),d.links.push({id:b.id,slot:-1})):d.type&&b.inputs[c].type&&d.type.toLowerCase()!=b.inputs[c].type.toLowerCase()|| -(a={id:this.graph.last_link_id++,origin_id:this.id,origin_slot:a,target_id:b.id,target_slot:c},this.graph.links[a.id]=a,null==d.links&&(d.links=[]),d.links.push(a.id),b.inputs[c].link=a.id);this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);return!0}; +console.log("Connect: Error, no slot of name "+c),!1}else{if(c===LiteGraph.EVENT)return!1;if(!b.inputs||c>=b.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1}null!=b.inputs[c].link&&b.disconnectInput(c);this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);var d=this.outputs[a];if(b.onConnectInput&&!1===b.onConnectInput(c,d.type,d))return!1;if(LiteGraph.isValidConnection(d.type,b.inputs[c].type)){var e={id:this.graph.last_link_id++,origin_id:this.id, +origin_slot:a,target_id:b.id,target_slot:c};this.graph.links[e.id]=e;null==d.links&&(d.links=[]);d.links.push(e.id);b.inputs[c].link=e.id;if(this.onConnectionsChange)this.onConnectionsChange(LiteGraph.OUTPUT,a);if(b.onConnectionsChange)b.onConnectionsChange(LiteGraph.OUTPUT,c)}this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);return!0}; LGraphNode.prototype.disconnectOutput=function(a,b){if(a.constructor===String){if(a=this.findOutputSlot(a),-1==a)return LiteGraph.debug&&console.log("Connect: Error, no slot of name "+a),!1}else if(!this.outputs||a>=this.outputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;var c=this.outputs[a];if(!c.links||0==c.links.length)return!1;if(b){b.constructor===Number&&(b=this.graph.getNodeById(b));if(!b)throw"Target Node not found";for(var d=0,e=c.links.length;d< e;d++){var f=c.links[d],g=this.graph.links[f];if(g.target_id==b.id){c.links.splice(d,1);b.inputs[g.target_slot].link=null;delete this.graph.links[f];break}}}else{d=0;for(e=c.links.length;d=this.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;if(!this.inputs[a])return!1;var b=this.inputs[a].link;this.inputs[a].link=null;if(b=this.graph.links[b]){a=this.graph.getNodeById(b.origin_id);if(!a)return!1;a=a.outputs[b.origin_slot];if(!a|| -!a.links||0==a.links.length)return!1;for(var c=0,d=a.links.length;c=this.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;if(!this.inputs[a])return!1;var b=this.inputs[a].link;this.inputs[a].link=null;if(b=this.graph.links[b]){a=this.graph.getNodeById(b.origin_id);if(!a)return!1;var c=a.outputs[b.origin_slot];if(!c|| +!c.links||0==c.links.length)return!1;for(var d=0,e=c.links.length;db&&this.inputs[b].pos?[this.pos[0]+this.inputs[b].pos[0],this.pos[1]+this.inputs[b].pos[1]]:!a&&this.outputs.length>b&&this.outputs[b].pos?[this.pos[0]+this.outputs[b].pos[0],this.pos[1]+this.outputs[b].pos[1]]: a?[this.pos[0],this.pos[1]+10+b*LiteGraph.NODE_SLOT_HEIGHT]:[this.pos[0]+this.size[0]+1,this.pos[1]+10+b*LiteGraph.NODE_SLOT_HEIGHT]};LGraphNode.prototype.alignToGrid=function(){this.pos[0]=LiteGraph.CANVAS_GRID_SIZE*Math.round(this.pos[0]/LiteGraph.CANVAS_GRID_SIZE);this.pos[1]=LiteGraph.CANVAS_GRID_SIZE*Math.round(this.pos[1]/LiteGraph.CANVAS_GRID_SIZE)}; LGraphNode.prototype.trace=function(a){this.console||(this.console=[]);this.console.push(a);this.console.length>LGraphNode.MAX_CONSOLE&&this.console.shift();this.graph.onNodeTrace(this,a)};LGraphNode.prototype.setDirtyCanvas=function(a,b){this.graph&&this.graph.sendActionToCanvas("setDirty",[a,b])};LGraphNode.prototype.loadImage=function(a){var b=new Image;b.src=LiteGraph.node_images_path+a;b.ready=!1;var c=this;b.onload=function(){this.ready=!0;c.setDirtyCanvas(!0)};return b}; -LGraphNode.prototype.executeAction=function(a){if(""==a)return!1;if(-1!=a.indexOf(";")||-1!=a.indexOf("}"))return this.trace("Error: Action contains unsafe characters"),!1;var b=a.split("(")[0];if("function"!=typeof this[b])return this.trace("Error: Action not found on node: "+b),!1;try{b=eval,eval=null,(new Function("with(this) { "+a+"}")).call(this),eval=b}catch(c){return this.trace("Error executing action {"+a+"} :"+c),!1}return!0}; LGraphNode.prototype.captureInput=function(a){if(this.graph&&this.graph.list_of_graphcanvas)for(var b=this.graph.list_of_graphcanvas,c=0;cLiteGraph.getTime()-this.last_mouseclick&&this.selected_nodes[c.id]){if(c.onDblClick)c.onDblClick(a);this.processNodeDblClicked(c);e=!0}c.onMouseDown&&c.onMouseDown(a)?e=!0:this.live_mode&&(e=d=!0);e||(this.allow_dragnodes&&(this.node_dragged=c),this.selected_nodes[c.id]||this.processNodeSelected(c,a));this.dirty_canvas=!0}}else d=!0;d&&this.allow_dragcanvas&&(this.dragging_canvas=!0)}else 2!=a.which&& -3==a.which&&this.processContextualMenu(c,a);this.last_mouse[0]=a.localX;this.last_mouse[1]=a.localY;this.last_mouseclick=LiteGraph.getTime();this.canvas_mouse=[a.canvasX,a.canvasY];this.graph.change();(!b.document.activeElement||"input"!=b.document.activeElement.nodeName.toLowerCase()&&"textarea"!=b.document.activeElement.nodeName.toLowerCase())&&a.preventDefault();a.stopPropagation();if(this.onMouseDown)this.onMouseDown(a);return!1}}; +LiteGraph.NODE_TITLE_HEIGHT,LiteGraph.NODE_TITLE_HEIGHT)&&(c.collapse(),f=!0);if(!f){e=!1;if(300>LiteGraph.getTime()-this.last_mouseclick&&this.selected_nodes[c.id]){if(c.onDblClick)c.onDblClick(a);this.processNodeDblClicked(c);e=!0}c.onMouseDown&&c.onMouseDown(a,[a.canvasX-c.pos[0],a.canvasY-c.pos[1]])?e=!0:this.live_mode&&(e=d=!0);e||(this.allow_dragnodes&&(this.node_dragged=c),this.selected_nodes[c.id]||this.processNodeSelected(c,a));this.dirty_canvas=!0}}else d=!0;d&&this.allow_dragcanvas&&(this.dragging_canvas= +!0)}else 2!=a.which&&3==a.which&&this.processContextualMenu(c,a);this.last_mouse[0]=a.localX;this.last_mouse[1]=a.localY;this.last_mouseclick=LiteGraph.getTime();this.canvas_mouse=[a.canvasX,a.canvasY];this.graph.change();(!b.document.activeElement||"input"!=b.document.activeElement.nodeName.toLowerCase()&&"textarea"!=b.document.activeElement.nodeName.toLowerCase())&&a.preventDefault();a.stopPropagation();if(this.onMouseDown)this.onMouseDown(a);return!1}}; LGraphCanvas.prototype.processMouseMove=function(a){this.autoresize&&this.resize();if(this.graph){this.adjustMouseEvent(a);var b=[a.localX,a.localY],c=[b[0]-this.last_mouse[0],b[1]-this.last_mouse[1]];this.last_mouse=b;this.canvas_mouse=[a.canvasX,a.canvasY];if(this.dragging_canvas)this.offset[0]+=c[0]/this.scale,this.offset[1]+=c[1]/this.scale,this.dirty_bgcanvas=this.dirty_canvas=!0;else{this.connecting_node&&(this.dirty_canvas=!0);for(var b=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes), -d=0,e=this.graph._nodes.length;db&&(c*=1/1.1);this.setZoom(c,[a.localX,a.localY]);this.graph.change();a.preventDefault();return!1}}; +LGraphCanvas.prototype.processMouseUp=function(a){if(this.graph){var b=this.getCanvasWindow().document;b.removeEventListener("mousemove",this._mousemove_callback,!0);this.canvas.addEventListener("mousemove",this._mousemove_callback,!0);b.removeEventListener("mouseup",this._mouseup_callback,!0);this.adjustMouseEvent(a);if(1==a.which)if(this.connecting_node){this.dirty_bgcanvas=this.dirty_canvas=!0;if(b=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes))if(this.connecting_output.type== +LiteGraph.EVENT&&this.isOverNodeBox(b,a.canvasX,a.canvasY))this.connecting_node.connect(this.connecting_slot,b,LiteGraph.EVENT);else{var c=this.isOverNodeInput(b,a.canvasX,a.canvasY);-1!=c?this.connecting_node.connect(this.connecting_slot,b,c):(c=b.getInputInfo(0),this.connecting_output.type==LiteGraph.EVENT?this.connecting_node.connect(this.connecting_slot,b,LiteGraph.EVENT):c&&!c.link&&c.type==this.connecting_output.type&&this.connecting_node.connect(this.connecting_slot,b,0))}this.connecting_node= +this.connecting_pos=this.connecting_output=null;this.connecting_slot=-1}else if(this.resizing_node)this.dirty_bgcanvas=this.dirty_canvas=!0,this.resizing_node=null;else if(this.node_dragged)this.dirty_bgcanvas=this.dirty_canvas=!0,this.node_dragged.pos[0]=Math.round(this.node_dragged.pos[0]),this.node_dragged.pos[1]=Math.round(this.node_dragged.pos[1]),this.graph.config.align_to_grid&&this.node_dragged.alignToGrid(),this.node_dragged=null;else{this.dirty_canvas=!0;this.dragging_canvas=!1;if(this.node_over&& +this.node_over.onMouseUp)this.node_over.onMouseUp(a,[a.canvasX-this.node_over.pos[0],a.canvasY-this.node_over.pos[1]]);if(this.node_capturing_input&&this.node_capturing_input.onMouseUp)this.node_capturing_input.onMouseUp(a,[a.canvasX-this.node_capturing_input.pos[0],a.canvasY-this.node_capturing_input.pos[1]])}else 2==a.which?(this.dirty_canvas=!0,this.dragging_canvas=!1):3==a.which&&(this.dirty_canvas=!0,this.dragging_canvas=!1);this.graph.change();a.stopPropagation();a.preventDefault();return!1}}; +LGraphCanvas.prototype.processMouseWheel=function(a){if(this.graph&&this.allow_dragcanvas){var b=null!=a.wheelDeltaY?a.wheelDeltaY:-60*a.detail;this.adjustMouseEvent(a);var c=this.scale;0b&&(c*=1/1.1);this.setZoom(c,[a.localX,a.localY]);this.graph.change();a.preventDefault();return!1}};LGraphCanvas.prototype.isOverNodeBox=function(a,b,c){var d=LiteGraph.NODE_TITLE_HEIGHT;return isInsideRectangle(b,c,a.pos[0]+2,a.pos[1]+2-d,d-4,d-4)?!0:!1}; LGraphCanvas.prototype.isOverNodeInput=function(a,b,c,d){if(a.inputs)for(var e=0,f=a.inputs.length;ec-this.graph._last_trigger_time)&&this.drawBackCanvas();(this.dirty_canvas|| +a)&&this.drawFrontCanvas();this.fps=this.render_time?1/this.render_time:0;this.frame+=1}}; LGraphCanvas.prototype.drawFrontCanvas=function(){this.ctx||(this.ctx=this.bgcanvas.getContext("2d"));var a=this.ctx;if(a){a.start2D&&a.start2D();var b=this.canvas;a.restore();a.setTransform(1,0,0,1,0,0);this.dirty_area&&(a.save(),a.beginPath(),a.rect(this.dirty_area[0],this.dirty_area[1],this.dirty_area[2],this.dirty_area[3]),a.clip());this.clear_background&&a.clearRect(0,0,b.width,b.height);this.bgcanvas==this.canvas?this.drawBackCanvas():a.drawImage(this.bgcanvas,0,0);if(this.onRender)this.onRender(b, -a);this.show_info&&this.renderInfo(a);if(this.graph){a.save();a.scale(this.scale,this.scale);a.translate(this.offset[0],this.offset[1]);this.visible_nodes=b=this.computeVisibleNodes();for(var c=0;cb[1]?0:Math.PI;a.save();a.translate(d[0],d[1]);a.rotate(f);a.beginPath();a.moveTo(-5,-5);a.lineTo(0,5);a.lineTo(5,-5);a.fill();a.restore()}}else a.beginPath(),a.moveTo(b[0],b[1]),a.lineTo(c[0], -c[1]),a.stroke()};LGraphCanvas.prototype.computeConnectionPoint=function(a,b,c){var d=distance(a,b),e=[a[0]+0.25*d,a[1]],d=[b[0]-0.25*d,b[1]],f=(1-c)*(1-c)*(1-c),g=3*(1-c)*(1-c)*c,h=3*(1-c)*c*c;c*=c*c;return[f*a[0]+g*e[0]+h*d[0]+c*b[0],f*a[1]+g*e[1]+h*d[1]+c*b[1]]}; +c,0.5*a.size[1]-c,2*c,2*c,5),b.fill(),b.shadowColor="rgba(0,0,0,0)",b.stroke(),b.fillStyle=a.boxcolor||LiteGraph.NODE_DEFAULT_BOXCOLOR,b.beginPath(),b.roundRect(0.5*a.size[0]-0.5*c,0.5*a.size[1]-0.5*c,c,c,2)):(b.beginPath(),b.rect(0,0,a.size[0],2*c),b.fill(),b.shadowColor="rgba(0,0,0,0)",b.stroke(),b.fillStyle=a.boxcolor||LiteGraph.NODE_DEFAULT_BOXCOLOR,b.beginPath(),b.rect(0.5*c,0.5*c,c,c));b.fill()}; +LGraphCanvas.prototype.drawConnections=function(a){var b=LiteGraph.getTime();a.lineWidth=this.connections_width;a.fillStyle="#AAA";a.strokeStyle="#AAA";a.globalAlpha=this.editor_alpha;for(var c=0,d=this.graph._nodes.length;cb-h._last_time&&(h=2-0.002*(b-h._last_time),k="rgba(255,255,255, "+h.toFixed(2)+")",this.renderLink(a,g,e.getConnectionPos(!0,f),k,!0,h))}}}}}a.globalAlpha=1}; +LGraphCanvas.prototype.renderLink=function(a,b,c,d,e,f){if(this.highquality_render){var g=distance(b,c);this.render_connections_border&&0.6this.scale)&&(this.render_connection_arrows&&0.6b[1]?0:Math.PI,a.save(),a.translate(d[0],d[1]),a.rotate(g),a.beginPath(),a.moveTo(-5,-5),a.lineTo(0,5),a.lineTo(5,-5),a.fill(),a.restore()),f))for(f= +0;5>f;++f)d=(0.001*LiteGraph.getTime()+0.2*f)%1,d=this.computeConnectionPoint(b,c,d),a.beginPath(),a.arc(d[0],d[1],5,0,2*Math.PI),a.fill()}else a.beginPath(),a.moveTo(b[0],b[1]),a.lineTo(c[0],c[1]),a.stroke()};LGraphCanvas.prototype.computeConnectionPoint=function(a,b,c){var d=distance(a,b),e=[a[0]+0.25*d,a[1]],d=[b[0]-0.25*d,b[1]],f=(1-c)*(1-c)*(1-c),g=3*(1-c)*(1-c)*c,h=3*(1-c)*c*c;c*=c*c;return[f*a[0]+g*e[0]+h*d[0]+c*b[0],f*a[1]+g*e[1]+h*d[1]+c*b[1]]}; LGraphCanvas.prototype.resize=function(a,b){if(!a&&!b){var c=this.canvas.parentNode;a=c.offsetWidth;b=c.offsetHeight}if(this.canvas.width!=a||this.canvas.height!=b)this.canvas.width=a,this.canvas.height=b,this.bgcanvas.width=this.canvas.width,this.bgcanvas.height=this.canvas.height,this.setDirty(!0,!0)}; LGraphCanvas.prototype.switchLiveMode=function(a){if(a){var b=this,c=this.live_mode?1.1:0.9;this.live_mode&&(this.live_mode=!1,this.editor_alpha=0.1);var d=setInterval(function(){b.editor_alpha*=c;b.dirty_canvas=!0;b.dirty_bgcanvas=!0;1>c&&0.01>b.editor_alpha&&(clearInterval(d),1>c&&(b.live_mode=!0));1"+h+""+(void 0!==a.properties[h]?a.properties[h]:" ")+"",value:h});if(g.length)return LiteGraph.createContextualMenu(g,{event:b,callback:d,from:c},f),!1}}; +LGraphCanvas.prototype.showEditPropertyValue=function(a,b,c){function d(){e(q.value)}function e(c){a.properties[b]="number"==typeof a.properties[b]?Number(c):c;l.parentNode.removeChild(l);a.setDirtyCanvas(!0,!0)}if(a&&void 0!==a.properties[b]){c=c||{};var f="string";null!==a.properties[b]&&(f=typeof a.properties[b]);var g=null;a.getPropertyInfo&&(g=a.getPropertyInfo(b));g.type&&(f=g.type);var h="";if("string"==f||"number"==f)h="";else if("enum"==f&&g.values){var h= +""}var l=document.createElement("div");l.className="graphdialog";l.innerHTML=""+b+""+h+"";if("enum"==f&&g.values){var q=l.querySelector("select");q.addEventListener("change",function(a){e(a.options[a.selectedIndex].value)})}else q=l.querySelector("input"), +q.value=void 0!==a.properties[b]?a.properties[b]:"",q.addEventListener("keydown",function(a){13==a.keyCode&&(d(),a.preventDefault(),a.stopPropagation())});f=this.canvas.getClientRects()[0];h=g=-20;f&&(g-=f.left,h-=f.top);c.event?(l.style.left=c.event.pageX+g+"px",l.style.top=c.event.pageY+h+"px"):(l.style.left=0.5*this.canvas.width+g+"px",l.style.top=0.5*this.canvas.height+h+"px");l.querySelector("button").addEventListener("click",d);this.canvas.parentNode.appendChild(l)}}; +LGraphCanvas.onMenuNodeCollapse=function(a){a.flags.collapsed=!a.flags.collapsed;a.setDirtyCanvas(!0,!0)};LGraphCanvas.onMenuNodePin=function(a){a.pin()};LGraphCanvas.onMenuNodeMode=function(a,b,c){LiteGraph.createContextualMenu(["Always","On Event","Never"],{event:b,callback:function(b){if(a)switch(b){case "On Event":a.mode=LiteGraph.ON_EVENT;break;case "Never":a.mode=LiteGraph.NEVER;break;default:a.mode=LiteGraph.ALWAYS}},from:c});return!1}; LGraphCanvas.onMenuNodeColors=function(a,b,c){var d=[],e;for(e in LGraphCanvas.node_colors){var f=LGraphCanvas.node_colors[e];d.push({value:e,content:""+e+""})}LiteGraph.createContextualMenu(d,{event:b,callback:function(b){a&&(b=LGraphCanvas.node_colors[b.value])&&(a.color=b.color,a.bgcolor=b.bgcolor,a.setDirtyCanvas(!0))},from:c});return!1}; LGraphCanvas.onMenuNodeShapes=function(a,b){LiteGraph.createContextualMenu(["box","round"],{event:b,callback:function(b){a&&(a.shape=b,a.setDirtyCanvas(!0))}});return!1};LGraphCanvas.onMenuNodeRemove=function(a){!1!=a.removable&&(a.graph.remove(a),a.setDirtyCanvas(!0,!0))};LGraphCanvas.onMenuNodeClone=function(a){if(!1!=a.clonable){var b=a.clone();b&&(b.pos=[a.pos[0]+5,a.pos[1]+5],a.graph.add(b),a.setDirtyCanvas(!0,!0))}}; LGraphCanvas.node_colors={red:{color:"#FAA",bgcolor:"#A44"},green:{color:"#AFA",bgcolor:"#4A4"},blue:{color:"#AAF",bgcolor:"#44A"},white:{color:"#FFF",bgcolor:"#AAA"}}; -LGraphCanvas.prototype.getCanvasMenuOptions=function(){var a=null;this.getMenuOptions?a=this.getMenuOptions():(a=[{content:"Add Node",is_menu:!0,callback:LGraphCanvas.onMenuAdd}],this._graph_stack&&0a&&db?!0:!1}function growBounding(a,b,c){ba[2]&&(a[2]=b);ca[3]&&(a[3]=c)} -function isInsideBounding(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0}function overlapBounding(a,b){return a[0]>b[2]||a[1]>b[3]||a[2]f;f+=2)d="0123456789ABCDEF".indexOf(a.charAt(f)),e="0123456789ABCDEF".indexOf(a.charAt(f+1)),b[c]=16*d+e,c++;return b} +LGraphCanvas.prototype.getCanvasMenuOptions=function(){var a=null;this.getMenuOptions?a=this.getMenuOptions():(a=[{content:"Add Node",is_menu:!0,callback:LGraphCanvas.onMenuAdd}],this._graph_stack&&0a&&db?!0:!1} +function growBounding(a,b,c){ba[2]&&(a[2]=b);ca[3]&&(a[3]=c)}function isInsideBounding(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0}function overlapBounding(a,b){return a[0]>b[2]||a[1]>b[3]||a[2]f;f+=2)d="0123456789ABCDEF".indexOf(a.charAt(f)),e="0123456789ABCDEF".indexOf(a.charAt(f+1)),b[c]=16*d+e,c++;return b} function num2hex(a){for(var b="#",c,d,e=0;3>e;e++)c=a[e]/16,d=a[e]%16,b+="0123456789ABCDEF".charAt(c)+"0123456789ABCDEF".charAt(d);return b} -LiteGraph.createContextualMenu=function(a,b,c){function d(a){var c=!0;b.callback&&(a=b.callback.call(g,this.data,a),void 0!==a&&(c=a));c&&LiteGraph.closeAllContextualMenus()}this.options=b=b||{};c=c||window;if(b.from){var e=document.querySelectorAll(".graphcontextualmenu"),f;for(f in e)e[f].previousSibling==b.from&&e[f].closeMenu()}else LiteGraph.closeAllContextualMenus();var g=c.document.createElement("div");g.className="graphcontextualmenu graphmenubar-panel";this.root=g;e=g.style;e.minWidth="100px"; -e.minHeight="20px";e.position="fixed";e.top="100px";e.left="100px";e.color="#AAF";e.padding="2px";e.borderBottom="2px solid #AAF";e.backgroundColor="#444";b.title&&(e=document.createElement("div"),e.className="graphcontextualmenu-title",e.innerHTML=b.title,g.appendChild(e));g.addEventListener("contextmenu",function(a){a.preventDefault();return!1});for(var h in a)f=a[h],e=c.document.createElement("div"),e.className="graphmenu-entry",null==f?e.className="graphmenu-entry separator":(f.is_menu&&(e.className+= -" submenu"),f.disabled&&(e.className+=" disabled"),e.style.cursor="pointer",e.dataset.value="string"==typeof f?f:f.value,e.data=f,e.innerHTML="string"==typeof f?a.constructor==Array?a[h]:h:f.content?f.content:h,e.addEventListener("click",d)),g.appendChild(e);g.addEventListener("mouseover",function(a){this.mouse_inside=!0});g.addEventListener("mouseout",function(a){for(a=a.relatedTarget||a.toElement;a!=this&&a!=c.document;)a=a.parentNode;a!=this&&(this.mouse_inside=!1,this.block_close||this.closeMenu())}); -c.document.body.appendChild(g);a=g.getClientRects()[0];b.from&&(b.from.block_close=!0);f=b.left||0;h=b.top||0;b.event&&(f=b.event.pageX-10,h=b.event.pageY-10,b.left&&(f=b.left),e=c.document.body.getClientRects()[0],b.from&&(f=b.from.getClientRects()[0],f=f.left+f.width),f>e.width-a.width-10&&(f=e.width-a.width-10),h>e.height-a.height-10&&(h=e.height-a.height-10));g.style.left=f+"px";g.style.top=h+"px";g.closeMenu=function(){b.from&&(b.from.block_close=!1,b.from.mouse_inside||b.from.closeMenu());this.parentNode&& -c.document.body.removeChild(this)};return g};LiteGraph.closeAllContextualMenus=function(){var a=document.querySelectorAll(".graphcontextualmenu");if(a.length){for(var b=[],c=0;ce.width-a.width-10&&(f=e.width-a.width-10),h>e.height-a.height-10&&(h=e.height-a.height-10));g.style.left=f+"px";g.style.top=h+"px";g.closeMenu=function(){b.from&&(b.from.block_close=!1,b.from.mouse_inside|| +b.from.closeMenu());this.parentNode&&c.document.body.removeChild(this)};return g};LiteGraph.closeAllContextualMenus=function(a){a=a||window;a=a.document.querySelectorAll(".graphcontextualmenu");if(a.length){for(var b=[],c=0;ca.canvasY-this.pos[1]||distance([a.canvasX,a.canvasY],[this.pos[0]+this.center[0],this.pos[1]+this.center[1]])>this.radius)return!1;this.oldmouse=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];this.captureInput(!0);return!0}};a.prototype.onMouseMove=function(a){if(this.oldmouse){a=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];var b=this.value,b=b-0.01*(a[1]-this.oldmouse[1]);1b&&(b=0);this.value=b;this.properties.value=this.properties.min+(this.properties.max- -this.properties.min)*this.value;this.oldmouse=a;this.setDirtyCanvas(!0)}};a.prototype.onMouseUp=function(a){this.oldmouse&&(this.oldmouse=null,this.captureInput(!1))};a.prototype.onMouseLeave=function(a){};a.prototype.onWidget=function(a,b){if("increase"==b.name)this.onPropertyChange("size",this.properties.size+10);else if("decrease"==b.name)this.onPropertyChange("size",this.properties.size-10)};a.prototype.onPropertyChange=function(a,b){if("wcolor"==a)this.properties[a]=b;else if("size"==a)b=parseInt(b), -this.properties[a]=b,this.size=[b+4,b+24],this.setDirtyCanvas(!0,!0);else if("min"==a||"max"==a||"value"==a)this.properties[a]=parseFloat(b);else return!1;return!0};LiteGraph.registerNodeType("widget/knob",a);b.title="H.Slider";b.desc="Linear slider controller";b.prototype.onInit=function(){this.value=0.5;this.imgfg=this.loadImage("imgs/slider_fg.png")};b.prototype.onDrawVectorial=function(a){this.imgfg&&this.imgfg.width&&(a.lineWidth=1,a.strokeStyle=this.mouseOver?"#FFF":"#AAA",a.fillStyle="#000", -a.beginPath(),a.rect(2,0,this.size[0]-4,20),a.stroke(),a.fillStyle=this.properties.wcolor,a.beginPath(),a.rect(2+(this.size[0]-4-20)*this.value,0,20,20),a.fill())};b.prototype.onDrawImage=function(a){this.imgfg&&this.imgfg.width&&(a.lineWidth=1,a.fillStyle="#000",a.fillRect(2,9,this.size[0]-4,2),a.strokeStyle="#333",a.beginPath(),a.moveTo(2,9),a.lineTo(this.size[0]-4,9),a.stroke(),a.strokeStyle="#AAA",a.beginPath(),a.moveTo(2,11),a.lineTo(this.size[0]-4,11),a.stroke(),a.drawImage(this.imgfg,2+(this.size[0]- -4)*this.value-0.5*this.imgfg.width,0.5*-this.imgfg.height+10))};b.prototype.onDrawForeground=function(a){this.onDrawImage(a)};b.prototype.onExecute=function(){this.properties.value=this.properties.min+(this.properties.max-this.properties.min)*this.value;this.setOutputData(0,this.properties.value);this.boxcolor=colorToString([this.value,this.value,this.value])};b.prototype.onMouseDown=function(a){if(0>a.canvasY-this.pos[1])return!1;this.oldmouse=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];this.captureInput(!0); -return!0};b.prototype.onMouseMove=function(a){if(this.oldmouse){a=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];var b=this.value,b=b+(a[0]-this.oldmouse[0])/this.size[0];1b&&(b=0);this.value=b;this.oldmouse=a;this.setDirtyCanvas(!0)}};b.prototype.onMouseUp=function(a){this.oldmouse=null;this.captureInput(!1)};b.prototype.onMouseLeave=function(a){};b.prototype.onPropertyChange=function(a,b){if("wcolor"==a)this.properties[a]=b;else return!1;return!0};LiteGraph.registerNodeType("widget/hslider", -b);c.title="Progress";c.desc="Shows data in linear progress";c.prototype.onExecute=function(){var a=this.getInputData(0);void 0!=a&&(this.properties.value=a)};c.prototype.onDrawForeground=function(a){a.lineWidth=1;a.fillStyle=this.properties.wcolor;var b=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min),b=Math.min(1,b),b=Math.max(0,b);a.fillRect(2,2,(this.size[0]-4)*b,this.size[1]-4)};LiteGraph.registerNodeType("widget/progress",c);d.title="Text";d.desc="Shows the input value"; -d.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"led_text",text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}];d.prototype.onDrawForeground=function(a){a.fillStyle=this.properties.color;var b=this.properties.value;this.properties.glowSize?(a.shadowColor=this.properties.color,a.shadowOffsetX=0,a.shadowOffsetY=0,a.shadowBlur=this.properties.glowSize):a.shadowColor="transparent";var c=this.properties.fontsize;a.textAlign=this.properties.align;a.font=c.toString()+ -"px "+this.properties.font;this.str="number"==typeof b?b.toFixed(this.properties.decimals):b;if("string"==typeof this.str){var b=this.str.split("\\n"),d;for(d in b)a.fillText(b[d],"left"==this.properties.align?15:this.size[0]-15,-0.15*c+c*(parseInt(d)+1))}a.shadowColor="transparent";this.last_ctx=a;a.textAlign="left"};d.prototype.onExecute=function(){var a=this.getInputData(0);this.properties.value=null!=a?a:"";this.setDirtyCanvas(!0)};d.prototype.resize=function(){if(this.last_ctx){var a=this.str.split("\\n"); -this.last_ctx.font=this.properties.fontsize+"px "+this.properties.font;var b=0,c;for(c in a){var d=this.last_ctx.measureText(a[c]).width;ba.canvasY-this.pos[1]||distance([a.canvasX,a.canvasY],[this.pos[0]+this.center[0],this.pos[1]+this.center[1]])>this.radius)return!1;this.oldmouse=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];this.captureInput(!0);return!0}};b.prototype.onMouseMove=function(a){if(this.oldmouse){a=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];var b=this.value,b=b-0.01*(a[1]-this.oldmouse[1]);1b&&(b=0);this.value=b;this.properties.value= +this.properties.min+(this.properties.max-this.properties.min)*this.value;this.oldmouse=a;this.setDirtyCanvas(!0)}};b.prototype.onMouseUp=function(a){this.oldmouse&&(this.oldmouse=null,this.captureInput(!1))};b.prototype.onMouseLeave=function(a){};b.prototype.onWidget=function(a,b){if("increase"==b.name)this.onPropertyChange("size",this.properties.size+10);else if("decrease"==b.name)this.onPropertyChange("size",this.properties.size-10)};b.prototype.onPropertyChange=function(a,b){if("wcolor"==a)this.properties[a]= +b;else if("size"==a)b=parseInt(b),this.properties[a]=b,this.size=[b+4,b+24],this.setDirtyCanvas(!0,!0);else if("min"==a||"max"==a||"value"==a)this.properties[a]=parseFloat(b);else return!1;return!0};LiteGraph.registerNodeType("widget/knob",b);c.title="H.Slider";c.desc="Linear slider controller";c.prototype.onInit=function(){this.value=0.5;this.imgfg=this.loadImage("imgs/slider_fg.png")};c.prototype.onDrawVectorial=function(a){this.imgfg&&this.imgfg.width&&(a.lineWidth=1,a.strokeStyle=this.mouseOver? +"#FFF":"#AAA",a.fillStyle="#000",a.beginPath(),a.rect(2,0,this.size[0]-4,20),a.stroke(),a.fillStyle=this.properties.wcolor,a.beginPath(),a.rect(2+(this.size[0]-4-20)*this.value,0,20,20),a.fill())};c.prototype.onDrawImage=function(a){this.imgfg&&this.imgfg.width&&(a.lineWidth=1,a.fillStyle="#000",a.fillRect(2,9,this.size[0]-4,2),a.strokeStyle="#333",a.beginPath(),a.moveTo(2,9),a.lineTo(this.size[0]-4,9),a.stroke(),a.strokeStyle="#AAA",a.beginPath(),a.moveTo(2,11),a.lineTo(this.size[0]-4,11),a.stroke(), +a.drawImage(this.imgfg,2+(this.size[0]-4)*this.value-0.5*this.imgfg.width,0.5*-this.imgfg.height+10))};c.prototype.onDrawForeground=function(a){this.onDrawImage(a)};c.prototype.onExecute=function(){this.properties.value=this.properties.min+(this.properties.max-this.properties.min)*this.value;this.setOutputData(0,this.properties.value);this.boxcolor=colorToString([this.value,this.value,this.value])};c.prototype.onMouseDown=function(a){if(0>a.canvasY-this.pos[1])return!1;this.oldmouse=[a.canvasX-this.pos[0], +a.canvasY-this.pos[1]];this.captureInput(!0);return!0};c.prototype.onMouseMove=function(a){if(this.oldmouse){a=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];var b=this.value,b=b+(a[0]-this.oldmouse[0])/this.size[0];1b&&(b=0);this.value=b;this.oldmouse=a;this.setDirtyCanvas(!0)}};c.prototype.onMouseUp=function(a){this.oldmouse=null;this.captureInput(!1)};c.prototype.onMouseLeave=function(a){};c.prototype.onPropertyChange=function(a,b){if("wcolor"==a)this.properties[a]=b;else return!1;return!0}; +LiteGraph.registerNodeType("widget/hslider",c);d.title="Progress";d.desc="Shows data in linear progress";d.prototype.onExecute=function(){var a=this.getInputData(0);void 0!=a&&(this.properties.value=a)};d.prototype.onDrawForeground=function(a){a.lineWidth=1;a.fillStyle=this.properties.wcolor;var b=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min),b=Math.min(1,b),b=Math.max(0,b);a.fillRect(2,2,(this.size[0]-4)*b,this.size[1]-4)};LiteGraph.registerNodeType("widget/progress", +d);e.title="Text";e.desc="Shows the input value";e.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"led_text",text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}];e.prototype.onDrawForeground=function(a){a.fillStyle=this.properties.color;var b=this.properties.value;this.properties.glowSize?(a.shadowColor=this.properties.color,a.shadowOffsetX=0,a.shadowOffsetY=0,a.shadowBlur=this.properties.glowSize):a.shadowColor="transparent";var c=this.properties.fontsize; +a.textAlign=this.properties.align;a.font=c.toString()+"px "+this.properties.font;this.str="number"==typeof b?b.toFixed(this.properties.decimals):b;if("string"==typeof this.str){var b=this.str.split("\\n"),d;for(d in b)a.fillText(b[d],"left"==this.properties.align?15:this.size[0]-15,-0.15*c+c*(parseInt(d)+1))}a.shadowColor="transparent";this.last_ctx=a;a.textAlign="left"};e.prototype.onExecute=function(){var a=this.getInputData(0);this.properties.value=null!=a?a:"";this.setDirtyCanvas(!0)};e.prototype.resize= +function(){if(this.last_ctx){var a=this.str.split("\\n");this.last_ctx.font=this.properties.fontsize+"px "+this.properties.font;var b=0,c;for(c in a){var d=this.last_ctx.measureText(a[c]).width;bd;d++)if(c[d]){a=c[d];c=this.xbox_mapping;c||(c=this.xbox_mapping= @@ -181,33 +201,38 @@ a.xbox.axes.ly;break;case "right_x_axis":e=a.xbox.axes.rx;break;case "right_y_ax a.buttons[d].pressed;break;case 7:c.buttons.rt=a.buttons[d].pressed;break;case 8:c.buttons.back=a.buttons[d].pressed;break;case 9:c.buttons.start=a.buttons[d].pressed;break;case 10:c.buttons.ls=a.buttons[d].pressed;break;case 11:c.buttons.rs=a.buttons[d].pressed;break;case 12:a.buttons[d].pressed&&(c.hat+="up");break;case 13:a.buttons[d].pressed&&(c.hat+="down");break;case 14:a.buttons[d].pressed&&(c.hat+="left");break;case 15:a.buttons[d].pressed&&(c.hat+="right");break;case 16:c.buttons.home=a.buttons[d].pressed}a.xbox= c;return a}};a.prototype.onDrawBackground=function(a){};a.prototype.onGetOutputs=function(){return[["left_axis","vec2"],["right_axis","vec2"],["left_x_axis","number"],["left_y_axis","number"],["right_x_axis","number"],["right_y_axis","number"],["trigger_left","number"],["trigger_right","number"],["a_button","number"],["b_button","number"],["x_button","number"],["y_button","number"],["lb_button","number"],["rb_button","number"],["ls_button","number"],["rs_button","number"],["start","number"],["back", "number"]]};LiteGraph.registerNodeType("input/gamepad",a)})(); -(function(){function a(){this.addInput("in","*");this.size=[60,20]}function b(){this.addInput("in");this.addOutput("out");this.size=[60,20]}function c(){this.addInput("in","number",{locked:!0});this.addOutput("out","number",{locked:!0});this.properties={"in":0,in_min:0,in_max:1,out_min:0,out_max:1}}function d(){this.addOutput("value","number");this.properties={min:0,max:1};this.size=[60,20]}function e(){this.addInput("in","number");this.addOutput("out","number");this.size=[60,20];this.properties= -{min:0,max:1}}function f(){this.addInput("in","number");this.addOutput("out","number");this.size=[60,20]}function g(){this.addInput("in","number");this.addOutput("out","number");this.size=[60,20]}function h(){this.addInput("in","number");this.addOutput("out","number");this.size=[60,20]}function k(){this.addInput("in","number");this.addOutput("out","number");this.size=[60,20];this.properties={A:0,B:1}}function l(){this.addInput("in","number",{label:""});this.addOutput("out","number",{label:""});this.size= -[60,20];this.properties={factor:1}}function n(){this.addInput("A","number");this.addInput("B","number");this.addOutput("=","number");this.properties={A:1,B:1,OP:"+"}}function q(){this.addInput("A","number");this.addInput("B","number");this.addOutput("A==B","boolean");this.addOutput("A!=B","boolean");this.properties={A:0,B:0}}function r(){this.addInput("A","number");this.addInput("B","number");this.addOutput("out","boolean");this.properties={A:0,B:1,OP:">"};this.size=[60,40]}function t(){this.addInput("inc", -"number");this.addOutput("total","number");this.properties={increment:0,value:0}}function s(){this.addInput("v","number");this.addOutput("sin","number");this.properties={amplitude:1,offset:0};this.bgImageUrl="nodes/imgs/icon-sin.png"}function u(){this.addInput("vec2","vec2");this.addOutput("x","number");this.addOutput("y","number")}function v(){this.addInputs([["x","number"],["y","number"]]);this.addOutput("vec2","vec2");this.properties={x:0,y:0};this._data=new Float32Array(2)}function w(){this.addInput("vec3", -"vec3");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z","number")}function x(){this.addInputs([["x","number"],["y","number"],["z","number"]]);this.addOutput("vec3","vec3");this.properties={x:0,y:0,z:0};this._data=new Float32Array(3)}function y(){this.addInput("vec4","vec4");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z","number");this.addOutput("w","number")}function z(){this.addInputs([["x","number"],["y","number"],["z","number"],["w","number"]]); -this.addOutput("vec4","vec4");this.properties={x:0,y:0,z:0,w:0};this._data=new Float32Array(4)}a.title="Converter";a.desc="type A to type B";a.prototype.onExecute=function(){var a=this.getInputData(0);if(null!=a&&this.outputs)for(var b=0;b","string",{values:s.values});this.size=[60,40]}function u(){this.addInput("inc","number");this.addOutput("total","number");this.addProperty("increment", +1);this.addProperty("value",0)}function t(){this.addInput("v","number");this.addOutput("sin","number");this.addProperty("amplitude",1);this.addProperty("offset",0);this.bgImageUrl="nodes/imgs/icon-sin.png"}function v(){this.addInput("vec2","vec2");this.addOutput("x","number");this.addOutput("y","number")}function w(){this.addInputs([["x","number"],["y","number"]]);this.addOutput("vec2","vec2");this.properties={x:0,y:0};this._data=new Float32Array(2)}function x(){this.addInput("vec3","vec3");this.addOutput("x", +"number");this.addOutput("y","number");this.addOutput("z","number")}function y(){this.addInputs([["x","number"],["y","number"],["z","number"]]);this.addOutput("vec3","vec3");this.properties={x:0,y:0,z:0};this._data=new Float32Array(3)}function z(){this.addInput("vec4","vec4");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z","number");this.addOutput("w","number")}function A(){this.addInputs([["x","number"],["y","number"],["z","number"],["w","number"]]);this.addOutput("vec4", +"vec4");this.properties={x:0,y:0,z:0,w:0};this._data=new Float32Array(4)}a.title="Converter";a.desc="type A to type B";a.prototype.onExecute=function(){var a=this.getInputData(0);if(null!=a&&this.outputs)for(var b=0;bB":value=a>b;break;case "A=B":value=a>=b}this.setOutputData(c,value)}}};q.prototype.onGetOutputs=function(){return[["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]]}; -LiteGraph.registerNodeType("math/compare",q);r["@OP"]={type:"enum",title:"operation",values:"> < == != <= >=".split(" ")};r.title="Condition";r.desc="evaluates condition between A and B";r.prototype.onExecute=function(){var a=this.getInputData(0);void 0===a?a=this.properties.A:this.properties.A=a;var b=this.getInputData(1);void 0===b?b=this.properties.B:this.properties.B=b;var c=!0;switch(this.properties.OP){case ">":c=a>b;break;case "<":c=a=":c=a>=b}this.setOutputData(0,c)};LiteGraph.registerNodeType("math/condition",r);t.title="Accumulate";t.desc="Increments a value every time";t.prototype.onExecute=function(){var a=this.getInputData(0);this.properties.value=null!==a?this.properties.value+a:this.properties.value+this.properties.increment;this.setOutputData(0,this.properties.value)};LiteGraph.registerNodeType("math/accumulate",t);s.title="Trigonometry";s.desc="Sin Cos Tan";s.filter="shader";s.prototype.onExecute=function(){var a= -this.getInputData(0),b=this.properties.amplitude,c=this.findInputSlot("amplitude");-1!=c&&(b=this.getInputData(c));var d=this.properties.offset,c=this.findInputSlot("offset");-1!=c&&(d=this.getInputData(c));for(var c=0,e=this.outputs.length;cb&&(this._current=0);for(var c=a=0;cb&&(b=1);this.properties.samples= +Math.round(b);var c=this._values;this._values=new Float32Array(this.properties.samples);c.length<=this._values.length?this._values.set(c):this._values.set(c.subarray(0,this._values.length))};LiteGraph.registerNodeType("math/average",l);q.values="+-*/%^".split("");q.title="Operation";q.desc="Easy math operators";q["@OP"]={type:"enum",title:"operation",values:q.values};q.prototype.setValue=function(a){"string"==typeof a&&(a=parseFloat(a));this.properties.value=a};q.prototype.onExecute=function(){var a= +this.getInputData(0),b=this.getInputData(1);null!=a?this.properties.A=a:a=this.properties.A;null!=b?this.properties.B=b:b=this.properties.B;var c=0;switch(this.properties.OP){case "+":c=a+b;break;case "-":c=a-b;break;case "x":case "X":case "*":c=a*b;break;case "/":c=a/b;break;case "%":c=a%b;break;case "^":c=Math.pow(a,b);break;default:console.warn("Unknown operation: "+this.properties.OP)}this.setOutputData(0,c)};q.prototype.onDrawBackground=function(a){this.flags.collapsed||(a.font="40px Arial", +a.fillStyle="black",a.textAlign="center",a.fillText(this.properties.OP,0.5*this.size[0],0.5*this.size[1]+LiteGraph.NODE_TITLE_HEIGHT),a.textAlign="left")};LiteGraph.registerNodeType("math/operation",q);r.title="Compare";r.desc="compares between two values";r.prototype.onExecute=function(){var a=this.getInputData(0),b=this.getInputData(1);void 0!==a?this.properties.A=a:a=this.properties.A;void 0!==b?this.properties.B=b:b=this.properties.B;for(var c=0,d=this.outputs.length;cB":value=a>b;break;case "A=B":value=a>=b}this.setOutputData(c,value)}}};r.prototype.onGetOutputs=function(){return[["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]]};LiteGraph.registerNodeType("math/compare",r);s.values="> < == != <= >=".split(" ");s["@OP"]={type:"enum",title:"operation", +values:s.values};s.title="Condition";s.desc="evaluates condition between A and B";s.prototype.onExecute=function(){var a=this.getInputData(0);void 0===a?a=this.properties.A:this.properties.A=a;var b=this.getInputData(1);void 0===b?b=this.properties.B:this.properties.B=b;var c=!0;switch(this.properties.OP){case ">":c=a>b;break;case "<":c=a=":c=a>=b}this.setOutputData(0,c)};LiteGraph.registerNodeType("math/condition", +s);u.title="Accumulate";u.desc="Increments a value every time";u.prototype.onExecute=function(){null===this.properties.value&&(this.properties.value=0);var a=this.getInputData(0);this.properties.value=null!==a?this.properties.value+a:this.properties.value+this.properties.increment;this.setOutputData(0,this.properties.value)};LiteGraph.registerNodeType("math/accumulate",u);t.title="Trigonometry";t.desc="Sin Cos Tan";t.filter="shader";t.prototype.onExecute=function(){var a=this.getInputData(0);null== +a&&(a=0);var b=this.properties.amplitude,c=this.findInputSlot("amplitude");-1!=c&&(b=this.getInputData(c));var d=this.properties.offset,c=this.findInputSlot("offset");-1!=c&&(d=this.getInputData(c));for(var c=0,e=this.outputs.length;cVec2";v.desc="components to vector2";v.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this._data;c[0]=a;c[1]=b;this.setOutputData(0,c)};LiteGraph.registerNodeType("math3d/xy-to-vec2", -v);w.title="Vec3->XYZ";w.desc="vector 3 to components";w.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]),this.setOutputData(2,a[2]))};LiteGraph.registerNodeType("math3d/vec3-to-xyz",w);x.title="XYZ->Vec3";x.desc="components to vector3";x.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this.getInputData(2);null==c&&(c=this.properties.z); -var d=this._data;d[0]=a;d[1]=b;d[2]=c;this.setOutputData(0,d)};LiteGraph.registerNodeType("math3d/xyz-to-vec3",x);y.title="Vec4->XYZW";y.desc="vector 4 to components";y.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]),this.setOutputData(2,a[2]),this.setOutputData(3,a[3]))};LiteGraph.registerNodeType("math3d/vec4-to-xyzw",y);z.title="XYZW->Vec4";z.desc="components to vector4";z.prototype.onExecute=function(){var a=this.getInputData(0); -null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this.getInputData(2);null==c&&(c=this.properties.z);var d=this.getInputData(3);null==d&&(d=this.properties.w);var e=this._data;e[0]=a;e[1]=b;e[2]=c;e[3]=d;this.setOutputData(0,e)};LiteGraph.registerNodeType("math3d/xyzw-to-vec4",z);window.glMatrix&&(p=function(){this.addInputs([["degrees","number"],["axis","vec3"]]);this.addOutput("quat","quat");this.properties={angle:90,axis:vec3.fromValues(0,1,0)}},p.title= -"Rotation",p.desc="quaternion rotation",p.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.angle);var b=this.getInputData(1);null==b&&(b=this.properties.axis);a=quat.setAxisAngle(quat.create(),b,0.0174532925*a);this.setOutputData(0,a)},LiteGraph.registerNodeType("math3d/rotation",p),p=function(){this.addInputs([["vec3","vec3"],["quat","quat"]]);this.addOutput("result","vec3");this.properties={vec:[0,0,1]}},p.title="Rot. Vec3",p.desc="rotate a point",p.prototype.onExecute= -function(){var a=this.getInputData(0);null==a&&(a=this.properties.vec);var b=this.getInputData(1);null==b?this.setOutputData(a):this.setOutputData(0,vec3.transformQuat(vec3.create(),a,b))},LiteGraph.registerNodeType("math3d/rotate_vec3",p),p=function(){this.addInputs([["A","quat"],["B","quat"]]);this.addOutput("A*B","quat")},p.title="Mult. Quat",p.desc="rotate quaternion",p.prototype.onExecute=function(){var a=this.getInputData(0);if(null!=a){var b=this.getInputData(1);null!=b&&(a=quat.multiply(quat.create(), -a,b),this.setOutputData(0,a))}},LiteGraph.registerNodeType("math3d/mult-quat",p))})();function Selector(){this.addInput("sel","boolean");this.addOutput("value","number");this.properties={A:0,B:1};this.size=[60,20]}Selector.title="Selector";Selector.desc="outputs A if selector is true, B if selector is false"; +p)}v.title="Vec2->XY";v.desc="vector 2 to components";v.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]))};LiteGraph.registerNodeType("math3d/vec2-to-xyz",v);w.title="XY->Vec2";w.desc="components to vector2";w.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this._data;c[0]=a;c[1]=b;this.setOutputData(0,c)};LiteGraph.registerNodeType("math3d/xy-to-vec2", +w);x.title="Vec3->XYZ";x.desc="vector 3 to components";x.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]),this.setOutputData(2,a[2]))};LiteGraph.registerNodeType("math3d/vec3-to-xyz",x);y.title="XYZ->Vec3";y.desc="components to vector3";y.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this.getInputData(2);null==c&&(c=this.properties.z); +var d=this._data;d[0]=a;d[1]=b;d[2]=c;this.setOutputData(0,d)};LiteGraph.registerNodeType("math3d/xyz-to-vec3",y);z.title="Vec4->XYZW";z.desc="vector 4 to components";z.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]),this.setOutputData(2,a[2]),this.setOutputData(3,a[3]))};LiteGraph.registerNodeType("math3d/vec4-to-xyzw",z);A.title="XYZW->Vec4";A.desc="components to vector4";A.prototype.onExecute=function(){var a=this.getInputData(0); +null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this.getInputData(2);null==c&&(c=this.properties.z);var d=this.getInputData(3);null==d&&(d=this.properties.w);var e=this._data;e[0]=a;e[1]=b;e[2]=c;e[3]=d;this.setOutputData(0,e)};LiteGraph.registerNodeType("math3d/xyzw-to-vec4",A);window.glMatrix&&(p=function(){this.addOutput("quat","quat");this.properties={x:0,y:0,z:0,w:1};this._value=quat.create()},p.title="Quaternion",p.desc="quaternion",p.prototype.onExecute= +function(){this._value[0]=this.properties.x;this._value[1]=this.properties.y;this._value[2]=this.properties.z;this._value[3]=this.properties.w;this.setOutputData(0,this._value)},LiteGraph.registerNodeType("math3d/quaternion",p),p=function(){this.addInputs([["degrees","number"],["axis","vec3"]]);this.addOutput("quat","quat");this.properties={angle:90,axis:vec3.fromValues(0,1,0)};this._value=quat.create()},p.title="Rotation",p.desc="quaternion rotation",p.prototype.onExecute=function(){var a=this.getInputData(0); +null==a&&(a=this.properties.angle);var b=this.getInputData(1);null==b&&(b=this.properties.axis);a=quat.setAxisAngle(this._value,b,0.0174532925*a);this.setOutputData(0,a)},LiteGraph.registerNodeType("math3d/rotation",p),p=function(){this.addInputs([["vec3","vec3"],["quat","quat"]]);this.addOutput("result","vec3");this.properties={vec:[0,0,1]}},p.title="Rot. Vec3",p.desc="rotate a point",p.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.vec);var b=this.getInputData(1); +null==b?this.setOutputData(a):this.setOutputData(0,vec3.transformQuat(vec3.create(),a,b))},LiteGraph.registerNodeType("math3d/rotate_vec3",p),p=function(){this.addInputs([["A","quat"],["B","quat"]]);this.addOutput("A*B","quat");this._value=quat.create()},p.title="Mult. Quat",p.desc="rotate quaternion",p.prototype.onExecute=function(){var a=this.getInputData(0);if(null!=a){var b=this.getInputData(1);null!=b&&(a=quat.multiply(this._value,a,b),this.setOutputData(0,a))}},LiteGraph.registerNodeType("math3d/mult-quat", +p),p=function(){this.addInputs([["A","quat"],["B","quat"],["factor","number"]]);this.addOutput("slerp","quat");this.addProperty("factor",0.5);this._value=quat.create()},p.title="Quat Slerp",p.desc="quaternion spherical interpolation",p.prototype.onExecute=function(){var a=this.getInputData(0);if(null!=a){var b=this.getInputData(1);if(null!=b){var c=this.properties.factor;null!=this.getInputData(2)&&(c=this.getInputData(2));a=quat.slerp(this._value,a,b,c);this.setOutputData(0,a)}}},LiteGraph.registerNodeType("math3d/quat-slerp", +p))})();function Selector(){this.addInput("sel","boolean");this.addOutput("value","number");this.properties={A:0,B:1};this.size=[60,20]}Selector.title="Selector";Selector.desc="outputs A if selector is true, B if selector is false"; Selector.prototype.onExecute=function(){var a=this.getInputData(0);if(void 0!==a){for(var b=1;b=this.size[1]))if(this._drop_texture&&a.webgl)a.drawImage(this._drop_texture,0,0,this.size[0],this.size[1]);else{if(this._last_preview_tex!=this._last_tex)if(a.webgl)this._canvas=this._last_tex;else{var b=LGraphTexture.generateLowResTexturePreview(this._last_tex);if(!b)return;this._last_preview_tex=this._last_tex; this._canvas=cloneCanvas(b)}this._canvas&&(a.save(),a.webgl||(a.translate(0,this.size[1]),a.scale(1,-1)),a.drawImage(this._canvas,0,0,this.size[0],this.size[1]),a.restore())}};LGraphTexture.generateLowResTexturePreview=function(a){if(!a)return null;var b=LGraphTexture.image_preview_size,c=a;if(a.format==gl.DEPTH_COMPONENT)return null;if(a.width>b||a.height>b)c=this._preview_temp_tex,this._preview_temp_tex||(this._preview_temp_tex=c=new GL.Texture(b,b,{minFilter:gl.NEAREST})),a.copyTo(c);a=this._preview_canvas; a||(this._preview_canvas=a=createCanvas(b,b));c&&c.toCanvas(a);return a};LGraphTexture.prototype.onGetInputs=function(){return[["in","Texture"]]};LGraphTexture.prototype.onGetOutputs=function(){return[["width","number"],["height","number"],["aspect","number"]]};LiteGraph.registerNodeType("texture/texture",LGraphTexture);var LGraphTexturePreview=function(){this.addInput("Texture","Texture");this.properties={flipY:!1};this.size=[LGraphTexture.image_preview_size,LGraphTexture.image_preview_size]};LGraphTexturePreview.title= -"Preview";LGraphTexturePreview.desc="Show a texture in the graph canvas";LGraphTexturePreview.prototype.onDrawBackground=function(a){if(!this.flags.collapsed){var b=this.getInputData(0);if(b){var c=null,c=!b.handle&&a.webgl?b:LGraphTexture.generateLowResTexturePreview(b);a.save();this.properties.flipY&&(a.translate(0,this.size[1]),a.scale(1,-1));a.drawImage(c,0,0,this.size[0],this.size[1]);a.restore()}}};LiteGraph.registerNodeType("texture/preview",LGraphTexturePreview);var LGraphTextureSave=function(){this.addInput("Texture", -"Texture");this.addOutput("","Texture");this.properties={name:""}};LGraphTextureSave.title="Save";LGraphTextureSave.desc="Save a texture in the repository";LGraphTextureSave.prototype.onExecute=function(){var a=this.getInputData(0);a&&(this.properties.name&&(LGraphTexture.getTexturesContainer()[this.properties.name]=a),this.setOutputData(0,a))};LiteGraph.registerNodeType("texture/save",LGraphTextureSave);var LGraphTextureOperation=function(){this.addInput("Texture","Texture");this.addInput("TextureB", -"Texture");this.addInput("value","number");this.addOutput("Texture","Texture");this.help="

pixelcode must be vec3

\t\t\t

uvcode must be vec2, is optional

\t\t\t

uv: tex. coords

color: texture

colorB: textureB

time: scene time

value: input value

";this.properties={value:1,uvcode:"",pixelcode:"color + colorB * value",precision:LGraphTexture.DEFAULT}};LGraphTextureOperation.widgets_info= -{uvcode:{widget:"textarea",height:100},pixelcode:{widget:"textarea",height:100},precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphTextureOperation.title="Operation";LGraphTextureOperation.desc="Texture shader operation";LGraphTextureOperation.prototype.getExtraMenuOptions=function(a){var b=this;return[{content:b.properties.show?"Hide Texture":"Show Texture",callback:function(){b.properties.show=!b.properties.show}}]};LGraphTextureOperation.prototype.onDrawBackground=function(a){this.flags.collapsed|| -20>=this.size[1]||!this.properties.show||!this._tex||this._tex.gl!=a||(a.save(),a.drawImage(this._tex,0,0,this.size[0],this.size[1]),a.restore())};LGraphTextureOperation.prototype.onExecute=function(){var a=this.getInputData(0);if(this.isOutputConnected(0))if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,a);else{var b=this.getInputData(1);if(this.properties.uvcode||this.properties.pixelcode){var c=512,d=512;a?(c=a.width,d=a.height):b&&(c=b.width,d=b.height);this._tex= -a||this._tex?LGraphTexture.getTargetTexture(a||this._tex,this._tex,this.properties.precision):new GL.Texture(c,d,{type:this.precision===LGraphTexture.LOW?gl.UNSIGNED_BYTE:gl.HIGH_PRECISION_FORMAT,format:gl.RGBA,filter:gl.LINEAR});var e="";this.properties.uvcode&&(e="uv = "+this.properties.uvcode,-1!=this.properties.uvcode.indexOf(";")&&(e=this.properties.uvcode));var f="";this.properties.pixelcode&&(f="result = "+this.properties.pixelcode,-1!=this.properties.pixelcode.indexOf(";")&&(f=this.properties.pixelcode)); -var g=this._shader;if(!g||this._shader_code!=e+"|"+f){try{this._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureOperation.pixel_shader,{UV_CODE:e,PIXEL_CODE:f}),this.boxcolor="#00FF00"}catch(h){console.log("Error compiling shader: ",h);this.boxcolor="#FF0000";return}this._shader_code=e+"|"+f;g=this._shader}if(g){this.boxcolor="green";var k=this.getInputData(2);null!=k?this.properties.value=k:k=parseFloat(this.properties.value);var l=this.graph.getTime();this._tex.drawTo(function(){gl.disable(gl.DEPTH_TEST); -gl.disable(gl.CULL_FACE);gl.disable(gl.BLEND);a&&a.bind(0);b&&b.bind(1);var e=Mesh.getScreenQuad();g.uniforms({u_texture:0,u_textureB:1,value:k,texSize:[c,d],time:l}).draw(e)});this.setOutputData(0,this._tex)}else this.boxcolor="red"}}};LGraphTextureOperation.pixel_shader="precision highp float;\n\t\t\t\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform sampler2D u_textureB;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform vec2 texSize;\n\t\t\tuniform float time;\n\t\t\tuniform float value;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec2 uv = v_coord;\n\t\t\t\tUV_CODE;\n\t\t\t\tvec3 color = texture2D(u_texture, uv).rgb;\n\t\t\t\tvec3 colorB = texture2D(u_textureB, uv).rgb;\n\t\t\t\tvec3 result = color;\n\t\t\t\tfloat alpha = 1.0;\n\t\t\t\tPIXEL_CODE;\n\t\t\t\tgl_FragColor = vec4(result, alpha);\n\t\t\t}\n\t\t\t"; +"Preview";LGraphTexturePreview.desc="Show a texture in the graph canvas";LGraphTexturePreview.prototype.onDrawBackground=function(a){if(!this.flags.collapsed&&a.webgl){var b=this.getInputData(0);if(b){var c=null,c=!b.handle&&a.webgl?b:LGraphTexture.generateLowResTexturePreview(b);a.save();this.properties.flipY&&(a.translate(0,this.size[1]),a.scale(1,-1));a.drawImage(c,0,0,this.size[0],this.size[1]);a.restore()}}};LiteGraph.registerNodeType("texture/preview",LGraphTexturePreview);var LGraphTextureSave= +function(){this.addInput("Texture","Texture");this.addOutput("","Texture");this.properties={name:""}};LGraphTextureSave.title="Save";LGraphTextureSave.desc="Save a texture in the repository";LGraphTextureSave.prototype.onExecute=function(){var a=this.getInputData(0);a&&(this.properties.name&&(LGraphTexture.getTexturesContainer()[this.properties.name]=a),this.setOutputData(0,a))};LiteGraph.registerNodeType("texture/save",LGraphTextureSave);var LGraphTextureOperation=function(){this.addInput("Texture", +"Texture");this.addInput("TextureB","Texture");this.addInput("value","number");this.addOutput("Texture","Texture");this.help="

pixelcode must be vec3

\t\t\t

uvcode must be vec2, is optional

\t\t\t

uv: tex. coords

color: texture

colorB: textureB

time: scene time

value: input value

";this.properties={value:1,uvcode:"",pixelcode:"color + colorB * value",precision:LGraphTexture.DEFAULT}}; +LGraphTextureOperation.widgets_info={uvcode:{widget:"textarea",height:100},pixelcode:{widget:"textarea",height:100},precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphTextureOperation.title="Operation";LGraphTextureOperation.desc="Texture shader operation";LGraphTextureOperation.prototype.getExtraMenuOptions=function(a){var b=this;return[{content:b.properties.show?"Hide Texture":"Show Texture",callback:function(){b.properties.show=!b.properties.show}}]};LGraphTextureOperation.prototype.onDrawBackground= +function(a){this.flags.collapsed||20>=this.size[1]||!this.properties.show||!this._tex||this._tex.gl!=a||(a.save(),a.drawImage(this._tex,0,0,this.size[0],this.size[1]),a.restore())};LGraphTextureOperation.prototype.onExecute=function(){var a=this.getInputData(0);if(this.isOutputConnected(0))if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,a);else{var b=this.getInputData(1);if(this.properties.uvcode||this.properties.pixelcode){var c=512,d=512;a?(c=a.width,d=a.height):b&& +(c=b.width,d=b.height);this._tex=a||this._tex?LGraphTexture.getTargetTexture(a||this._tex,this._tex,this.properties.precision):new GL.Texture(c,d,{type:this.precision===LGraphTexture.LOW?gl.UNSIGNED_BYTE:gl.HIGH_PRECISION_FORMAT,format:gl.RGBA,filter:gl.LINEAR});var e="";this.properties.uvcode&&(e="uv = "+this.properties.uvcode,-1!=this.properties.uvcode.indexOf(";")&&(e=this.properties.uvcode));var f="";this.properties.pixelcode&&(f="result = "+this.properties.pixelcode,-1!=this.properties.pixelcode.indexOf(";")&& +(f=this.properties.pixelcode));var g=this._shader;if(!g||this._shader_code!=e+"|"+f){try{this._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureOperation.pixel_shader,{UV_CODE:e,PIXEL_CODE:f}),this.boxcolor="#00FF00"}catch(h){console.log("Error compiling shader: ",h);this.boxcolor="#FF0000";return}this._shader_code=e+"|"+f;g=this._shader}if(g){this.boxcolor="green";var k=this.getInputData(2);null!=k?this.properties.value=k:k=parseFloat(this.properties.value);var n=this.graph.getTime(); +this._tex.drawTo(function(){gl.disable(gl.DEPTH_TEST);gl.disable(gl.CULL_FACE);gl.disable(gl.BLEND);a&&a.bind(0);b&&b.bind(1);var e=Mesh.getScreenQuad();g.uniforms({u_texture:0,u_textureB:1,value:k,texSize:[c,d],time:n}).draw(e)});this.setOutputData(0,this._tex)}else this.boxcolor="red"}}};LGraphTextureOperation.pixel_shader="precision highp float;\n\t\t\t\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform sampler2D u_textureB;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform vec2 texSize;\n\t\t\tuniform float time;\n\t\t\tuniform float value;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec2 uv = v_coord;\n\t\t\t\tUV_CODE;\n\t\t\t\tvec3 color = texture2D(u_texture, uv).rgb;\n\t\t\t\tvec3 colorB = texture2D(u_textureB, uv).rgb;\n\t\t\t\tvec3 result = color;\n\t\t\t\tfloat alpha = 1.0;\n\t\t\t\tPIXEL_CODE;\n\t\t\t\tgl_FragColor = vec4(result, alpha);\n\t\t\t}\n\t\t\t"; LiteGraph.registerNodeType("texture/operation",LGraphTextureOperation);var LGraphTextureShader=function(){this.addOutput("Texture","Texture");this.properties={code:"",width:512,height:512};this.properties.code="\nvoid main() {\n vec2 uv = coord;\n vec3 color = vec3(0.0);\n//your code here\n\ngl_FragColor = vec4(color, 1.0);\n}\n"};LGraphTextureShader.title="Shader";LGraphTextureShader.desc="Texture shader";LGraphTextureShader.widgets_info={code:{type:"code"},precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}}; LGraphTextureShader.prototype.onExecute=function(){if(this.isOutputConnected(0)){if(this._shader_code!=this.properties.code)if(this._shader_code=this.properties.code,this._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureShader.pixel_shader+this.properties.code))this.boxcolor="green";else{this.boxcolor="red";return}this._tex&&this._tex.width==this.properties.width&&this._tex.height==this.properties.height||(this._tex=new GL.Texture(this.properties.width,this.properties.height,{format:gl.RGBA, filter:gl.LINEAR}));var a=this._tex,b=this._shader,c=this.graph.getTime();a.drawTo(function(){b.uniforms({texSize:[a.width,a.height],time:c}).draw(Mesh.getScreenQuad())});this.setOutputData(0,this._tex)}};LGraphTextureShader.pixel_shader="precision highp float;\n\t\t\t\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform float time;\n\t\t\t";LiteGraph.registerNodeType("texture/shader",LGraphTextureShader);var LGraphTextureScaleOffset=function(){this.addInput("in","Texture");this.addInput("scale","vec2");this.addInput("offset", @@ -254,9 +279,10 @@ f[0],this.properties.scale[1]=f[1]):f=this.properties.scale;var g=this.getInputD LiteGraph.registerNodeType("texture/scaleOffset",LGraphTextureScaleOffset);var LGraphTextureWarp=function(){this.addInput("in","Texture");this.addInput("warp","Texture");this.addInput("factor","number");this.addOutput("out","Texture");this.properties={factor:0.01,precision:LGraphTexture.DEFAULT}};LGraphTextureWarp.widgets_info={precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphTextureWarp.title="Warp";LGraphTextureWarp.desc="Texture warp operation";LGraphTextureWarp.prototype.onExecute= function(){var a=this.getInputData(0);if(this.isOutputConnected(0))if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,a);else{var b=this.getInputData(1),c=512,d=512;a?(c=a.width,d=a.height):b&&(c=b.width,d=b.height);this._tex=a||this._tex?LGraphTexture.getTargetTexture(a||this._tex,this._tex,this.properties.precision):new GL.Texture(c,d,{type:this.precision===LGraphTexture.LOW?gl.UNSIGNED_BYTE:gl.HIGH_PRECISION_FORMAT,format:gl.RGBA,filter:gl.LINEAR});var e=this._shader; e||(e=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,LGraphTextureWarp.pixel_shader));var f=this.getInputData(2);null!=f?this.properties.factor=f:f=parseFloat(this.properties.factor);this._tex.drawTo(function(){gl.disable(gl.DEPTH_TEST);gl.disable(gl.CULL_FACE);gl.disable(gl.BLEND);a&&a.bind(0);b&&b.bind(1);var c=Mesh.getScreenQuad();e.uniforms({u_texture:0,u_textureB:1,u_factor:f}).draw(c)});this.setOutputData(0,this._tex)}};LGraphTextureWarp.pixel_shader="precision highp float;\n\t\t\t\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform sampler2D u_textureB;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform float u_factor;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec2 uv = v_coord;\n\t\t\t\tuv += texture2D(u_textureB, uv).rg * u_factor;\n\t\t\t\tgl_FragColor = texture2D(u_texture, uv);\n\t\t\t}\n\t\t\t"; -LiteGraph.registerNodeType("texture/warp",LGraphTextureWarp);var LGraphTextureToViewport=function(){this.addInput("Texture","Texture");this.properties={additive:!1,antialiasing:!1,disable_alpha:!1,gamma:1};this.size[0]=130};LGraphTextureToViewport.title="to Viewport";LGraphTextureToViewport.desc="Texture to viewport";LGraphTextureToViewport.prototype.onExecute=function(){var a=this.getInputData(0);if(a){this.properties.disable_alpha?gl.disable(gl.BLEND):(gl.enable(gl.BLEND),this.properties.additive? -gl.blendFunc(gl.SRC_ALPHA,gl.ONE):gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA));gl.disable(gl.DEPTH_TEST);var b=this.properties.gamma||1;this.isInputConnected(1)&&(b=this.getInputData(1));if(this.properties.antialiasing){LGraphTextureToViewport._shader||(LGraphTextureToViewport._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,LGraphTextureToViewport.aa_pixel_shader));gl.getViewport();var c=Mesh.getScreenQuad();a.bind(0);LGraphTextureToViewport._shader.uniforms({u_texture:0,uViewportSize:[a.width, -a.height],u_igamma:1/b,inverseVP:[1/a.width,1/a.height]}).draw(c)}else 1!=b?(LGraphTextureToViewport._gamma_shader||(LGraphTextureToViewport._gamma_shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureToViewport.gamma_pixel_shader)),a.toViewport(LGraphTextureToViewport._gamma_shader,{u_texture:0,u_igamma:1/b})):a.toViewport()}};LGraphTextureToViewport.prototype.onGetInputs=function(){return[["gamma","number"]]};LGraphTextureToViewport.aa_pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform vec2 uViewportSize;\n\t\t\tuniform vec2 inverseVP;\n\t\t\tuniform float u_igamma;\n\t\t\t#define FXAA_REDUCE_MIN (1.0/ 128.0)\n\t\t\t#define FXAA_REDUCE_MUL (1.0 / 8.0)\n\t\t\t#define FXAA_SPAN_MAX 8.0\n\t\t\t\n\t\t\t/* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\t\t\tvec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\t\t\t{\n\t\t\t\tvec4 color = vec4(0.0);\n\t\t\t\t/*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\t\t\t\tvec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\t\t\t\tvec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\t\t\t\tvec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\t\t\t\tvec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\t\t\t\tvec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\t\t\t\tvec3 luma = vec3(0.299, 0.587, 0.114);\n\t\t\t\tfloat lumaNW = dot(rgbNW, luma);\n\t\t\t\tfloat lumaNE = dot(rgbNE, luma);\n\t\t\t\tfloat lumaSW = dot(rgbSW, luma);\n\t\t\t\tfloat lumaSE = dot(rgbSE, luma);\n\t\t\t\tfloat lumaM = dot(rgbM, luma);\n\t\t\t\tfloat lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\t\t\t\tfloat lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\t\t\t\t\n\t\t\t\tvec2 dir;\n\t\t\t\tdir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\t\t\t\tdir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\t\t\t\t\n\t\t\t\tfloat dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\t\t\t\t\n\t\t\t\tfloat rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\t\t\t\tdir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\t\t\t\t\n\t\t\t\tvec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\t\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\t\t\t\tvec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\t\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\t\t\t\t\n\t\t\t\t//return vec4(rgbA,1.0);\n\t\t\t\tfloat lumaB = dot(rgbB, luma);\n\t\t\t\tif ((lumaB < lumaMin) || (lumaB > lumaMax))\n\t\t\t\t\tcolor = vec4(rgbA, 1.0);\n\t\t\t\telse\n\t\t\t\t\tcolor = vec4(rgbB, 1.0);\n\t\t\t\tif(u_igamma != 1.0)\n\t\t\t\t\tcolor.xyz = pow( color.xyz, vec3(u_igamma) );\n\t\t\t\treturn color;\n\t\t\t}\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\t\t\t}\n\t\t\t"; +LiteGraph.registerNodeType("texture/warp",LGraphTextureWarp);var LGraphTextureToViewport=function(){this.addInput("Texture","Texture");this.properties={additive:!1,antialiasing:!1,filter:!0,disable_alpha:!1,gamma:1};this.size[0]=130};LGraphTextureToViewport.title="to Viewport";LGraphTextureToViewport.desc="Texture to viewport";LGraphTextureToViewport.prototype.onExecute=function(){var a=this.getInputData(0);if(a){this.properties.disable_alpha?gl.disable(gl.BLEND):(gl.enable(gl.BLEND),this.properties.additive? +gl.blendFunc(gl.SRC_ALPHA,gl.ONE):gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA));gl.disable(gl.DEPTH_TEST);var b=this.properties.gamma||1;this.isInputConnected(1)&&(b=this.getInputData(1));a.setParameter(gl.TEXTURE_MAG_FILTER,this.properties.filter?gl.LINEAR:gl.NEAREST);if(this.properties.antialiasing){LGraphTextureToViewport._shader||(LGraphTextureToViewport._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,LGraphTextureToViewport.aa_pixel_shader));gl.getViewport();var c=Mesh.getScreenQuad(); +a.bind(0);LGraphTextureToViewport._shader.uniforms({u_texture:0,uViewportSize:[a.width,a.height],u_igamma:1/b,inverseVP:[1/a.width,1/a.height]}).draw(c)}else 1!=b?(LGraphTextureToViewport._gamma_shader||(LGraphTextureToViewport._gamma_shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureToViewport.gamma_pixel_shader)),a.toViewport(LGraphTextureToViewport._gamma_shader,{u_texture:0,u_igamma:1/b})):a.toViewport()}};LGraphTextureToViewport.prototype.onGetInputs=function(){return[["gamma","number"]]}; +LGraphTextureToViewport.aa_pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform vec2 uViewportSize;\n\t\t\tuniform vec2 inverseVP;\n\t\t\tuniform float u_igamma;\n\t\t\t#define FXAA_REDUCE_MIN (1.0/ 128.0)\n\t\t\t#define FXAA_REDUCE_MUL (1.0 / 8.0)\n\t\t\t#define FXAA_SPAN_MAX 8.0\n\t\t\t\n\t\t\t/* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\t\t\tvec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\t\t\t{\n\t\t\t\tvec4 color = vec4(0.0);\n\t\t\t\t/*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\t\t\t\tvec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\t\t\t\tvec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\t\t\t\tvec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\t\t\t\tvec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\t\t\t\tvec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\t\t\t\tvec3 luma = vec3(0.299, 0.587, 0.114);\n\t\t\t\tfloat lumaNW = dot(rgbNW, luma);\n\t\t\t\tfloat lumaNE = dot(rgbNE, luma);\n\t\t\t\tfloat lumaSW = dot(rgbSW, luma);\n\t\t\t\tfloat lumaSE = dot(rgbSE, luma);\n\t\t\t\tfloat lumaM = dot(rgbM, luma);\n\t\t\t\tfloat lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\t\t\t\tfloat lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\t\t\t\t\n\t\t\t\tvec2 dir;\n\t\t\t\tdir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\t\t\t\tdir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\t\t\t\t\n\t\t\t\tfloat dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\t\t\t\t\n\t\t\t\tfloat rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\t\t\t\tdir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\t\t\t\t\n\t\t\t\tvec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\t\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\t\t\t\tvec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\t\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\t\t\t\t\n\t\t\t\t//return vec4(rgbA,1.0);\n\t\t\t\tfloat lumaB = dot(rgbB, luma);\n\t\t\t\tif ((lumaB < lumaMin) || (lumaB > lumaMax))\n\t\t\t\t\tcolor = vec4(rgbA, 1.0);\n\t\t\t\telse\n\t\t\t\t\tcolor = vec4(rgbB, 1.0);\n\t\t\t\tif(u_igamma != 1.0)\n\t\t\t\t\tcolor.xyz = pow( color.xyz, vec3(u_igamma) );\n\t\t\t\treturn color;\n\t\t\t}\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\t\t\t}\n\t\t\t"; LGraphTextureToViewport.gamma_pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform float u_igamma;\n\t\t\tvoid main() {\n\t\t\t\tvec4 color = texture2D( u_texture, v_coord);\n\t\t\t\tcolor.xyz = pow(color.xyz, vec3(u_igamma) );\n\t\t\t gl_FragColor = color;\n\t\t\t}\n\t\t\t";LiteGraph.registerNodeType("texture/toviewport",LGraphTextureToViewport);var LGraphTextureCopy=function(){this.addInput("Texture", "Texture");this.addOutput("","Texture");this.properties={size:0,generate_mipmaps:!1,precision:LGraphTexture.DEFAULT}};LGraphTextureCopy.title="Copy";LGraphTextureCopy.desc="Copy Texture";LGraphTextureCopy.widgets_info={size:{widget:"combo",values:[0,32,64,128,256,512,1024,2048]},precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphTextureCopy.prototype.onExecute=function(){var a=this.getInputData(0);if((a||this._temp_texture)&&this.isOutputConnected(0)){if(a){var b=a.width,c=a.height; 0!=this.properties.size&&(c=b=this.properties.size);var d=this._temp_texture,e=a.type;this.properties.precision===LGraphTexture.LOW?e=gl.UNSIGNED_BYTE:this.properties.precision===LGraphTexture.HIGH&&(e=gl.HIGH_PRECISION_FORMAT);d&&d.width==b&&d.height==c&&d.type==e||(d=gl.LINEAR,this.properties.generate_mipmaps&&isPowerOfTwo(b)&&isPowerOfTwo(c)&&(d=gl.LINEAR_MIPMAP_LINEAR),this._temp_texture=new GL.Texture(b,c,{type:e,format:gl.RGBA,minFilter:d,magFilter:gl.LINEAR}));a.copyTo(this._temp_texture); @@ -290,8 +316,8 @@ this.isInputConnected(1)&&(c=this.getInputData(1),this.properties.distance=c);va this._temp_texture)}}};LGraphTextureDepthRange.pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform vec2 u_camera_planes;\n\t\t\tuniform float u_distance;\n\t\t\tuniform float u_range;\n\t\t\t\n\t\t\tfloat LinearDepth()\n\t\t\t{\n\t\t\t\tfloat n = u_camera_planes.x;\n\t\t\t\tfloat f = u_camera_planes.y;\n\t\t\t\treturn (2.0 * n) / (f + n - texture2D(u_texture, v_coord).x * (f - n));\n\t\t\t}\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tfloat diff = abs(LinearDepth() * u_camera_planes.y - u_distance);\n\t\t\t\tfloat dof = 1.0;\n\t\t\t\tif(diff <= u_range)\n\t\t\t\t\tdof = diff / u_range;\n\t\t\t gl_FragColor = vec4(dof);\n\t\t\t}\n\t\t\t"; LiteGraph.registerNodeType("texture/depth_range",LGraphTextureDepthRange);var LGraphTextureBlur=function(){this.addInput("Texture","Texture");this.addInput("Iterations","number");this.addInput("Intensity","number");this.addOutput("Blurred","Texture");this.properties={intensity:1,iterations:1,preserve_aspect:!1,scale:[1,1]};LGraphTextureBlur._shader||(LGraphTextureBlur._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureBlur.pixel_shader))};LGraphTextureBlur.title="Blur";LGraphTextureBlur.desc= "Blur a texture";LGraphTextureBlur.max_iterations=20;LGraphTextureBlur.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isOutputConnected(0)){var b=this._temp_texture;b&&b.width==a.width&&b.height==a.height&&b.type==a.type||(this._temp_texture=new GL.Texture(a.width,a.height,{type:a.type,format:gl.RGBA,filter:gl.LINEAR}),this._final_texture=new GL.Texture(a.width,a.height,{type:a.type,format:gl.RGBA,filter:gl.LINEAR}));b=this.properties.iterations;this.isInputConnected(1)&&(b= -this.getInputData(1),this.properties.iterations=b);b=Math.min(Math.floor(b),LGraphTextureBlur.max_iterations);if(0==b)this.setOutputData(0,a);else{var c=this.properties.intensity;this.isInputConnected(2)&&(c=this.getInputData(2),this.properties.intensity=c);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var d=Mesh.getScreenQuad(),e=LGraphTextureBlur._shader,f=this.properties.scale||[1,1],g=LiteGraph.camera_aspect;g||void 0===window.gl||(g=gl.canvas.height/gl.canvas.width);g||(g=1);for(var h=a,g=this.properties.preserve_aspect? -g:1,a=0;a=this.size[1]||!this._video||(a.save(),a.webgl?this._temp_texture&&a.drawImage(this._temp_texture,0,0,this.size[0],this.size[1]):(a.translate(0,this.size[1]),a.scale(1,-1),a.drawImage(this._video,0,0,this.size[0],this.size[1])),a.restore())};LGraphTextureWebcam.prototype.onExecute=function(){null!=this._webcam_stream||this._waiting_confirmation||this.openStream();if(this._video&& @@ -304,8 +330,8 @@ LGraphFXLens.widgets_info={precision:{widget:"combo",values:LGraphTexture.MODE_V LiteGraph.registerNodeType("fx/lens",LGraphFXLens);window.LGraphFXLens=LGraphFXLens;var LGraphFXBokeh=function(){this.addInput("Texture","Texture");this.addInput("Blurred","Texture");this.addInput("Mask","Texture");this.addInput("Threshold","number");this.addOutput("Texture","Texture");this.properties={shape:"",size:10,alpha:1,threshold:1,high_precision:!1}};LGraphFXBokeh.title="Bokeh";LGraphFXBokeh.desc="applies an Bokeh effect";LGraphFXBokeh.widgets_info={shape:{widget:"texture"}};LGraphFXBokeh.prototype.onExecute= function(){var a=this.getInputData(0),b=this.getInputData(1),c=this.getInputData(2);if(a&&c&&this.properties.shape){b||(b=a);var d=LGraphTexture.getTexture(this.properties.shape);if(d){var e=this.properties.threshold;this.isInputConnected(3)&&(e=this.getInputData(3),this.properties.threshold=e);var f=gl.UNSIGNED_BYTE;this.properties.high_precision&&(f=gl.half_float_ext?gl.HALF_FLOAT_OES:gl.FLOAT);this._temp_texture&&this._temp_texture.type==f&&this._temp_texture.width==a.width&&this._temp_texture.height== a.height||(this._temp_texture=new GL.Texture(a.width,a.height,{type:f,format:gl.RGBA,filter:gl.LINEAR}));var g=LGraphFXBokeh._first_shader;g||(g=LGraphFXBokeh._first_shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphFXBokeh._first_pixel_shader));var h=LGraphFXBokeh._second_shader;h||(h=LGraphFXBokeh._second_shader=new GL.Shader(LGraphFXBokeh._second_vertex_shader,LGraphFXBokeh._second_pixel_shader));var k=this._points_mesh;k&&k._width==a.width&&k._height==a.height&&2==k._spacing||(k=this.createPointsMesh(a.width, -a.height,2));var l=Mesh.getScreenQuad(),n=this.properties.size,q=this.properties.alpha;gl.disable(gl.DEPTH_TEST);gl.disable(gl.BLEND);this._temp_texture.drawTo(function(){a.bind(0);b.bind(1);c.bind(2);g.uniforms({u_texture:0,u_texture_blur:1,u_mask:2,u_texsize:[a.width,a.height]}).draw(l)});this._temp_texture.drawTo(function(){gl.enable(gl.BLEND);gl.blendFunc(gl.ONE,gl.ONE);a.bind(0);d.bind(3);h.uniforms({u_texture:0,u_mask:2,u_shape:3,u_alpha:q,u_threshold:e,u_pointSize:n,u_itexsize:[1/a.width,1/ -a.height]}).draw(k,gl.POINTS)});this.setOutputData(0,this._temp_texture)}}else this.setOutputData(0,a)};LGraphFXBokeh.prototype.createPointsMesh=function(a,b,c){for(var d=Math.round(a/c),e=Math.round(b/c),f=new Float32Array(d*e*2),g=-1,h=2/a*c,k=2/b*c,l=0;l=b.NOTEON||c<=b.NOTEOFF)this.channel=a&15};Object.defineProperty(b.prototype,"velocity",{get:function(){return this.cmd==b.NOTEON?this.data[2]:-1},set:function(a){this.data[2]=a},enumerable:!0}); +b.notes="A A# B C C# D D# E F F# G G#".split(" ");b.prototype.getPitch=function(){return 440*Math.pow(2,(this.data[1]-69)/12)};b.computePitch=function(a){return 440*Math.pow(2,(a-69)/12)};b.prototype.getPitchBend=function(){return this.data[1]+(this.data[2]<<7)-8192};b.computePitchBend=function(a,b){return a+(b<<7)-8192};b.prototype.setCommandFromString=function(a){this.cmd=b.computeCommandFromString(a)};b.computeCommandFromString=function(a){if(!a)return 0;if(a&&a.constructor===Number)return a;a= +a.toUpperCase();switch(a){case "NOTE ON":case "NOTEON":return b.NOTEON;case "NOTE OFF":case "NOTEOFF":return b.NOTEON;case "KEY PRESSURE":case "KEYPRESSURE":return b.KEYPRESSURE;case "CONTROLLER CHANGE":case "CONTROLLERCHANGE":case "CC":return b.CONTROLLERCHANGE;case "PROGRAM CHANGE":case "PROGRAMCHANGE":case "PC":return b.PROGRAMCHANGE;case "CHANNEL PRESSURE":case "CHANNELPRESSURE":return b.CHANNELPRESSURE;case "PITCH BEND":case "PITCHBEND":return b.PITCHBEND;case "TIME TICK":case "TIMETICK":return b.TIMETICK; +default:return Number(a)}};b.toNoteString=function(a){var c;c=(a-21)%12;0>c&&(c=12+c);return b.notes[c]+Math.floor((a-24)/12+1)};b.prototype.toString=function(){var a=""+this.channel+". ";switch(this.cmd){case b.NOTEON:a+="NOTEON "+b.toNoteString(this.data[1]);break;case b.NOTEOFF:a+="NOTEOFF "+b.toNoteString(this.data[1]);break;case b.CONTROLLERCHANGE:a+="CC "+this.data[1]+" "+this.data[2];break;case b.PROGRAMCHANGE:a+="PC "+this.data[1];break;case b.PITCHBEND:a+="PITCHBEND "+this.getPitchBend(); +break;case b.KEYPRESSURE:a+="KEYPRESS "+this.data[1]}return a};b.prototype.toHexString=function(){for(var a="",b=0;bthis.properties.max_value||this.trigger("on_midi",c)};LiteGraph.registerNodeType("midi/filter", +g);h.title="MIDIEvent";h.desc="Create a MIDI Event";h.prototype.onAction=function(a,c){"assign"==a?(this.properties.channel=c.channel,this.properties.cmd=c.cmd,this.properties.value1=c.data[1],this.properties.value2=c.data[2]):(c=new b,c.channel=this.properties.channel,this.properties.cmd&&this.properties.cmd.constructor===String?c.setCommandFromString(this.properties.cmd):c.cmd=this.properties.cmd,c.data[0]=c.cmd|c.channel,c.data[1]=Number(this.properties.value1),c.data[2]=Number(this.properties.value2), +this.trigger("on_midi",c))};h.prototype.onExecute=function(){var a=this.properties;if(this.outputs)for(var c=0;c + + diff --git a/src/litegraph.js b/src/litegraph.js index 204d13cc8..087048b3b 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -28,6 +28,17 @@ var LiteGraph = { DEFAULT_POSITION: [100,100],//default node position node_images_path: "", + //enums + INPUT: 1, + OUTPUT: 2, + + EVENT: -1, //for outputs + ACTION: -1, //for inputs + + ALWAYS: 0, + ON_EVENT: 1, + NEVER: 2, + proxy: null, //used to redirect calls debug: false, @@ -66,6 +77,10 @@ var LiteGraph = { this.registered_node_types[ type ] = base_class; if(base_class.constructor.name) this.Nodes[ base_class.constructor.name ] = base_class; + + //warnings + if(base_class.prototype.onPropertyChange) + console.warn("LiteGraph node class " + type + " has onPropertyChange method, it must be called onPropertyChanged with d at the end"); }, /** @@ -89,7 +104,7 @@ var LiteGraph = { * @param {Object} options to set options */ - createNode: function(type, title, options) + createNode: function( type, title, options ) { var base_class = this.registered_node_types[type]; if (!base_class) @@ -108,9 +123,11 @@ var LiteGraph = { node.type = type; if(!node.title) node.title = title; if(!node.properties) node.properties = {}; + if(!node.properties_info) node.properties_info = []; if(!node.flags) node.flags = {}; if(!node.size) node.size = node.computeSize(); if(!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat(); + if(!node.mode) node.mode = LiteGraph.ALWAYS; //extra options if(options) @@ -227,6 +244,16 @@ var LiteGraph = { for(var i in r) target[i] = r[i]; return target; + }, + + isValidConnection: function( type_a, type_b ) + { + if( !type_a || //generic output + !type_b || //generic input + type_a == type_a || //same type (is valid for triggers) + (type_a !== LiteGraph.EVENT && type_b !== LiteGraph.EVENT && type_a.toLowerCase() == type_b.toLowerCase()) ) //same type + return true; + return false; } }; @@ -412,11 +439,22 @@ LGraph.prototype.runStep = function(num) var start = LiteGraph.getTime(); this.globaltime = 0.001 * (start - this.starttime); + var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; + if(!nodes) + return; + try { + //iterations for(var i = 0; i < num; i++) { - this.sendEventToAllNodes("onExecute"); + for( var j = 0, l = nodes.length; j < l; ++j ) + { + var node = nodes[j]; + if( node.mode == LiteGraph.ALWAYS && node.onExecute ) + node.onExecute(); + } + this.fixedtime += this.fixedtime_lapse; if( this.onExecuteStep ) this.onExecuteStep(); @@ -437,7 +475,8 @@ LGraph.prototype.runStep = function(num) } var elapsed = LiteGraph.getTime() - start; - if (elapsed == 0) elapsed = 1; + if (elapsed == 0) + elapsed = 1; this.elapsed_time = 0.001 * elapsed; this.globaltime += 0.001 * elapsed; this.iteration += 1; @@ -528,13 +567,13 @@ LGraph.prototype.computeExecutionOrder = function() //the remaining ones (loops) for(var i in M) - L.push(M[i]); + L.push( M[i] ); - if(L.length != this._nodes.length && LiteGraph.debug) - console.log("something went wrong, nodes missing"); + if( L.length != this._nodes.length && LiteGraph.debug ) + console.warn("something went wrong, nodes missing"); //save order number in the node - for(var i in L) + for(var i = 0; i < L.length; ++i) L[i].order = i; return L; @@ -582,8 +621,10 @@ LGraph.prototype.getElapsedTime = function() * @param {Array} params parameters in array format */ -LGraph.prototype.sendEventToAllNodes = function(eventname, params) +LGraph.prototype.sendEventToAllNodes = function( eventname, params, mode ) { + mode = mode || LiteGraph.ALWAYS; + var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; if(!nodes) return; @@ -591,12 +632,12 @@ LGraph.prototype.sendEventToAllNodes = function(eventname, params) for( var j = 0, l = nodes.length; j < l; ++j ) { var node = nodes[j]; - if(node[eventname]) + if(node[eventname] && node.mode == mode ) { if(params === undefined) node[eventname](); else if(params && params.constructor === Array) - node[eventname].apply(M[j], params); + node[eventname].apply( node, params ); else node[eventname](params); } @@ -646,7 +687,7 @@ LGraph.prototype.add = function(node, skip_compute_order) */ if(node.onAdded) - node.onAdded(); + node.onAdded( this ); if(this.config.align_to_grid) node.alignToGrid(); @@ -1087,8 +1128,11 @@ LGraph.prototype.serialize = function() //remove data from links, we dont want to store it for(var i in this.links) //links is an OBJECT - this.links[i].data = null; - + { + var link = this.links[i]; + link.data = null; + delete link._last_time; + } var data = { // graph: this.graph, @@ -1184,7 +1228,7 @@ LGraph.prototype.onNodeTrace = function(node, msg, color) + onMouseEnter + onMouseLeave + onExecute: execute the node - + onPropertyChange: when a property is changed in the panel (return true to skip default behaviour) + + onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour) + onGetInputs: returns an array of possible inputs + onGetOutputs: returns an array of possible outputs + onDblClick @@ -1194,6 +1238,7 @@ LGraph.prototype.onNodeTrace = function(node, msg, color) + onDropItem : DOM item dropped over the node + onDropFile : file dropped over the node + onConnectInput : if returns false the incoming connection will be canceled + + onConnectionsChange : a connection changed (new one or removed) */ /** @@ -1239,7 +1284,9 @@ LGraphNode.prototype._ctor = function( title ) this.connections = []; //local data - this.properties = {}; + this.properties = {}; //for the values + this.properties_info = []; //for the info + this.data = null; //persistent local data this.flags = { //skip_title_render: true, @@ -1255,13 +1302,18 @@ LGraphNode.prototype.configure = function(info) { for (var j in info) { - if(j == "console") continue; + if(j == "console") + continue; if(j == "properties") { //i dont want to clone properties, I want to reuse the old container for(var k in info.properties) + { this.properties[k] = info.properties[k]; + if(this.onPropertyChanged) + this.onPropertyChanged(k,info.properties[k]); + } continue; } @@ -1278,6 +1330,9 @@ LGraphNode.prototype.configure = function(info) this[j] = info[j]; } + if(this.onConnectionsChange) + this.onConnectionsChange(); + //FOR LEGACY, PLEASE REMOVE ON NEXT VERSION for(var i in this.inputs) { @@ -1322,7 +1377,8 @@ LGraphNode.prototype.serialize = function() data: this.data, flags: LiteGraph.cloneObject(this.flags), inputs: this.inputs, - outputs: this.outputs + outputs: this.outputs, + mode: this.mode }; if(this.properties) @@ -1352,8 +1408,23 @@ LGraphNode.prototype.clone = function() { var node = LiteGraph.createNode(this.type); - var data = this.serialize(); + //we clone it because serialize returns shared containers + var data = LiteGraph.cloneObject( this.serialize() ); + + //remove links + if(data.inputs) + for(var i = 0; i < data.inputs.length; ++i) + data.inputs[i].link = null; + + if(data.outputs) + for(var i = 0; i < data.outputs.length; ++i) + { + if(data.outputs[i].links) + data.outputs[i].links.length = 0; + } + delete data["id"]; + //remove links node.configure(data); return node; @@ -1412,13 +1483,30 @@ LGraphNode.prototype.setOutputData = function(slot,data) * @param {number} slot * @return {*} data or if it is not connected returns undefined */ -LGraphNode.prototype.getInputData = function(slot) +LGraphNode.prototype.getInputData = function( slot, force_update ) { if(!this.inputs) return; //undefined; - if(slot < this.inputs.length && this.inputs[slot].link != null) - return this.graph.links[ this.inputs[slot].link ].data; - return; //undefined; + + if(slot >= this.inputs.length || this.inputs[slot].link == null) + return; + + var link_id = this.inputs[slot].link; + var link = this.graph.links[ link_id ]; + + if(!force_update) + return link.data; + + var node = this.graph.getNodeById( link.origin_id ); + if(!node) + return link.data; + + if(node.updateOutputData) + node.updateOutputData( link.origin_slot ); + else if(node.onExecute) + node.onExecute(); + + return link.data; } /** @@ -1499,13 +1587,80 @@ LGraphNode.prototype.getOutputNodes = function(slot) return null; } -LGraphNode.prototype.triggerOutput = function(slot,param) +/** +* Triggers an event in this node, this will trigger any output with the same name +* @method trigger +* @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all +* @param {*} param +*/ +LGraphNode.prototype.trigger = function( action, param ) { - var n = this.getOutputNode(slot); - if(n && n.onTrigger) - n.onTrigger(param); + if( !this.outputs || !this.outputs.length ) + return; + + if(this.graph) + this.graph._last_trigger_time = LiteGraph.getTime(); + + for(var i = 0; i < this.outputs.length; ++i) + { + var output = this.outputs[i]; + if(output.type !== LiteGraph.EVENT || (action && output.name != action) ) + continue; + + var links = output.links; + if(!links || !links.length) + continue; + + //for every link attached here + for(var k = 0; k < links.length; ++k) + { + var link_info = this.graph.links[ links[k] ]; + if(!link_info) //not connected + continue; + var node = this.graph.getNodeById( link_info.target_id ); + if(!node) //node not found? + continue; + + //used to mark events in graph + link_info._last_time = LiteGraph.getTime(); + + var target_connection = node.inputs[ link_info.target_slot ]; + + if(node.onAction) + node.onAction( target_connection.name, param ); + else if(node.mode === LiteGraph.ON_TRIGGER) + { + if(node.onExecute) + node.onExecute(param); + } + } + } } +/** +* add a new property to this node +* @method addProperty +* @param {string} name +* @param {*} default_value +* @param {string} type string defining the output type ("vec3","number",...) +* @param {Object} extra_info this can be used to have special properties of the property (like values, etc) +*/ +LGraphNode.prototype.addProperty = function( name, default_value, type, extra_info ) +{ + var o = { name: name, type: type, default_value: default_value }; + if(extra_info) + for(var i in extra_info) + o[i] = extra_info[i]; + if(!this.properties_info) + this.properties_info = []; + this.properties_info.push(o); + if(!this.properties) + this.properties = {}; + this.properties[ name ] = default_value; + return o; +} + + //connections /** @@ -1517,16 +1672,18 @@ LGraphNode.prototype.triggerOutput = function(slot,param) */ LGraphNode.prototype.addOutput = function(name,type,extra_info) { - var o = {name:name,type:type,links:null}; + var o = { name: name, type: type, links: null }; if(extra_info) for(var i in extra_info) o[i] = extra_info[i]; - if(!this.outputs) this.outputs = []; + if(!this.outputs) + this.outputs = []; this.outputs.push(o); if(this.onOutputAdded) this.onOutputAdded(o); this.size = this.computeSize(); + return o; } /** @@ -1589,6 +1746,7 @@ LGraphNode.prototype.addInput = function(name,type,extra_info) this.size = this.computeSize(); if(this.onInputAdded) this.onInputAdded(o); + return o; } /** @@ -1640,7 +1798,15 @@ LGraphNode.prototype.removeInput = function(slot) */ LGraphNode.prototype.addConnection = function(name,type,pos,direction) { - this.connections.push( {name:name,type:type,pos:pos,direction:direction,links:null}); + var o = { + name: name, + type: type, + pos: pos, + direction: direction, + links: null + }; + this.connections.push( o ); + return o; } /** @@ -1649,10 +1815,10 @@ LGraphNode.prototype.addConnection = function(name,type,pos,direction) * @param {number} minHeight * @return {number} the total size */ -LGraphNode.prototype.computeSize = function(minHeight) +LGraphNode.prototype.computeSize = function( minHeight, out ) { var rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1); - var size = new Float32Array([0,0]); + var size = out || new Float32Array([0,0]); rows = Math.max(rows, 1); size[1] = rows * 14 + 6; @@ -1794,10 +1960,10 @@ LGraphNode.prototype.findOutputSlot = function(name) * @method connect * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) * @param {LGraphNode} node the target node -* @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot) +* @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) * @return {boolean} if it was connected succesfully */ -LGraphNode.prototype.connect = function(slot, node, target_slot) +LGraphNode.prototype.connect = function( slot, node, target_slot ) { target_slot = target_slot || 0; @@ -1829,6 +1995,7 @@ LGraphNode.prototype.connect = function(slot, node, target_slot) return false; //if( node.constructor != LGraphNode ) throw ("LGraphNode.connect: node is not of type LGraphNode"); + //you can specify the slot by name if(target_slot.constructor === String) { target_slot = node.findInputSlot(target_slot); @@ -1839,7 +2006,18 @@ LGraphNode.prototype.connect = function(slot, node, target_slot) return false; } } - else if(!node.inputs || target_slot >= node.inputs.length) + else if( target_slot === LiteGraph.EVENT ) + { + //search for first slot with event? + /* + //create input for trigger + var input = node.addInput("onTrigger", LiteGraph.EVENT ); + target_slot = node.inputs.length - 1; //last one is the one created + node.mode = LiteGraph.ON_TRIGGER; + */ + return false; + } + else if( !node.inputs || target_slot >= node.inputs.length ) { if(LiteGraph.debug) console.log("Connect: Error, slot number not found"); @@ -1847,41 +2025,46 @@ LGraphNode.prototype.connect = function(slot, node, target_slot) } //if there is something already plugged there, disconnect - if(target_slot != -1 && node.inputs[target_slot].link != null) - node.disconnectInput(target_slot); + if(node.inputs[ target_slot ].link != null ) + node.disconnectInput( target_slot ); //why here?? this.setDirtyCanvas(false,true); this.graph.connectionChange( this ); - //special case: -1 means node-connection, used for triggers var output = this.outputs[slot]; - //allows nodes to block connection even if all test passes + //allows nodes to block connection if(node.onConnectInput) if( node.onConnectInput( target_slot, output.type, output ) === false) return false; - if(target_slot == -1) + var input = node.inputs[target_slot]; + + if( LiteGraph.isValidConnection( output.type, input.type) ) { - if( output.links == null ) - output.links = []; - output.links.push({id:node.id, slot: -1}); - } - else if( !output.type || //generic output - !node.inputs[target_slot].type || //generic input - output.type.toLowerCase() == node.inputs[target_slot].type.toLowerCase() ) //same type - { - //info: link structure => [ 0:link_id, 1:start_node_id, 2:start_slot, 3:end_node_id, 4:end_slot ] - //var link = [ this.graph.last_link_id++, this.id, slot, node.id, target_slot ]; - var link = { id: this.graph.last_link_id++, origin_id: this.id, origin_slot: slot, target_id: node.id, target_slot: target_slot }; + var link = { + id: this.graph.last_link_id++, + origin_id: this.id, + origin_slot: slot, + target_id: node.id, + target_slot: target_slot + }; + + //add to graph links list this.graph.links[ link.id ] = link; - //connect - if( output.links == null ) output.links = []; + //connect in output + if( output.links == null ) + output.links = []; output.links.push( link.id ); + //connect in input node.inputs[target_slot].link = link.id; + if(this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT, slot ); + if(node.onConnectionsChange) + node.onConnectionsChange( LiteGraph.OUTPUT, target_slot ); } this.setDirtyCanvas(false,true); @@ -1993,6 +2176,7 @@ LGraphNode.prototype.disconnectInput = function(slot) var input = this.inputs[slot]; if(!input) return false; + var link_id = this.inputs[slot].link; this.inputs[slot].link = null; @@ -2019,6 +2203,11 @@ LGraphNode.prototype.disconnectInput = function(slot) break; } } + + if(this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT ); + if(node.onConnectionsChange) + node.onConnectionsChange( LiteGraph.INPUT); } this.setDirtyCanvas(false,true); @@ -2102,6 +2291,7 @@ LGraphNode.prototype.loadImage = function(url) } //safe LGraphNode action execution (not sure if safe) +/* LGraphNode.prototype.executeAction = function(action) { if(action == "") return false; @@ -2137,6 +2327,7 @@ LGraphNode.prototype.executeAction = function(action) return true; } +*/ /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ LGraphNode.prototype.captureInput = function(v) @@ -2219,6 +2410,30 @@ function LGraphCanvas( canvas, graph, options ) this.max_zoom = 10; this.min_zoom = 0.1; + this.title_text_font = "bold 14px Arial"; + this.inner_text_font = "normal 12px Arial"; + this.default_link_color = "#AAC"; + + this.highquality_render = true; + this.editor_alpha = 1; //used for transition + this.pause_rendering = false; + this.render_shadows = true; + this.clear_background = true; + + this.render_only_selected = true; + this.live_mode = false; + this.show_info = true; + this.allow_dragcanvas = true; + this.allow_dragnodes = true; + + this.always_render_background = false; + this.render_connections_shadows = false; //too much cpu + this.render_connections_border = true; + this.render_curved_connections = true; + this.render_connection_arrows = true; + + this.connections_width = 4; + //link canvas and graph if(graph) graph.attachCanvas(this); @@ -2232,7 +2447,7 @@ function LGraphCanvas( canvas, graph, options ) this.autoresize = options.autoresize; } -LGraphCanvas.link_type_colors = {'number':"#AAC",'node':"#DCA"}; +LGraphCanvas.link_type_colors = {"-1":"#F85",'number':"#AAC","node":"#DCA"}; /** @@ -2256,18 +2471,6 @@ LGraphCanvas.prototype.clear = function() this.node_capturing_input = null; this.connecting_node = null; - this.highquality_render = true; - this.editor_alpha = 1; //used for transition - this.pause_rendering = false; - this.render_shadows = true; - this.clear_background = true; - - this.render_only_selected = true; - this.live_mode = false; - this.show_info = true; - this.allow_dragcanvas = true; - this.allow_dragnodes = true; - this.dirty_canvas = true; this.dirty_bgcanvas = true; this.dirty_area = null; @@ -2277,17 +2480,8 @@ LGraphCanvas.prototype.clear = function() this.last_mouse = [0,0]; this.last_mouseclick = 0; - this.title_text_font = "bold 14px Arial"; - this.inner_text_font = "normal 12px Arial"; - - this.render_connections_shadows = false; //too much cpu - this.render_connections_border = true; - this.render_curved_connections = true; - this.render_connection_arrows = true; - - this.connections_width = 4; - - if(this.onClear) this.onClear(); + if(this.onClear) + this.onClear(); //this.UIinit(); } @@ -2618,6 +2812,8 @@ LGraphCanvas.prototype.setDirty = function(fgcanvas,bgcanvas) */ LGraphCanvas.prototype.getCanvasWindow = function() { + if(!this.canvas) + return window; var doc = this.canvas.ownerDocument; return doc.defaultView || doc.parentWindow; } @@ -2682,11 +2878,10 @@ LGraphCanvas.prototype.processMouseDown = function(e) var n = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); var skip_dragging = false; - LiteGraph.closeAllContextualMenus(); + LiteGraph.closeAllContextualMenus( ref_window ); if(e.which == 1) //left button mouse { - if(!e.shiftKey) //REFACTOR: integrate with function { //no node or another node selected @@ -2783,7 +2978,7 @@ LGraphCanvas.prototype.processMouseDown = function(e) //if do not capture mouse - if( n.onMouseDown && n.onMouseDown(e) ) + if( n.onMouseDown && n.onMouseDown(e, [e.canvasX - n.pos[0], e.canvasY - n.pos[1]] ) ) block_drag_node = true; else if(this.live_mode) { @@ -2906,19 +3101,27 @@ LGraphCanvas.prototype.processMouseMove = function(e) if(n.onMouseMove) n.onMouseMove(e); - //ontop of input + //on top of input if(this.connecting_node) { - var pos = this._highlight_input || [0,0]; - var slot = this.isOverNodeInput(n, e.canvasX, e.canvasY, pos); - if(slot != -1 && n.inputs[slot]) - { - var slot_type = n.inputs[slot].type; - if( !this.connecting_output.type || !slot_type || slot_type.toLowerCase() == this.connecting_output.type.toLowerCase() ) - this._highlight_input = pos; + var pos = this._highlight_input || [0,0]; //to store the output of isOverNodeInput + + if( this.isOverNodeBox( n, e.canvasX, e.canvasY ) ) + { + //mouse on top of the corner box, dont know what to do } else - this._highlight_input = null; + { + var slot = this.isOverNodeInput( n, e.canvasX, e.canvasY, pos ); + if(slot != -1 && n.inputs[slot]) + { + var slot_type = n.inputs[slot].type; + if( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) + this._highlight_input = pos; + } + else + this._highlight_input = null; + } } //Search for corner @@ -3010,15 +3213,14 @@ LGraphCanvas.prototype.processMouseUp = function(e) this.dirty_canvas = true; this.dirty_bgcanvas = true; - var node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes); + var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); //node below mouse if(node) { - - if(this.connecting_output.type == 'node') + if( this.connecting_output.type == LiteGraph.EVENT && this.isOverNodeBox( node, e.canvasX, e.canvasY ) ) { - this.connecting_node.connect(this.connecting_slot, node, -1); + this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT ); } else { @@ -3031,9 +3233,12 @@ LGraphCanvas.prototype.processMouseUp = function(e) else { //not on top of an input var input = node.getInputInfo(0); - //simple connect - if(input && !input.link && input.type == this.connecting_output.type) //toLowerCase missing - this.connecting_node.connect(this.connecting_slot, node, 0); + //auto connect + if(this.connecting_output.type == LiteGraph.EVENT) + this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT ); + else + if(input && !input.link && input.type == this.connecting_output.type) //toLowerCase missing + this.connecting_node.connect(this.connecting_slot, node, 0); } } } @@ -3066,9 +3271,9 @@ LGraphCanvas.prototype.processMouseUp = function(e) this.dragging_canvas = false; if( this.node_over && this.node_over.onMouseUp ) - this.node_over.onMouseUp(e); + this.node_over.onMouseUp(e, [e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1]] ); if( this.node_capturing_input && this.node_capturing_input.onMouseUp ) - this.node_capturing_input.onMouseUp(e); + this.node_capturing_input.onMouseUp(e, [e.canvasX - this.node_capturing_input.pos[0], e.canvasY - this.node_capturing_input.pos[1]] ); } } else if (e.which == 2) //middle button @@ -3126,7 +3331,15 @@ LGraphCanvas.prototype.processMouseWheel = function(e) return false; // prevent default } -LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos) +LGraphCanvas.prototype.isOverNodeBox = function( node, canvasx, canvasy ) +{ + var title_height = LiteGraph.NODE_TITLE_HEIGHT; + if( isInsideRectangle( canvasx, canvasy, node.pos[0] + 2, node.pos[1] + 2 - title_height, title_height - 4,title_height - 4) ) + return true; + return false; +} + +LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos ) { if(node.inputs) for(var i = 0, l = node.inputs.length; i < l; ++i) @@ -3135,7 +3348,11 @@ LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_p var link_pos = node.getConnectionPos(true,i); if( isInsideRectangle(canvasx, canvasy, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) { - if(slot_pos) { slot_pos[0] = link_pos[0]; slot_pos[1] = link_pos[1] }; + if(slot_pos) + { + slot_pos[0] = link_pos[0]; + slot_pos[1] = link_pos[1]; + } return i; } } @@ -3454,6 +3671,9 @@ LGraphCanvas.prototype.computeVisibleNodes = function() LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { + if(!this.canvas) + return; + //fps counting var now = LiteGraph.getTime(); this.render_time = (now - this.last_draw_time)*0.001; @@ -3466,7 +3686,7 @@ LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) this.visible_area = new Float32Array([start[0],start[1],end[0],end[1]]); } - if(this.dirty_bgcanvas || force_bgcanvas) + if(this.dirty_bgcanvas || force_bgcanvas || this.always_render_background || (this.graph && this.graph._last_trigger_time && (now - this.graph._last_trigger_time) < 1000) ) this.drawBackCanvas(); if(this.dirty_canvas || force_canvas) @@ -3558,11 +3778,22 @@ LGraphCanvas.prototype.drawFrontCanvas = function() if(this.connecting_pos != null) { ctx.lineWidth = this.connections_width; - var link_color = this.connecting_output.type == 'node' ? "#F85" : "#AFA"; + var link_color = null; + switch( this.connecting_output.type ) + { + case LiteGraph.EVENT: link_color = "#F85"; break; + default: + link_color = "#AFA"; + } this.renderLink(ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]], link_color ); ctx.beginPath(); - ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2); + + if( this.connecting_output.type === LiteGraph.EVENT ) + ctx.rect( (this.connecting_pos[0] - 6) + 0.5, (this.connecting_pos[1] - 5) + 0.5,14,10); + else + ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2); + /* if( this.connecting_output.round) ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2); @@ -3825,6 +4056,8 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) var render_text = this.scale > 0.6; + var out_slot = this.connecting_output; + //render inputs and outputs if(!node.flags.collapsed) { @@ -3835,7 +4068,8 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) var slot = node.inputs[i]; ctx.globalAlpha = editor_alpha; - if (this.connecting_node != null && this.connecting_output.type && node.inputs[i].type && this.connecting_output.type.toLowerCase() != node.inputs[i].type.toLowerCase() ) + //change opacity of incompatible slots + if ( this.connecting_node && LiteGraph.isValidConnection( slot.type && out_slot.type ) ) ctx.globalAlpha = 0.4 * editor_alpha; ctx.fillStyle = slot.link != null ? "#7F7" : "#AAA"; @@ -3846,10 +4080,10 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) ctx.beginPath(); - if (1 || slot.round) + if (slot.type === LiteGraph.EVENT) + ctx.rect((pos[0] - 6) + 0.5, (pos[1] - 5) + 0.5,14,10); + else ctx.arc(pos[0],pos[1],4,0,Math.PI*2); - //else - // ctx.rect((pos[0] - 6) + 0.5, (pos[1] - 5) + 0.5,14,10); ctx.fill(); @@ -3886,10 +4120,10 @@ LGraphCanvas.prototype.drawNode = function(node, ctx ) ctx.beginPath(); //ctx.rect( node.size[0] - 14,i*14,10,10); - if (1 || slot.round) - ctx.arc(pos[0],pos[1],4,0,Math.PI*2); - //else - // ctx.rect((pos[0] - 6) + 0.5,(pos[1] - 5) + 0.5,14,10); + if (slot.type === LiteGraph.EVENT) + ctx.rect((pos[0] - 6) + 0.5,(pos[1] - 5) + 0.5,14,10); + else + ctx.arc( pos[0],pos[1],4,0, Math.PI*2 ); //trigger //if(slot.node_id != null && slot.slot == -1) @@ -3983,7 +4217,7 @@ LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolo if(node.onDrawBackground) node.onDrawBackground(ctx); - //title bg + //title bg (remember, it is rendered ABOVE the node if(!no_title) { ctx.fillStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; @@ -4004,7 +4238,7 @@ LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolo //ctx.stroke(); } - //box + //title box ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; ctx.beginPath(); if (shape == "round") @@ -4079,10 +4313,11 @@ LGraphCanvas.prototype.drawNodeCollapsed = function(node, ctx, fgcolor, bgcolor) } } -LGraphCanvas.link_colors = ["#AAC","#ACA","#CAA"]; - +//OPTIMIZE THIS: precatch connections position instead of recomputing them every time LGraphCanvas.prototype.drawConnections = function(ctx) { + var now = LiteGraph.getTime(); + //draw connections ctx.lineWidth = this.connections_width; @@ -4102,7 +4337,8 @@ LGraphCanvas.prototype.drawConnections = function(ctx) continue; var link_id = input.link; var link = this.graph.links[ link_id ]; - if(!link) continue; + if(!link) + continue; var start_node = this.graph.getNodeById( link.origin_id ); if(start_node == null) continue; @@ -4114,16 +4350,22 @@ LGraphCanvas.prototype.drawConnections = function(ctx) else start_node_slotpos = start_node.getConnectionPos(false, start_node_slot); - var color = LGraphCanvas.link_type_colors[node.inputs[i].type]; - if(color == null) - color = LGraphCanvas.link_colors[node.id % LGraphCanvas.link_colors.length]; + var color = LGraphCanvas.link_type_colors[ node.inputs[i].type ] || this.default_link_color; + this.renderLink(ctx, start_node_slotpos, node.getConnectionPos(true,i), color ); + + if(link && link._last_time && now - link._last_time < 1000 ) + { + var f = 2.0 - (now - link._last_time) * 0.002; + var color = "rgba(255,255,255, " + f.toFixed(2) + ")"; + this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i) , color, true, f ); + } } } ctx.globalAlpha = 1; } -LGraphCanvas.prototype.renderLink = function(ctx,a,b,color) +LGraphCanvas.prototype.renderLink = function(ctx,a,b,color, skip_border, flow ) { if(!this.highquality_render) { @@ -4156,7 +4398,7 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color) ctx.lineTo(b[0]-10,b[1]); } - if(this.render_connections_border && this.scale > 0.6) + if(this.render_connections_border && this.scale > 0.6 && !skip_border) { ctx.strokeStyle = "rgba(0,0,0,0.5)"; ctx.stroke(); @@ -4166,12 +4408,17 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color) ctx.fillStyle = ctx.strokeStyle = color; ctx.stroke(); + //no symbols + if(!this.render_connection_arrows || this.scale < 0.6) + return; + //render arrow if(this.render_connection_arrows && this.scale > 0.6) { - //get two points in the bezier curve var pos = this.computeConnectionPoint(a,b,0.5); var pos2 = this.computeConnectionPoint(a,b,0.51); + + //get two points in the bezier curve var angle = 0; if(this.render_curved_connections) angle = -Math.atan2( pos2[0] - pos[0], pos2[1] - pos[1]); @@ -4188,6 +4435,18 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color) ctx.fill(); ctx.restore(); } + + if(flow) + { + for(var i = 0; i < 5; ++i) + { + var f = (LiteGraph.getTime() * 0.001 + (i * 0.2)) % 1; + var pos = this.computeConnectionPoint(a,b,f); + ctx.beginPath(); + ctx.arc(pos[0],pos[1],5,0,2*Math.PI); + ctx.fill(); + } + } } LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t) @@ -4319,7 +4578,7 @@ LGraphCanvas.prototype.touchHandler = function(event) LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) { - var window = canvas.getCanvasWindow(); + var ref_window = canvas.getCanvasWindow(); var values = LiteGraph.getNodeTypesCategories(); var entries = {}; @@ -4327,7 +4586,7 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) if(values[i]) entries[ i ] = { value: values[i], content: values[i] , is_menu: true }; - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, window); + var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); function inner_clicked(v, e) { @@ -4337,7 +4596,7 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) for(var i in node_types) values.push( { content: node_types[i].title, value: node_types[i].type }); - LiteGraph.createContextualMenu(values, {event: e, callback: inner_create, from: menu}, window); + LiteGraph.createContextualMenu(values, {event: e, callback: inner_create, from: menu}, ref_window); return false; } @@ -4365,16 +4624,20 @@ LGraphCanvas.onMenuNodeEdit = function() } -LGraphCanvas.onMenuNodeInputs = function(node, e, prev_menu) +LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) { - if(!node) return; + if(!node) + return; + + var that = this; + var ref_window = this.getCanvasWindow(); var options = node.optional_inputs; if(node.onGetInputs) options = node.onGetInputs(); + + var entries = []; if(options) - { - var entries = []; for (var i in options) { var entry = options[i]; @@ -4383,50 +4646,86 @@ LGraphCanvas.onMenuNodeInputs = function(node, e, prev_menu) label = entry[2].label; entries.push({content: label, value: entry}); } - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); - } - function inner_clicked(v) + if(this.onMenuNodeInputs) + entries = this.onMenuNodeInputs( entries ); + + if(!entries.length) + return; + + var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + + function inner_clicked(v, e, prev) { - if(!node) return; - node.addInput(v.value[0],v.value[1], v.value[2]); + if(!node) + return; + + if(v.callback) + v.callback.call(that, node, v, e, prev); + + if(v.value) + node.addInput(v.value[0],v.value[1], v.value[2]); } return false; } -LGraphCanvas.onMenuNodeOutputs = function(node, e, prev_menu) +LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) { - if(!node) return; + if(!node) + return; + + var that = this; + var ref_window = this.getCanvasWindow(); var options = node.optional_outputs; if(node.onGetOutputs) options = node.onGetOutputs(); + + var entries = []; if(options) - { - var entries = []; for (var i in options) { var entry = options[i]; + if(!entry) //separator? + { + entries.push(null); + continue; + } + if(node.findOutputSlot(entry[0]) != -1) continue; //skip the ones already on var label = entry[0]; if(entry[2] && entry[2].label) label = entry[2].label; - entries.push({content: label, value: entry}); + var data = {content: label, value: entry}; + if(entry[1] == LiteGraph.EVENT) + data.className = "event"; + entries.push(data); } - if(entries.length) - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); - } - function inner_clicked(v) + if(this.onMenuNodeOutputs) + entries = this.onMenuNodeOutputs( entries ); + + if(!entries.length) + return; + + var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + + function inner_clicked( v, e, prev ) { if(!node) return; + if(v.callback) + v.callback.call(that, node, v, e, prev); + + if(!v.value) + return; + var value = v.value[1]; - if(value && (value.constructor === Object || value.constructor === Array)) //submenu + if(value && (value.constructor === Object || value.constructor === Array)) //submenu why? { var entries = []; for(var i in value) @@ -4441,6 +4740,137 @@ LGraphCanvas.onMenuNodeOutputs = function(node, e, prev_menu) return false; } +LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) +{ + if(!node || !node.properties) + return; + + var that = this; + var ref_window = this.getCanvasWindow(); + + var entries = []; + for (var i in node.properties) + { + var value = node.properties[i] !== undefined ? node.properties[i] : " "; + entries.push({content: "" + i + "" + "" + value + "", value: i}); + } + if(!entries.length) + return; + + var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu},ref_window); + + function inner_clicked( v, e, prev ) + { + if(!node) + return; + that.showEditPropertyValue( node, v.value, { event: e }); + } + + return false; +} + +LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) +{ + if(!node || node.properties[ property ] === undefined ) + return; + + options = options || {}; + var that = this; + + var type = "string"; + + if(node.properties[ property ] !== null) + type = typeof(node.properties[ property ]); + + var info = null; + if(node.getPropertyInfo) + info = node.getPropertyInfo(property); + if(info.type) + type = info.type; + + var input_html = ""; + + if(type == "string" || type == "number") + input_html = ""; + else if(type == "enum" && info.values) + { + input_html = ""; + } + + + var dialog = document.createElement("div"); + dialog.className = "graphdialog"; + dialog.innerHTML = "" + property + ""+input_html+""; + + if(type == "enum" && info.values) + { + var input = dialog.querySelector("select"); + input.addEventListener("change", function(e){ + var index = e.target.value; + setValue( e.options[e.selectedIndex].value ); + }); + } + else + { + var input = dialog.querySelector("input"); + input.value = node.properties[ property ] !== undefined ? node.properties[ property ] : ""; + 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( options.event ) + { + dialog.style.left = (options.event.pageX + offsetx) + "px"; + dialog.style.top = (options.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) + { + if(typeof( node.properties[ property ] ) == "number") + node.properties[ property ] = Number(value); + else + node.properties[ property ] = value; + dialog.parentNode.removeChild( dialog ); + node.setDirtyCanvas(true,true); + } +} + LGraphCanvas.onMenuNodeCollapse = function(node) { node.flags.collapsed = !node.flags.collapsed; @@ -4452,6 +4882,27 @@ LGraphCanvas.onMenuNodePin = function(node) node.pin(); } +LGraphCanvas.onMenuNodeMode = function(node, e, prev_menu) +{ + LiteGraph.createContextualMenu(["Always","On Event","Never"], {event: e, callback: inner_clicked, from: prev_menu}); + + function inner_clicked(v) + { + if(!node) + return; + switch(v) + { + case "On Event": node.mode = LiteGraph.ON_EVENT; break; + case "Never": node.mode = LiteGraph.NEVER; break; + case "Always": + default: + node.mode = LiteGraph.ALWAYS; break; + } + } + + return false; +} + LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) { var values = []; @@ -4534,12 +4985,9 @@ LGraphCanvas.prototype.getCanvasMenuOptions = function() if(this.getExtraMenuOptions) { - var extra = this.getExtraMenuOptions(this); + var extra = this.getExtraMenuOptions(this,options); if(extra) - { - extra.push(null); - options = extra.concat( options ); - } + options = options.concat( extra ); } return options; @@ -4553,9 +5001,12 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node) options = node.getMenuOptions(this); else options = [ - {content:"Inputs", is_menu: true, disabled:true, callback: LGraphCanvas.onMenuNodeInputs }, - {content:"Outputs", is_menu: true, disabled:true, callback: LGraphCanvas.onMenuNodeOutputs }, + {content:"Inputs", is_menu: true, disabled:true, callback: LGraphCanvas.showMenuNodeInputs }, + {content:"Outputs", is_menu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOutputs }, null, + {content:"Properties", is_menu: true, callback: LGraphCanvas.onShowMenuNodeProperties }, + null, + {content:"Mode", is_menu: true, callback: LGraphCanvas.onMenuNodeMode }, {content:"Collapse", callback: LGraphCanvas.onMenuNodeCollapse }, {content:"Pin", callback: LGraphCanvas.onMenuNodePin }, {content:"Colors", is_menu: true, callback: LGraphCanvas.onMenuNodeColors }, @@ -4612,6 +5063,8 @@ LGraphCanvas.prototype.processContextualMenu = function(node, event) { menu_info = slot.locked ? [ "Cannot remove" ] : { "Remove Slot": slot }; options.title = slot.input ? slot.input.type : slot.output.type; + if(slot.input && slot.input.type == LiteGraph.EVENT) + options.title = "Event"; } else menu_info = node ? this.getNodeMenuOptions(node) : this.getCanvasMenuOptions(); @@ -4638,7 +5091,7 @@ LGraphCanvas.prototype.processContextualMenu = function(node, event) } if(v.callback) - return v.callback(node, e, menu, that, event ); + return v.callback.call(that, node, e, menu, that, event ); } } @@ -4776,7 +5229,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) ref_window = ref_window || window; if (!options.from) - LiteGraph.closeAllContextualMenus(); + LiteGraph.closeAllContextualMenus( ref_window ); else { //closing submenus var menus = document.querySelectorAll(".graphcontextualmenu"); @@ -4822,7 +5275,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) if(item == null) { - element.className = "graphmenu-entry separator"; + element.className += " separator"; root.appendChild(element); continue; } @@ -4833,6 +5286,9 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) if(item.disabled) element.className += " disabled"; + if(item.className) + element.className += " " + item.className; + element.style.cursor = "pointer"; element.dataset["value"] = typeof(item) == "string" ? item : item.value; element.data = item; @@ -4911,7 +5367,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) } if(close) - LiteGraph.closeAllContextualMenus(); + LiteGraph.closeAllContextualMenus( ref_window ); //root.closeMenu(); } @@ -4930,9 +5386,11 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) return root; } -LiteGraph.closeAllContextualMenus = function() +LiteGraph.closeAllContextualMenus = function( ref_window ) { - var elements = document.querySelectorAll(".graphcontextualmenu"); + ref_window = ref_window || window; + + var elements = ref_window.document.querySelectorAll(".graphcontextualmenu"); if(!elements.length) return; var result = []; diff --git a/src/nodes/base.js b/src/nodes/base.js index 0e186698d..8862201c6 100755 --- a/src/nodes/base.js +++ b/src/nodes/base.js @@ -150,11 +150,11 @@ function GlobalInput() this.addOutput(input_name, null ); - this.properties = {name: input_name, type: null }; + this.properties = { name: input_name, type: null }; var that = this; - Object.defineProperty(this.properties, "name", { + Object.defineProperty( this.properties, "name", { get: function() { return input_name; }, @@ -173,7 +173,7 @@ function GlobalInput() enumerable: true }); - Object.defineProperty(this.properties, "type", { + Object.defineProperty( this.properties, "type", { get: function() { return that.outputs[0].type; }, set: function(v) { that.outputs[0].type = v; @@ -272,7 +272,7 @@ LiteGraph.registerNodeType("graph/output", GlobalOutput); function Constant() { this.addOutput("value","number"); - this.properties = { value:1.0 }; + this.addProperty( "value", 1.0 ); this.editable = { property:"value", type:"number" }; } @@ -313,7 +313,7 @@ function Watch() this.size = [60,20]; this.addInput("value",0,{label:""}); this.addOutput("value",0,{label:""}); - this.properties = { value:"" }; + this.addProperty( "value", "" ); } Watch.title = "Watch"; @@ -348,16 +348,37 @@ LiteGraph.registerNodeType("basic/watch", Watch); //Show value inside the debug console function Console() { + this.mode = LiteGraph.ON_EVENT; this.size = [60,20]; - this.addInput("data",0); + this.addProperty( "msg", "" ); + this.addInput("log", LiteGraph.EVENT); + this.addInput("msg",0); } Console.title = "Console"; Console.desc = "Show value inside the console"; +Console.prototype.onAction = function(action, param) +{ + if(action == "log") + console.log( param ); + else if(action == "warn") + console.warn( param ); + else if(action == "error") + console.error( param ); +} + Console.prototype.onExecute = function() { - console.log( this.getInputData(0) ); + var msg = this.getInputData(0); + if(msg !== null) + this.properties.msg = msg; + console.log(msg); +} + +Console.prototype.onGetInputs = function() +{ + return [["log",LiteGraph.ACTION],["warn",LiteGraph.ACTION],["error",LiteGraph.ACTION]]; } LiteGraph.registerNodeType("basic/console", Console ); diff --git a/src/nodes/events.js b/src/nodes/events.js new file mode 100644 index 000000000..a48531de3 --- /dev/null +++ b/src/nodes/events.js @@ -0,0 +1,51 @@ +//event related nodes +(function(){ + +//Show value inside the debug console +function DelayEvent() +{ + this.size = [60,20]; + this.addProperty( "time", 1000 ); + this.addInput("event", LiteGraph.ACTION); + this.addOutput("on_time", LiteGraph.EVENT); + + this._pending = []; +} + +DelayEvent.title = "Delay"; +DelayEvent.desc = "Delays one event"; + +DelayEvent.prototype.onAction = function(action, param) +{ + this._pending.push([ this.properties.time, param ]); +} + +DelayEvent.prototype.onExecute = function() +{ + var dt = this.graph.elapsed_time * 1000; //in ms + + for(var i = 0; i < this._pending.length; ++i) + { + var action = this._pending[i]; + action[0] -= dt; + if( action[0] > 0 ) + continue; + + //remove + this._pending.splice(i,1); + --i; + + //trigger + this.trigger(null, action[1]); + } +} + +DelayEvent.prototype.onGetInputs = function() +{ + return [["event",LiteGraph.ACTION]]; +} + +LiteGraph.registerNodeType("events/delay", DelayEvent ); + + +})(); \ No newline at end of file diff --git a/src/nodes/gltextures.js b/src/nodes/gltextures.js index 6eee3b625..ea607726e 100755 --- a/src/nodes/gltextures.js +++ b/src/nodes/gltextures.js @@ -309,6 +309,9 @@ if(typeof(LiteGraph) != "undefined") if(this.flags.collapsed) return; + if(!ctx.webgl) + return; //not working well + var tex = this.getInputData(0); if(!tex) return; @@ -833,7 +836,7 @@ if(typeof(LiteGraph) != "undefined") function LGraphTextureToViewport() { this.addInput("Texture","Texture"); - this.properties = { additive: false, antialiasing: false, disable_alpha: false, gamma: 1.0 }; + this.properties = { additive: false, antialiasing: false, filter: true, disable_alpha: false, gamma: 1.0 }; this.size[0] = 130; } @@ -862,6 +865,7 @@ if(typeof(LiteGraph) != "undefined") if( this.isInputConnected(1) ) gamma = this.getInputData(1); + tex.setParameter( gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST ); if(this.properties.antialiasing) { @@ -1774,38 +1778,23 @@ if(typeof(LiteGraph) != "undefined") this.properties.intensity = intensity; } - gl.disable( gl.BLEND ); - gl.disable( gl.DEPTH_TEST ); - var mesh = Mesh.getScreenQuad(); - var shader = LGraphTextureBlur._shader; - var scale = this.properties.scale || [1,1]; - //blur sometimes needs an aspect correction var aspect = LiteGraph.camera_aspect; if(!aspect && window.gl !== undefined) aspect = gl.canvas.height / gl.canvas.width; if(!aspect) aspect = 1; - - //iterate - var start_texture = tex; aspect = this.properties.preserve_aspect ? aspect : 1; + + var start_texture = tex; + var scale = this.properties.scale || [1,1]; + var origin = start_texture; for(var i = 0; i < iterations; ++i) { - this._temp_texture.drawTo( function() { - start_texture.bind(0); - shader.uniforms({u_texture:0, u_intensity: 1, u_offset: [0, 1/start_texture.height * scale[1] ] }) - .draw(mesh); - }); - - this._temp_texture.bind(0); - this._final_texture.drawTo( function() { - shader.uniforms({u_texture:0, u_intensity: intensity, u_offset: [aspect/start_texture.width * scale[0], 0] }) - .draw(mesh); - }); - start_texture = this._final_texture; + origin.applyBlur( aspect * scale[0] * i, scale[1] * i, intensity, this._temp_texture, this._final_texture ); + origin = this._final_texture; } - + this.setOutputData(0, this._final_texture); } diff --git a/src/nodes/interface.js b/src/nodes/interface.js index 1fe0a6e79..228edd563 100755 --- a/src/nodes/interface.js +++ b/src/nodes/interface.js @@ -1,6 +1,65 @@ //widgets (function(){ + /* Button ****************/ + + function WidgetButton() + { + this.addOutput( "clicked", LiteGraph.EVENT ); + this.addProperty( "text","" ); + this.addProperty( "font","40px Arial" ); + this.addProperty( "message", "" ); + this.size = [64,84]; + } + + WidgetButton.title = "Button"; + WidgetButton.desc = "Triggers an event"; + + WidgetButton.prototype.onDrawForeground = function(ctx) + { + if(this.flags.collapsed) + return; + + //ctx.font = "40px Arial"; + //ctx.textAlign = "center"; + ctx.fillStyle = "black"; + ctx.fillRect(1,1,this.size[0] - 3, this.size[1] - 3); + ctx.fillStyle = "#AAF"; + ctx.fillRect(0,0,this.size[0] - 3, this.size[1] - 3); + ctx.fillStyle = this.clicked ? "white" : (this.mouseOver ? "#668" : "#334"); + ctx.fillRect(1,1,this.size[0] - 4, this.size[1] - 4); + + if( this.properties.text || this.properties.text === 0 ) + { + ctx.textAlign = "center"; + ctx.fillStyle = this.clicked ? "black" : "white"; + if( this.properties.font ) + ctx.font = this.properties.font; + ctx.fillText(this.properties.text, this.size[0] * 0.5, this.size[1] * 0.85 ); + ctx.textAlign = "left"; + } + } + + WidgetButton.prototype.onMouseDown = function(e, local_pos) + { + if(local_pos[0] > 1 && local_pos[1] > 1 && local_pos[0] < (this.size[0] - 2) && local_pos[1] < (this.size[1] - 2) ) + { + this.clicked = true; + this.trigger( "clicked", this.properties.message ); + return true; + } + } + + WidgetButton.prototype.onMouseUp = function(e) + { + this.clicked = false; + } + + + LiteGraph.registerNodeType("widget/button", WidgetButton ); + + /* Knob ****************/ + function WidgetKnob() { this.addOutput("",'number'); diff --git a/src/nodes/math.js b/src/nodes/math.js index e2677ad1d..93b768d69 100755 --- a/src/nodes/math.js +++ b/src/nodes/math.js @@ -85,7 +85,12 @@ function MathRange() { this.addInput("in","number",{locked:true}); this.addOutput("out","number",{locked:true}); - this.properties = { "in": 0, in_min:0, in_max:1, out_min: 0, out_max: 1 }; + + this.addProperty( "in", 0 ); + this.addProperty( "in_min", 0 ); + this.addProperty( "in_max", 1 ); + this.addProperty( "out_min", 0 ); + this.addProperty( "out_max", 1 ); } MathRange.title = "Range"; @@ -136,7 +141,8 @@ LiteGraph.registerNodeType("math/range", MathRange); function MathRand() { this.addOutput("value","number"); - this.properties = { min:0, max:1 }; + this.addProperty( "min", 0 ); + this.addProperty( "max", 1 ); this.size = [60,20]; } @@ -182,7 +188,8 @@ function MathClamp() this.addInput("in","number"); this.addOutput("out","number"); this.size = [60,20]; - this.properties = {min:0, max:1}; + this.addProperty( "min", 0 ); + this.addProperty( "max", 1 ); } MathClamp.title = "Clamp"; @@ -310,7 +317,7 @@ function MathScale() this.addInput("in","number",{label:""}); this.addOutput("out","number",{label:""}); this.size = [60,20]; - this.properties = {"factor":1}; + this.addProperty( "factor", 1 ); } MathScale.title = "Scale"; @@ -326,18 +333,73 @@ MathScale.prototype.onExecute = function() LiteGraph.registerNodeType("math/scale", MathScale ); +//Math clamp +function MathAverageFilter() +{ + this.addInput("in","number"); + this.addOutput("out","number"); + this.size = [60,20]; + this.addProperty( "samples", 10 ); + this._values = new Float32Array(10); + this._current = 0; +} + +MathAverageFilter.title = "Average"; +MathAverageFilter.desc = "Average Filter"; + +MathAverageFilter.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) + v = 0; + + var num_samples = this._values.length; + + this._values[ this._current % num_samples ] = v; + this._current += 1; + if(this._current > num_samples) + this._current = 0; + + var avr = 0; + for(var i = 0; i < num_samples; ++i) + avr += this._values[i]; + + this.setOutputData( 0, avr / num_samples ); +} + +MathAverageFilter.prototype.onPropertyChanged = function( name, value ) +{ + if(value < 1) + value = 1; + this.properties.samples = Math.round(value); + var old = this._values; + + this._values = new Float32Array( this.properties.samples ); + if(old.length <= this._values.length ) + this._values.set(old); + else + this._values.set( old.subarray( 0, this._values.length ) ); +} + +LiteGraph.registerNodeType("math/average", MathAverageFilter ); + + //Math operation function MathOperation() { this.addInput("A","number"); this.addInput("B","number"); this.addOutput("=","number"); - this.properties = {A:1.0, B:1.0, OP:"+"}; + this.addProperty( "A", 1 ); + this.addProperty( "B", 1 ); + this.addProperty( "OP", "+", "string", { values: MathOperation.values } ); } +MathOperation.values = ["+","-","*","/","%","^"]; + MathOperation.title = "Operation"; MathOperation.desc = "Easy math operators"; -MathOperation["@OP"] = { type:"enum", title: "operation", values:["+","-","*","/","%","^"]}; +MathOperation["@OP"] = { type:"enum", title: "operation", values: MathOperation.values }; MathOperation.prototype.setValue = function(v) @@ -365,16 +427,28 @@ MathOperation.prototype.onExecute = function() { case '+': result = A+B; break; case '-': result = A-B; break; + case 'x': + case 'X': + case '*': result = A*B; break; case '/': result = A/B; break; case '%': result = A%B; break; case '^': result = Math.pow(A,B); break; + default: + console.warn("Unknown operation: " + this.properties.OP); } this.setOutputData(0, result ); } MathOperation.prototype.onDrawBackground = function(ctx) { - this.outputs[0].label = "A" + this.properties.OP + "B"; + if(this.flags.collapsed) + return; + + ctx.font = "40px Arial"; + ctx.fillStyle = "black"; + ctx.textAlign = "center"; + ctx.fillText(this.properties.OP, this.size[0] * 0.5, this.size[1] * 0.5 + LiteGraph.NODE_TITLE_HEIGHT ); + ctx.textAlign = "left"; } LiteGraph.registerNodeType("math/operation", MathOperation ); @@ -387,7 +461,8 @@ function MathCompare() this.addInput( "B","number" ); this.addOutput("A==B","boolean"); this.addOutput("A!=B","boolean"); - this.properties = {A:0,B:0}; + this.addProperty( "A", 0 ); + this.addProperty( "B", 0 ); } MathCompare.title = "Compare"; @@ -437,11 +512,15 @@ function MathCondition() this.addInput("A","number"); this.addInput("B","number"); this.addOutput("out","boolean"); - this.properties = { A:0, B:1, OP:">" }; + this.addProperty( "A", 1 ); + this.addProperty( "B", 1 ); + this.addProperty( "OP", ">", "string", { values: MathCondition.values } ); + this.size = [60,40]; } -MathCondition["@OP"] = { type:"enum", title: "operation", values:[">","<","==","!=","<=",">="]}; +MathCondition.values = [">","<","==","!=","<=",">="]; +MathCondition["@OP"] = { type:"enum", title: "operation", values: MathCondition.values }; MathCondition.title = "Condition"; MathCondition.desc = "evaluates condition between A and B"; @@ -481,7 +560,8 @@ function MathAccumulate() { this.addInput("inc","number"); this.addOutput("total","number"); - this.properties = { increment: 0, value: 0 }; + this.addProperty( "increment", 1 ); + this.addProperty( "value", 0 ); } MathAccumulate.title = "Accumulate"; @@ -489,6 +569,9 @@ MathAccumulate.desc = "Increments a value every time"; MathAccumulate.prototype.onExecute = function() { + if(this.properties.value === null) + this.properties.value = 0; + var inc = this.getInputData(0); if(inc !== null) this.properties.value += inc; @@ -504,7 +587,9 @@ function MathTrigonometry() { this.addInput("v","number"); this.addOutput("sin","number"); - this.properties = {amplitude:1.0, offset: 0}; + + this.addProperty( "amplitude", 1 ); + this.addProperty( "offset", 0 ); this.bgImageUrl = "nodes/imgs/icon-sin.png"; } @@ -515,6 +600,8 @@ MathTrigonometry.filter = "shader"; MathTrigonometry.prototype.onExecute = function() { var v = this.getInputData(0); + if(v == null) + v = 0; var amplitude = this.properties["amplitude"]; var slot = this.findInputSlot("amplitude"); if(slot != -1) @@ -778,12 +865,35 @@ LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4 ); if(window.glMatrix) { + function Math3DQuaternion() + { + this.addOutput("quat","quat"); + this.properties = { x:0, y:0, z:0, w: 1 }; + this._value = quat.create(); + } + + Math3DQuaternion.title = "Quaternion"; + Math3DQuaternion.desc = "quaternion"; + + Math3DQuaternion.prototype.onExecute = function() + { + this._value[0] = this.properties.x; + this._value[1] = this.properties.y; + this._value[2] = this.properties.z; + this._value[3] = this.properties.w; + this.setOutputData( 0, this._value ); + } + + LiteGraph.registerNodeType("math3d/quaternion", Math3DQuaternion ); + function Math3DRotation() { this.addInputs([["degrees","number"],["axis","vec3"]]); this.addOutput("quat","quat"); this.properties = { angle:90.0, axis: vec3.fromValues(0,1,0) }; + + this._value = quat.create(); } Math3DRotation.title = "Rotation"; @@ -796,7 +906,7 @@ if(window.glMatrix) var axis = this.getInputData(1); if(axis == null) axis = this.properties.axis; - var R = quat.setAxisAngle(quat.create(), axis, angle * 0.0174532925 ); + var R = quat.setAxisAngle( this._value, axis, angle * 0.0174532925 ); this.setOutputData( 0, R ); } @@ -834,6 +944,8 @@ if(window.glMatrix) { this.addInputs( [["A","quat"],["B","quat"]] ); this.addOutput( "A*B","quat" ); + + this._value = quat.create(); } Math3DMultQuat.title = "Mult. Quat"; @@ -846,12 +958,43 @@ if(window.glMatrix) var B = this.getInputData(1); if(B == null) return; - var R = quat.multiply(quat.create(), A,B); + var R = quat.multiply( this._value, A, B ); this.setOutputData( 0, R ); } LiteGraph.registerNodeType("math3d/mult-quat", Math3DMultQuat ); + + function Math3DQuatSlerp() + { + this.addInputs( [["A","quat"],["B","quat"],["factor","number"]] ); + this.addOutput( "slerp","quat" ); + this.addProperty( "factor", 0.5 ); + + this._value = quat.create(); + } + + Math3DQuatSlerp.title = "Quat Slerp"; + Math3DQuatSlerp.desc = "quaternion spherical interpolation"; + + Math3DQuatSlerp.prototype.onExecute = function() + { + var A = this.getInputData(0); + if(A == null) + return; + var B = this.getInputData(1); + if(B == null) + return; + var factor = this.properties.factor; + if( this.getInputData(2) != null ) + factor = this.getInputData(2); + + var R = quat.slerp( this._value, A, B, factor ); + this.setOutputData( 0, R ); + } + + LiteGraph.registerNodeType("math3d/quat-slerp", Math3DQuatSlerp ); + } //glMatrix })(); \ No newline at end of file diff --git a/src/nodes/midi.js b/src/nodes/midi.js new file mode 100644 index 000000000..405a59767 --- /dev/null +++ b/src/nodes/midi.js @@ -0,0 +1,614 @@ +(function( global ) +{ + +function MIDIEvent( data ) +{ + this.channel = 0; + this.cmd = 0; + + if(data) + this.setup(data) + else + this.data = [0,0,0]; +} + +MIDIEvent.prototype.setup = function( raw_data ) +{ + this.data = raw_data; + + var midiStatus = raw_data[0]; + this.status = midiStatus; + + var midiCommand = midiStatus & 0xF0; + + if(midiStatus >= 0xF0) + this.cmd = midiStatus; + else + this.cmd = midiCommand; + + if(this.cmd == MIDIEvent.NOTEON && this.velocity == 0) + this.cmd = MIDIEvent.NOTEOFF; + + this.cmd_str = MIDIEvent.commands[ this.cmd ] || ""; + + if ( midiCommand >= MIDIEvent.NOTEON || midiCommand <= MIDIEvent.NOTEOFF ) { + this.channel = midiStatus & 0x0F; + } +} + +Object.defineProperty( MIDIEvent.prototype, "velocity", { + get: function() { + if(this.cmd == MIDIEvent.NOTEON) + return this.data[2]; + return -1; + }, + set: function(v) { + this.data[2] = v; // v / 127; + }, + enumerable: true +}); + +MIDIEvent.notes = ["A","A#","B","C","C#","D","D#","E","F","F#","G","G#"]; + +//returns HZs +MIDIEvent.prototype.getPitch = function() +{ + return Math.pow(2, (this.data[1] - 69) / 12 ) * 440; +} + +MIDIEvent.computePitch = function( note ) +{ + return Math.pow(2, (note - 69) / 12 ) * 440; +} + + +//not tested, there is a formula missing here +MIDIEvent.prototype.getPitchBend = function() +{ + return this.data[1] + (this.data[2] << 7) - 8192; +} + +MIDIEvent.computePitchBend = function(v1,v2) +{ + return v1 + (v2 << 7) - 8192; +} + +MIDIEvent.prototype.setCommandFromString = function( str ) +{ + this.cmd = MIDIEvent.computeCommandFromString(str); +} + +MIDIEvent.computeCommandFromString = function( str ) +{ + if(!str) + return 0; + + if(str && str.constructor === Number) + return str; + + str = str.toUpperCase(); + switch( str ) + { + case "NOTE ON": + case "NOTEON": return MIDIEvent.NOTEON; break; + case "NOTE OFF": + case "NOTEOFF": return MIDIEvent.NOTEON; break; + case "KEY PRESSURE": + case "KEYPRESSURE": return MIDIEvent.KEYPRESSURE; break; + case "CONTROLLER CHANGE": + case "CONTROLLERCHANGE": + case "CC": return MIDIEvent.CONTROLLERCHANGE; break; + case "PROGRAM CHANGE": + case "PROGRAMCHANGE": + case "PC": return MIDIEvent.PROGRAMCHANGE; break; + case "CHANNEL PRESSURE": + case "CHANNELPRESSURE": return MIDIEvent.CHANNELPRESSURE; break; + case "PITCH BEND": + case "PITCHBEND": return MIDIEvent.PITCHBEND; break; + case "TIME TICK": + case "TIMETICK": return MIDIEvent.TIMETICK; break; + default: return Number(str); //asume its a hex code + } +} + +MIDIEvent.toNoteString = function(d) +{ + var note = d - 21; + var octave = d - 24; + note = note % 12; + if(note < 0) + note = 12 + note; + return MIDIEvent.notes[ note ] + Math.floor(octave / 12 + 1); +} + +MIDIEvent.prototype.toString = function() +{ + var str = "" + this.channel + ". " ; + switch( this.cmd ) + { + case MIDIEvent.NOTEON: str += "NOTEON " + MIDIEvent.toNoteString( this.data[1] ); break; + case MIDIEvent.NOTEOFF: str += "NOTEOFF " + MIDIEvent.toNoteString( this.data[1] ); break; + case MIDIEvent.CONTROLLERCHANGE: str += "CC " + this.data[1] + " " + this.data[2]; break; + case MIDIEvent.PROGRAMCHANGE: str += "PC " + this.data[1]; break; + case MIDIEvent.PITCHBEND: str += "PITCHBEND " + this.getPitchBend(); break; + case MIDIEvent.KEYPRESSURE: str += "KEYPRESS " + this.data[1]; break; + } + + return str; +} + +MIDIEvent.prototype.toHexString = function() +{ + var str = ""; + for(var i = 0; i < this.data.length; i++) + str += this.data[i].toString(16) + " "; +} + +MIDIEvent.NOTEOFF = 0x80; +MIDIEvent.NOTEON = 0x90; +MIDIEvent.KEYPRESSURE = 0xA0; +MIDIEvent.CONTROLLERCHANGE = 0xB0; +MIDIEvent.PROGRAMCHANGE = 0xC0; +MIDIEvent.CHANNELPRESSURE = 0xD0; +MIDIEvent.PITCHBEND = 0xE0; +MIDIEvent.TIMETICK = 0xF8; + +MIDIEvent.commands = { + 0x80: "note off", + 0x90: "note on", + 0xA0: "key pressure", + 0xB0: "controller change", + 0xC0: "program change", + 0xD0: "channel pressure", + 0xE0: "pitch bend", + 0xF0: "system", + 0xF2: "Song pos", + 0xF3: "Song select", + 0xF6: "Tune request", + 0xF8: "time tick", + 0xFA: "Start Song", + 0xFB: "Continue Song", + 0xFC: "Stop Song", + 0xFE: "Sensing", + 0xFF: "Reset" +} + +//MIDI wrapper +function MIDIInterface( on_ready, on_error ) +{ + if(!navigator.requestMIDIAccess) + { + this.error = "not suppoorted"; + if(on_error) + on_error("Not supported"); + else + console.error("MIDI NOT SUPPORTED, enable by chrome://flags"); + return; + } + + this.on_ready = on_ready; + + navigator.requestMIDIAccess().then( this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this) ); +} + +MIDIInterface.MIDIEvent = MIDIEvent; + +MIDIInterface.prototype.onMIDISuccess = function(midiAccess) +{ + console.log( "MIDI ready!" ); + console.log( midiAccess ); + this.midi = midiAccess; // store in the global (in real usage, would probably keep in an object instance) + this.updatePorts(); + + if (this.on_ready) + this.on_ready(this); +} + +MIDIInterface.prototype.updatePorts = function() +{ + var midi = this.midi; + this.input_ports = midi.inputs; + var num = 0; + for (var i = 0; i < this.input_ports.size; ++i) { + var input = this.input_ports.get(i); + console.log( "Input port [type:'" + input.type + "'] id:'" + input.id + + "' manufacturer:'" + input.manufacturer + "' name:'" + input.name + + "' version:'" + input.version + "'" ); + num++; + } + this.num_input_ports = num; + + + num = 0; + this.output_ports = midi.outputs; + for (var i = 0; i < this.output_ports.size; ++i) { + var output = this.output_ports.get(i); + console.log( "Output port [type:'" + output.type + "'] id:'" + output.id + + "' manufacturer:'" + output.manufacturer + "' name:'" + output.name + + "' version:'" + output.version + "'" ); + num++; + } + this.num_output_ports = num; +} + +MIDIInterface.prototype.onMIDIFailure = function(msg) +{ + console.error( "Failed to get MIDI access - " + msg ); +} + +MIDIInterface.prototype.openInputPort = function( port, callback) +{ + var input_port = this.input_ports.get( port ); + if(!input_port) + return false; + + input_port.onmidimessage = function(a) { + var midi_event = new MIDIEvent(a.data); + if(callback) + callback(a.data, midi_event ); + if(MIDIInterface.on_message) + MIDIInterface.on_message( a.data, midi_event ); + } + console.log("port open: ", input_port); + return true; +} + +MIDIInterface.parseMsg = function(data) +{ + +} + +MIDIInterface.prototype.sendMIDI = function( port, midi_data ) +{ + if( !midi_data ) + return; + + var output_port = this.output_ports.get(port); + if(!output_port) + return; + + if( midi_data.constructor === MIDIEvent) + output_port.send( midi_data.data ); + else + output_port.send( midi_data ); +} + + + +function LGMIDIIn() +{ + this.addOutput( "on_midi", LiteGraph.EVENT ); + this.addOutput( "out", "midi" ); + this.properties = {port: 0}; + this._last_midi_event = null; + this._current_midi_event = null; + + var that = this; + new MIDIInterface( function( midi ){ + //open + that._midi = midi; + if(that._waiting) + that.onStart(); + that._waiting = false; + }); +} + +LGMIDIIn.MIDIInterface = MIDIInterface; + +LGMIDIIn.title = "MIDI Input"; +LGMIDIIn.desc = "Reads MIDI from a input port"; + +LGMIDIIn.prototype.getPropertyInfo = function(name) +{ + if(!this._midi) + return; + + if(name == "port") + { + var values = {}; + for (var i = 0; i < this._midi.input_ports.size; ++i) + { + var input = this._midi.input_ports.get(i); + values[i] = i + ".- " + input.name + " version:" + input.version; + } + return { type: "enum", values: values }; + } +} + +LGMIDIIn.prototype.onStart = function() +{ + if(this._midi) + this._midi.openInputPort( this.properties.port, this.onMIDIEvent.bind(this) ); + else + this._waiting = true; +} + +LGMIDIIn.prototype.onMIDIEvent = function( data, midi_event ) +{ + this._last_midi_event = midi_event; + + this.trigger( "on_midi", midi_event ); + if(midi_event.cmd == MIDIEvent.NOTEON) + this.trigger( "on_noteon", midi_event ); + else if(midi_event.cmd == MIDIEvent.NOTEOFF) + this.trigger( "on_noteoff", midi_event ); + else if(midi_event.cmd == MIDIEvent.CONTROLLERCHANGE) + this.trigger( "on_cc", midi_event ); + else if(midi_event.cmd == MIDIEvent.PROGRAMCHANGE) + this.trigger( "on_pc", midi_event ); + else if(midi_event.cmd == MIDIEvent.PITCHBEND) + this.trigger( "on_pitchbend", midi_event ); +} + +LGMIDIIn.prototype.onExecute = function() +{ + if(this.outputs) + { + var last = this._last_midi_event; + for(var i = 0; i < this.outputs.length; ++i) + { + var output = this.outputs[i]; + var v = null; + switch (output.name) + { + case "last_midi": v = last; break; + default: + continue; + } + this.setOutputData( i, v ); + } + } +} + +LGMIDIIn.prototype.onGetOutputs = function() { + return [ + ["last_midi","midi"], + ["on_midi",LiteGraph.EVENT], + ["on_noteon",LiteGraph.EVENT], + ["on_noteoff",LiteGraph.EVENT], + ["on_cc",LiteGraph.EVENT], + ["on_pc",LiteGraph.EVENT], + ["on_pitchbend",LiteGraph.EVENT] + ]; +} + +LiteGraph.registerNodeType("midi/input", LGMIDIIn); + + +function LGMIDIOut() +{ + this.addInput( "send", LiteGraph.EVENT ); + this.properties = {port: 0}; + + var that = this; + new MIDIInterface( function( midi ){ + that._midi = midi; + }); +} + +LGMIDIOut.MIDIInterface = MIDIInterface; + +LGMIDIOut.title = "MIDI Output"; +LGMIDIOut.desc = "Sends MIDI to output channel"; + +LGMIDIOut.prototype.getPropertyInfo = function(name) +{ + if(!this._midi) + return; + + if(name == "port") + { + var values = {}; + for (var i = 0; i < this._midi.output_ports.size; ++i) + { + var output = this._midi.output_ports.get(i); + values[i] = i + ".- " + output.name + " version:" + output.version; + } + return { type: "enum", values: values }; + } +} + + +LGMIDIOut.prototype.onAction = function(event, midi_event ) +{ + console.log(midi_event); + if(!this._midi) + return; + if(event == "send") + this._midi.sendMIDI( this.port, midi_event ); + this.trigger("midi",midi_event); +} + +LGMIDIOut.prototype.onGetInputs = function() { + return [["send",LiteGraph.ACTION]]; +} + +LGMIDIOut.prototype.onGetOutputs = function() { + return [["on_midi",LiteGraph.EVENT]]; +} + +LiteGraph.registerNodeType("midi/output", LGMIDIOut); + + +function LGMIDIShow() +{ + this.addInput( "on_midi", LiteGraph.EVENT ); + this._str = ""; + this.size = [200,40] +} + +LGMIDIShow.title = "MIDI Show"; +LGMIDIShow.desc = "Shows MIDI in the graph"; + +LGMIDIShow.prototype.onAction = function(event, midi_event ) +{ + if(!midi_event) + return; + if(midi_event.constructor === MIDIEvent) + this._str = midi_event.toString(); + else + this._str = "???"; +} + +LGMIDIShow.prototype.onDrawForeground = function( ctx ) +{ + if( !this._str ) + return; + + ctx.font = "30px Arial"; + ctx.fillText( this._str, 10, this.size[1] * 0.8 ); +} + +LGMIDIShow.prototype.onGetInputs = function() { + return [["in",LiteGraph.ACTION]]; +} + +LGMIDIShow.prototype.onGetOutputs = function() { + return [["on_midi",LiteGraph.EVENT]]; +} + +LiteGraph.registerNodeType("midi/show", LGMIDIShow); + + + +function LGMIDIFilter() +{ + this.properties = { + channel: -1, + cmd: -1, + min_value: -1, + max_value: -1 + }; + + this.addInput( "in", LiteGraph.EVENT ); + this.addOutput( "on_midi", LiteGraph.EVENT ); +} + +LGMIDIFilter.title = "MIDI Filter"; +LGMIDIFilter.desc = "Filters MIDI messages"; + +LGMIDIFilter.prototype.onAction = function(event, midi_event ) +{ + if(!midi_event || midi_event.constructor !== MIDIEvent) + return; + + if( this.properties.channel != -1 && midi_event.channel != this.properties.channel) + return; + if(this.properties.cmd != -1 && midi_event.cmd != this.properties.cmd) + return; + if(this.properties.min_value != -1 && midi_event.data[1] < this.properties.min_value) + return; + if(this.properties.max_value != -1 && midi_event.data[1] > this.properties.max_value) + return; + this.trigger("on_midi",midi_event); +} + +LiteGraph.registerNodeType("midi/filter", LGMIDIFilter); + + +function LGMIDIEvent() +{ + this.properties = { + channel: 0, + cmd: "CC", + value1: 1, + value2: 1 + }; + + this.addInput( "send", LiteGraph.EVENT ); + this.addInput( "assign", LiteGraph.EVENT ); + this.addOutput( "on_midi", LiteGraph.EVENT ); +} + +LGMIDIEvent.title = "MIDIEvent"; +LGMIDIEvent.desc = "Create a MIDI Event"; + +LGMIDIEvent.prototype.onAction = function( event, midi_event ) +{ + if(event == "assign") + { + this.properties.channel = midi_event.channel; + this.properties.cmd = midi_event.cmd; + this.properties.value1 = midi_event.data[1]; + this.properties.value2 = midi_event.data[2]; + return; + } + + //send + var midi_event = new MIDIEvent(); + midi_event.channel = this.properties.channel; + if(this.properties.cmd && this.properties.cmd.constructor === String) + midi_event.setCommandFromString( this.properties.cmd ); + else + midi_event.cmd = this.properties.cmd; + midi_event.data[0] = midi_event.cmd | midi_event.channel; + midi_event.data[1] = Number(this.properties.value1); + midi_event.data[2] = Number(this.properties.value2); + this.trigger("on_midi",midi_event); +} + +LGMIDIEvent.prototype.onExecute = function() +{ + var props = this.properties; + + if(this.outputs) + { + for(var i = 0; i < this.outputs.length; ++i) + { + var output = this.outputs[i]; + var v = null; + switch (output.name) + { + case "midi": + v = new MIDIEvent(); + v.setup([ props.cmd, props.value1, props.value2 ]); + v.channel = props.channel; + break; + case "command": v = props.cmd; break; + case "note": v = (props.cmd == MIDIEvent.NOTEON || props.cmd == MIDIEvent.NOTEOFF) ? props.value1 : NULL; break; + case "velocity": v = props.cmd == MIDIEvent.NOTEON ? props.value2 : NULL; break; + case "pitch": v = props.cmd == MIDIEvent.NOTEON ? MIDIEvent.computePitch( props.value1 ) : null; break; + case "pitchbend": v = props.cmd == MIDIEvent.PITCHBEND ? MIDIEvent.computePitchBend( props.value1, props.value2 ) : null; break; + default: + continue; + } + if(v !== null) + this.setOutputData( i, v ); + } + } +} + +LGMIDIEvent.prototype.onPropertyChanged = function(name,value) +{ + if(name == "cmd") + this.properties.cmd = MIDIEvent.computeCommandFromString( value ); +} + + +LGMIDIEvent.prototype.onGetOutputs = function() { + return [ + ["midi","midi"], + ["on_midi",LiteGraph.EVENT], + ["command","number"], + ["note","number"], + ["velocity","number"], + ["pitch","number"], + ["pitchbend","number"] + ]; +} + + +LiteGraph.registerNodeType("midi/event", LGMIDIEvent); + + + + +function now() { return window.performance.now() } + + + + + + + +})( window ); \ No newline at end of file diff --git a/utils/deploy_files.txt b/utils/deploy_files.txt index 54e252603..24ca35587 100755 --- a/utils/deploy_files.txt +++ b/utils/deploy_files.txt @@ -1,5 +1,6 @@ ../src/litegraph.js ../src/nodes/base.js +../src/nodes/events.js ../src/nodes/interface.js ../src/nodes/input.js ../src/nodes/math.js @@ -7,3 +8,4 @@ ../src/nodes/image.js ../src/nodes/gltextures.js ../src/nodes/glfx.js +../src/nodes/midi.js