=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\tuvcode must be vec2, is optional
\t\t\tuv: 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\tuvcode must be vec2, is optional
\t\t\tuv: 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