Merge remote-tracking branch 'upstream/master'

Catching up to external updates and checking in
array fix (this time without build artifacts).
This commit is contained in:
Phil Mercurio
2020-07-25 12:04:21 -07:00
62 changed files with 18770 additions and 35077 deletions

9
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,9 @@
# Contribution Rules
There are some simple rules that everyone should follow:
### Do not commit files from bulid folder
> I usually have horrible merge conflicts when I upload the build version that take me too much time to solve, but I want to keep the build version in the repo, so I guess it would be better if only one of us does the built, which would be me.
> https://github.com/jagenjo/litegraph.js/pull/155#issuecomment-656602861
Those files will be updated by owner.

View File

@@ -1,15 +1,15 @@
# litegraph.js
A library in Javascript to create graphs in the browser similar to Unreal Blueprints. Nodes can be programmed easily and it includes an editor to construct the graphs.
A library in Javascript to create graphs in the browser similar to Unreal Blueprints. Nodes can be programmed easily and it includes an editor to construct and tests the graphs.
It can be integrated easily in any existing web applications and graphs can be run without the need of the editor.
Try it in the [demo site](https://tamats.com/projects/litegraph/demo).
Try it in the [demo site](https://tamats.com/projects/litegraph/editor).
![Node Graph](imgs/node_graph_example.png "WebGLStudio")
## Features
- Renders on Canvas2D (zoom in, zoom out, panning, can be used inside a WebGLTexture)
- Renders on Canvas2D (zoom in/out and panning, easy to render complex interfaces, can be used inside a WebGLTexture)
- Easy to use editor (searchbox, keyboard shortcuts, multiple selection, context menu, ...)
- Optimized to support hundreds of nodes per graph (on editor but also on execution)
- Customizable theme (colors, shapes, background)
@@ -19,6 +19,7 @@ Try it in the [demo site](https://tamats.com/projects/litegraph/demo).
- Graphs can be executed in NodeJS
- Highly customizable nodes (color, shape, slots vertical or horizontal, widgets, custom rendering)
- Easy to integrate in any JS application (one single file, no dependencies)
- Typescript support
## Nodes provided
Although it is easy to create new node types, LiteGraph comes with some default nodes that could be useful for many cases:
@@ -35,7 +36,7 @@ You can install it using npm
npm install litegraph.js
```
Or downloading the ```build/litegraph.js``` version from this repository.
Or downloading the ```build/litegraph.js``` and ```css/litegraph.css``` version from this repository.
## First project ##
@@ -179,6 +180,8 @@ You can write any feedback to javi.agenjo@gmail.com
- rappestad
- InventivetalentDev
- NateScarlet
- coderofsalvation
- ilyabesk

File diff suppressed because it is too large Load Diff

10005
build/litegraph.min.js vendored

File diff suppressed because it is too large Load Diff

View File

@@ -93,7 +93,7 @@
/* BUTTONS **********************/
.litegraph-editor button {
.litegraph-editor .btn {
/*font-family: "Metro Light";*/
color: #ccc;
font-size: 20px;
@@ -181,7 +181,7 @@
display: inline-block;
width: 90px;
height: 15px;
background-image: url("../demo/imgs/load-progress-empty.png");
background-image: url("../editor/imgs/load-progress-empty.png");
}
.litegraph-editor .cpuload .fgload,
@@ -190,39 +190,24 @@
width: 4px;
height: 15px;
max-width: 90px;
background-image: url("../demo/imgs/load-progress-full.png");
background-image: url("../editor/imgs/load-progress-full.png");
}
.litegraph-editor .dialog {
position: absolute;
top: 50%;
left: 50%;
margin-top: -150px;
margin-left: -200px;
background-color: #151515;
min-width: 400px;
min-height: 300px;
box-shadow: 0 0 2px black;
.litegraph-editor textarea.code, .litegraph-editor div.code {
height: 100%;
width: 100%;
background-color: black;
padding: 4px;
font: 16px monospace;
overflow: auto;
resize: none;
outline: none;
}
.litegraph-editor .dialog .dialog-header,
.litegraph-editor .dialog .dialog-footer {
height: 40px;
.litegraph-editor .codeflask {
background-color: #2a2a2a;
}
.litegraph-editor .dialog .dialog-header .dialog-title {
font: 20px "Arial";
margin: 4px;
padding: 4px 10px;
display: inline-block;
}
.litegraph-editor .dialog .dialog-content {
height: calc(100% - 40px);
width: calc(100% - 10px);
background-color: black;
margin: 4px;
display: inline-block;
}
.litegraph-editor .codeflask textarea {
opacity: 0;
}

View File

@@ -5,6 +5,7 @@
user-select: none;
-moz-user-select: none;
-webkit-user-select: none;
outline: none;
}
.litegraph.litecontextmenu {
@@ -207,6 +208,233 @@
color: black;
}
/* DIALOGs ******/
.litegraph .dialog {
position: absolute;
top: 50%;
left: 50%;
margin-top: -150px;
margin-left: -200px;
background-color: #2A2A2A;
min-width: 400px;
min-height: 200px;
box-shadow: 0 0 4px #111;
border-radius: 6px;
}
.litegraph .dialog.settings {
left: 10px;
top: 10px;
height: calc( 100% - 20px );
margin: auto;
}
.litegraph .dialog .close {
float: right;
margin: 4px;
margin-right: 10px;
cursor: pointer;
font-size: 1.4em;
}
.litegraph .dialog .close:hover {
color: white;
}
.litegraph .dialog .dialog-header {
color: #AAA;
border-bottom: 1px solid #161616;
}
.litegraph .dialog .dialog-header { height: 40px; }
.litegraph .dialog .dialog-footer { height: 50px; padding: 10px; border-top: 1px solid #1a1a1a;}
.litegraph .dialog .dialog-header .dialog-title {
font: 20px "Arial";
margin: 4px;
padding: 4px 10px;
display: inline-block;
}
.litegraph .dialog .dialog-content {
height: calc(100% - 90px);
width: 100%;
min-height: 100px;
display: inline-block;
color: #AAA;
/*background-color: black;*/
}
.litegraph .dialog .dialog-content h3 {
margin: 10px;
}
.litegraph .dialog .dialog-content .connections {
flex-direction: row;
}
.litegraph .dialog .dialog-content .connections .connections_side {
width: calc(50% - 5px);
min-height: 100px;
background-color: black;
display: flex;
}
.litegraph .dialog .node_type {
font-size: 1.2em;
display: block;
margin: 10px;
}
.litegraph .dialog .node_desc {
opacity: 0.5;
display: block;
margin: 10px;
}
.litegraph .dialog .separator {
display: block;
width: calc( 100% - 4px );
height: 1px;
border-top: 1px solid #000;
border-bottom: 1px solid #333;
margin: 10px 2px;
padding: 0;
}
.litegraph .dialog .property {
margin-bottom: 2px;
padding: 4px;
}
.litegraph .dialog .property_name {
color: #737373;
display: inline-block;
text-align: left;
vertical-align: top;
width: 120px;
padding-left: 4px;
overflow: hidden;
}
.litegraph .dialog .property_value {
display: inline-block;
text-align: right;
color: #AAA;
background-color: #1A1A1A;
width: calc( 100% - 122px );
max-height: 300px;
padding: 4px;
padding-right: 12px;
overflow: hidden;
cursor: pointer;
border-radius: 3px;
}
.litegraph .dialog .property_value:hover {
color: white;
}
.litegraph .dialog .property.boolean .property_value {
padding-right: 30px;
}
.litegraph .dialog .btn {
border-radius: 4px;
padding: 4px 20px;
margin-left: 0px;
background-color: #060606;
color: #8e8e8e;
}
.litegraph .dialog .btn:hover {
background-color: #111;
color: #FFF;
}
.litegraph .dialog .btn.delete:hover {
background-color: #F33;
color: black;
}
.litegraph .subgraph_property {
padding: 4px;
}
.litegraph .subgraph_property:hover {
background-color: #333;
}
.litegraph .subgraph_property.extra {
margin-top: 8px;
}
.litegraph .subgraph_property span.name {
font-size: 1.3em;
padding-left: 4px;
}
.litegraph .subgraph_property span.type {
opacity: 0.5;
margin-right: 20px;
padding-left: 4px;
}
.litegraph .subgraph_property span.label {
display: inline-block;
width: 60px;
padding: 0px 10px;
}
.litegraph .subgraph_property input {
width: 140px;
color: #999;
background-color: #1A1A1A;
border-radius: 4px;
border: 0;
margin-right: 10px;
padding: 4px;
padding-left: 10px;
}
.litegraph .subgraph_property button {
background-color: #1c1c1c;
color: #aaa;
border: 0;
border-radius: 2px;
padding: 4px 10px;
cursor: pointer;
}
.litegraph .subgraph_property.extra {
color: #ccc;
}
.litegraph .subgraph_property.extra input {
background-color: #111;
}
.litegraph .bullet_icon {
margin-left: 10px;
border-radius: 10px;
width: 12px;
height: 12px;
background-color: #666;
display: inline-block;
margin-top: 2px;
margin-right: 4px;
transition: background-color 0.1s ease 0s;
-moz-transition: background-color 0.1s ease 0s;
}
.litegraph .bullet_icon:hover {
background-color: #698;
cursor: pointer;
}
/* OLD */
.graphcontextmenu {
@@ -285,6 +513,7 @@
background-color: #333;
font-size: 1.2em;
box-shadow: 0 0 10px black !important;
z-index: 10;
}
.graphdialog.rounded {

View File

Before

Width:  |  Height:  |  Size: 350 B

After

Width:  |  Height:  |  Size: 350 B

View File

Before

Width:  |  Height:  |  Size: 368 B

After

Width:  |  Height:  |  Size: 368 B

View File

Before

Width:  |  Height:  |  Size: 607 B

After

Width:  |  Height:  |  Size: 607 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 346 B

After

Width:  |  Height:  |  Size: 346 B

View File

Before

Width:  |  Height:  |  Size: 344 B

After

Width:  |  Height:  |  Size: 344 B

View File

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 349 B

View File

Before

Width:  |  Height:  |  Size: 330 B

After

Width:  |  Height:  |  Size: 330 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 179 B

After

Width:  |  Height:  |  Size: 179 B

View File

Before

Width:  |  Height:  |  Size: 836 B

After

Width:  |  Height:  |  Size: 836 B

View File

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

Before

Width:  |  Height:  |  Size: 281 B

After

Width:  |  Height:  |  Size: 281 B

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -15,7 +15,10 @@
<script type="text/javascript" src="../external/jquery-1.6.2.min.js"></script>
<script type="text/javascript" src="https://tamats.com/projects/sillyserver/src/sillyclient.js"></script>
<!-- <script type="text/javascript" src="https://unpkg.com/codeflask/build/codeflask.min.js"></script> -->
<script type="text/javascript" src="js/libs/gl-matrix-min.js"></script>
<script type="text/javascript" src="js/libs/audiosynth.js"></script>
<script type="text/javascript" src="js/libs/midi-parser.js"></script>
<script type="text/javascript" src="../src/litegraph.js"></script>
<script type="text/javascript" src="../src/litegraph-editor.js"></script>
@@ -28,6 +31,7 @@
<script type="text/javascript" src="../src/nodes/strings.js"></script>
<script type="text/javascript" src="../src/nodes/interface.js"></script>
<script type="text/javascript" src="../src/nodes/geometry.js"></script>
<script type="text/javascript" src="../src/nodes/graphics.js"></script>
<script type="text/javascript" src="../src/nodes/input.js"></script>
<script type="text/javascript" src="../src/nodes/midi.js"></script>
<script type="text/javascript" src="../src/nodes/audio.js"></script>

View File

@@ -1,6 +1,7 @@
var webgl_canvas = null;
LiteGraph.node_images_path = "../nodes_data/";
var editor = new LiteGraph.Editor("main");
var editor = new LiteGraph.Editor("main",{miniwindow:false});
window.graphcanvas = editor.graphcanvas;
window.graph = editor.graph;
window.addEventListener("resize", function() { editor.graphcanvas.resize(); } );
@@ -10,10 +11,13 @@ window.onbeforeunload = function(){
localStorage.setItem("litegraphg demo backup", data );
}
//enable scripting
LiteGraph.allow_scripts = true;
//create scene selector
var elem = document.createElement("span");
elem.className = "selector";
elem.innerHTML = "Demo <select><option>Empty</option></select> <button id='save'>Save</button><button id='load'>Load</button><button id='download'>Download</button>";
elem.innerHTML = "Demo <select><option>Empty</option></select> <button class='btn' id='save'>Save</button><button class='btn' id='load'>Load</button><button class='btn' id='download'>Download</button> | <button class='btn' id='webgl'>WebGL</button>";
editor.tools.appendChild(elem);
var select = elem.querySelector("select");
select.addEventListener("change", function(e){
@@ -54,6 +58,9 @@ elem.querySelector("#download").addEventListener("click",function(){
setTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url
});
elem.querySelector("#webgl").addEventListener("click", enableWebGL );
function addDemo( name, url )
{
var option = document.createElement("option");
@@ -81,5 +88,79 @@ addDemo("autobackup", function(){
graph.configure( graph_data );
});
//allows to use the WebGL nodes like textures
function enableWebGL()
{
if( webgl_canvas )
{
webgl_canvas.style.display = (webgl_canvas.style.display == "none" ? "block" : "none");
return;
}
var libs = [
"js/libs/gl-matrix-min.js",
"js/libs/litegl.js",
"../src/nodes/gltextures.js",
"../src/nodes/glfx.js",
"../src/nodes/glshaders.js",
"../src/nodes/geometry.js"
];
function fetchJS()
{
if(libs.length == 0)
return on_ready();
var script = null;
script = document.createElement("script");
script.onload = fetchJS;
script.src = libs.shift();
document.head.appendChild(script);
}
fetchJS();
function on_ready()
{
console.log(this.src);
if(!window.GL)
return;
webgl_canvas = document.createElement("canvas");
webgl_canvas.width = 400;
webgl_canvas.height = 300;
webgl_canvas.style.position = "absolute";
webgl_canvas.style.top = "0px";
webgl_canvas.style.right = "0px";
webgl_canvas.style.border = "1px solid #AAA";
webgl_canvas.addEventListener("click", function(){
var rect = webgl_canvas.parentNode.getBoundingClientRect();
if( webgl_canvas.width != rect.width )
{
webgl_canvas.width = rect.width;
webgl_canvas.height = rect.height;
}
else
{
webgl_canvas.width = 400;
webgl_canvas.height = 300;
}
});
var parent = document.querySelector(".editor-area");
parent.appendChild( webgl_canvas );
var gl = GL.create({ canvas: webgl_canvas });
if(!gl)
return;
editor.graph.onBeforeStep = ondraw;
console.log("webgl ready");
function ondraw ()
{
gl.clearColor(0,0,0,0);
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.viewport(0,0,gl.canvas.width, gl.canvas.height );
}
}
}

View File

@@ -91,6 +91,8 @@ function TestWidgetsNode()
this.text = this.addWidget("text","Text", "edit me", function(v){}, {} );
this.toggle = this.addWidget("toggle","Toggle", true, function(v){}, { on: "enabled", off:"disabled"} );
this.button = this.addWidget("button","Button", null, function(v){}, {} );
this.toggle2 = this.addWidget("toggle","Disabled", true, function(v){}, { on: "enabled", off:"disabled"} );
this.toggle2.disabled = true;
this.size = this.computeSize();
this.serialize_widgets = true;
}
@@ -99,7 +101,6 @@ TestWidgetsNode.title = "Widgets";
LiteGraph.registerNodeType("features/widgets", TestWidgetsNode );
//Show value inside the debug console
function TestSpecialNode()
{

28
editor/js/libs/gl-matrix-min.js vendored Normal file

File diff suppressed because one or more lines are too long

13432
editor/js/libs/litegl.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,356 @@
/*
Project Name : midi-parser-js
Project Url : https://github.com/colxi/midi-parser-js/
Author : colxi
Author URL : http://www.colxi.info/
Description : MidiParser library reads .MID binary files, Base64 encoded MIDI Data,
or UInt8 Arrays, and outputs as a readable and structured JS object.
*/
(function(){
'use strict';
/**
* CROSSBROWSER & NODEjs POLYFILL for ATOB() -
* By: https://github.com/MaxArt2501 (modified)
* @param {string} string [description]
* @return {[type]} [description]
*/
const _atob = function(string) {
// base64 character set, plus padding character (=)
let b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
// Regular expression to check formal correctness of base64 encoded strings
let b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
// remove data type signatures at the begining of the string
// eg : "data:audio/mid;base64,"
string = string.replace( /^.*?base64,/ , '');
// atob can work with strings with whitespaces, even inside the encoded part,
// but only \t, \n, \f, \r and ' ', which can be stripped.
string = String(string).replace(/[\t\n\f\r ]+/g, '');
if (!b64re.test(string))
throw new TypeError('Failed to execute _atob() : The string to be decoded is not correctly encoded.');
// Adding the padding if missing, for semplicity
string += '=='.slice(2 - (string.length & 3));
let bitmap, result = '';
let r1, r2, i = 0;
for (; i < string.length;) {
bitmap = b64.indexOf(string.charAt(i++)) << 18 | b64.indexOf(string.charAt(i++)) << 12
| (r1 = b64.indexOf(string.charAt(i++))) << 6 | (r2 = b64.indexOf(string.charAt(i++)));
result += r1 === 64 ? String.fromCharCode(bitmap >> 16 & 255)
: r2 === 64 ? String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255)
: String.fromCharCode(bitmap >> 16 & 255, bitmap >> 8 & 255, bitmap & 255);
}
return result;
};
/**
* [MidiParser description]
* @type {Object}
*/
const MidiParser = {
// debug (bool), when enabled will log in console unimplemented events
// warnings and internal handled errors.
debug: false,
/**
* [parse description]
* @param {[type]} input [description]
* @param {[type]} _callback [description]
* @return {[type]} [description]
*/
parse: function(input, _callback){
if(input instanceof Uint8Array) return MidiParser.Uint8(input);
else if(typeof input === 'string') return MidiParser.Base64(input);
else if(input instanceof HTMLElement && input.type === 'file') return MidiParser.addListener(input , _callback);
else throw new Error('MidiParser.parse() : Invalid input provided');
},
/**
* addListener() should be called in order attach a listener to the INPUT HTML element
* that will provide the binary data automating the conversion, and returning
* the structured data to the provided callback function.
*/
addListener: function(_fileElement, _callback){
if(!File || !FileReader) throw new Error('The File|FileReader APIs are not supported in this browser. Use instead MidiParser.Base64() or MidiParser.Uint8()');
// validate provided element
if( _fileElement === undefined ||
!(_fileElement instanceof HTMLElement) ||
_fileElement.tagName !== 'INPUT' ||
_fileElement.type.toLowerCase() !== 'file'
){
console.warn('MidiParser.addListener() : Provided element is not a valid FILE INPUT element');
return false;
}
_callback = _callback || function(){};
_fileElement.addEventListener('change', function(InputEvt){ // set the 'file selected' event handler
if (!InputEvt.target.files.length) return false; // return false if no elements where selected
console.log('MidiParser.addListener() : File detected in INPUT ELEMENT processing data..');
let reader = new FileReader(); // prepare the file Reader
reader.readAsArrayBuffer(InputEvt.target.files[0]); // read the binary data
reader.onload = function(e){
_callback( MidiParser.Uint8(new Uint8Array(e.target.result))); // encode data with Uint8Array and call the parser
};
});
},
/**
* Base64() : convert baset4 string into uint8 array buffer, before performing the
* parsing subroutine.
*/
Base64 : function(b64String){
b64String = String(b64String);
let raw = _atob(b64String);
let rawLength = raw.length;
let t_array = new Uint8Array(new ArrayBuffer(rawLength));
for(let i=0; i<rawLength; i++) t_array[i] = raw.charCodeAt(i);
return MidiParser.Uint8(t_array) ;
},
/**
* parse() : function reads the binary data, interpreting and spliting each chuck
* and parsing it to a structured Object. When job is finised returns the object
* or 'false' if any error was generated.
*/
Uint8: function(FileAsUint8Array){
let file = {
data: null,
pointer: 0,
movePointer: function(_bytes){ // move the pointer negative and positive direction
this.pointer += _bytes;
return this.pointer;
},
readInt: function(_bytes){ // get integer from next _bytes group (big-endian)
_bytes = Math.min(_bytes, this.data.byteLength-this.pointer);
if (_bytes < 1) return -1; // EOF
let value = 0;
if(_bytes > 1){
for(let i=1; i<= (_bytes-1); i++){
value += this.data.getUint8(this.pointer) * Math.pow(256, (_bytes - i));
this.pointer++;
}
}
value += this.data.getUint8(this.pointer);
this.pointer++;
return value;
},
readStr: function(_bytes){ // read as ASCII chars, the followoing _bytes
let text = '';
for(let char=1; char <= _bytes; char++) text += String.fromCharCode(this.readInt(1));
return text;
},
readIntVLV: function(){ // read a variable length value
let value = 0;
if ( this.pointer >= this.data.byteLength ){
return -1; // EOF
}else if(this.data.getUint8(this.pointer) < 128){ // ...value in a single byte
value = this.readInt(1);
}else{ // ...value in multiple bytes
let FirstBytes = [];
while(this.data.getUint8(this.pointer) >= 128){
FirstBytes.push(this.readInt(1) - 128);
}
let lastByte = this.readInt(1);
for(let dt = 1; dt <= FirstBytes.length; dt++){
value += FirstBytes[FirstBytes.length - dt] * Math.pow(128, dt);
}
value += lastByte;
}
return value;
}
};
file.data = new DataView(FileAsUint8Array.buffer, FileAsUint8Array.byteOffset, FileAsUint8Array.byteLength); // 8 bits bytes file data array
// ** read FILE HEADER
if(file.readInt(4) !== 0x4D546864){
console.warn('Header validation failed (not MIDI standard or file corrupt.)');
return false; // Header validation failed (not MIDI standard or file corrupt.)
}
let headerSize = file.readInt(4); // header size (unused var), getted just for read pointer movement
let MIDI = {}; // create new midi object
MIDI.formatType = file.readInt(2); // get MIDI Format Type
MIDI.tracks = file.readInt(2); // get ammount of track chunks
MIDI.track = []; // create array key for track data storing
let timeDivisionByte1 = file.readInt(1); // get Time Division first byte
let timeDivisionByte2 = file.readInt(1); // get Time Division second byte
if(timeDivisionByte1 >= 128){ // discover Time Division mode (fps or tpf)
MIDI.timeDivision = [];
MIDI.timeDivision[0] = timeDivisionByte1 - 128; // frames per second MODE (1st byte)
MIDI.timeDivision[1] = timeDivisionByte2; // ticks in each frame (2nd byte)
}else MIDI.timeDivision = (timeDivisionByte1 * 256) + timeDivisionByte2;// else... ticks per beat MODE (2 bytes value)
// ** read TRACK CHUNK
for(let t=1; t <= MIDI.tracks; t++){
MIDI.track[t-1] = {event: []}; // create new Track entry in Array
let headerValidation = file.readInt(4);
if ( headerValidation === -1 ) break; // EOF
if(headerValidation !== 0x4D54726B) return false; // Track chunk header validation failed.
file.readInt(4); // move pointer. get chunk size (bytes length)
let e = 0; // init event counter
let endOfTrack = false; // FLAG for track reading secuence breaking
// ** read EVENT CHUNK
let statusByte;
let laststatusByte;
while(!endOfTrack){
e++; // increase by 1 event counter
MIDI.track[t-1].event[e-1] = {}; // create new event object, in events array
MIDI.track[t-1].event[e-1].deltaTime = file.readIntVLV(); // get DELTA TIME OF MIDI event (Variable Length Value)
statusByte = file.readInt(1); // read EVENT TYPE (STATUS BYTE)
if(statusByte === -1) break; // EOF
else if(statusByte >= 128) laststatusByte = statusByte; // NEW STATUS BYTE DETECTED
else{ // 'RUNNING STATUS' situation detected
statusByte = laststatusByte; // apply last loop, Status Byte
file.movePointer(-1); // move back the pointer (cause readed byte is not status byte)
}
//
// ** IS META EVENT
//
if(statusByte === 0xFF){ // Meta Event type
MIDI.track[t-1].event[e-1].type = 0xFF; // assign metaEvent code to array
MIDI.track[t-1].event[e-1].metaType = file.readInt(1); // assign metaEvent subtype
let metaEventLength = file.readIntVLV(); // get the metaEvent length
switch(MIDI.track[t-1].event[e-1].metaType){
case 0x2F: // end of track, has no data byte
case -1: // EOF
endOfTrack = true; // change FLAG to force track reading loop breaking
break;
case 0x01: // Text Event
case 0x02: // Copyright Notice
case 0x03:
case 0x04: // Instrument Name
case 0x05: // Lyrics)
case 0x07: // Cue point // Sequence/Track Name (documentation: http://www.ta7.de/txt/musik/musi0006.htm)
case 0x06: // Marker
MIDI.track[t-1].event[e-1].data = file.readStr(metaEventLength);
break;
case 0x21: // MIDI PORT
case 0x59: // Key Signature
case 0x51: // Set Tempo
MIDI.track[t-1].event[e-1].data = file.readInt(metaEventLength);
break;
case 0x54: // SMPTE Offset
MIDI.track[t-1].event[e-1].data = [];
MIDI.track[t-1].event[e-1].data[0] = file.readInt(1);
MIDI.track[t-1].event[e-1].data[1] = file.readInt(1);
MIDI.track[t-1].event[e-1].data[2] = file.readInt(1);
MIDI.track[t-1].event[e-1].data[3] = file.readInt(1);
MIDI.track[t-1].event[e-1].data[4] = file.readInt(1);
break;
case 0x58: // Time Signature
MIDI.track[t-1].event[e-1].data = [];
MIDI.track[t-1].event[e-1].data[0] = file.readInt(1);
MIDI.track[t-1].event[e-1].data[1] = file.readInt(1);
MIDI.track[t-1].event[e-1].data[2] = file.readInt(1);
MIDI.track[t-1].event[e-1].data[3] = file.readInt(1);
break;
default :
// if user provided a custom interpreter, call it
// and assign to event the returned data
if( this.customInterpreter !== null){
MIDI.track[t-1].event[e-1].data = this.customInterpreter( MIDI.track[t-1].event[e-1].metaType, file, metaEventLength);
}
// if no customInterpretr is provided, or returned
// false (=apply default), perform default action
if(this.customInterpreter === null || MIDI.track[t-1].event[e-1].data === false){
file.readInt(metaEventLength);
MIDI.track[t-1].event[e-1].data = file.readInt(metaEventLength);
if (this.debug) console.info('Unimplemented 0xFF meta event! data block readed as Integer');
}
}
}
//
// IS REGULAR EVENT
//
else{ // MIDI Control Events OR System Exclusive Events
statusByte = statusByte.toString(16).split(''); // split the status byte HEX representation, to obtain 4 bits values
if(!statusByte[1]) statusByte.unshift('0'); // force 2 digits
MIDI.track[t-1].event[e-1].type = parseInt(statusByte[0], 16);// first byte is EVENT TYPE ID
MIDI.track[t-1].event[e-1].channel = parseInt(statusByte[1], 16);// second byte is channel
switch(MIDI.track[t-1].event[e-1].type){
case 0xF:{ // System Exclusive Events
// if user provided a custom interpreter, call it
// and assign to event the returned data
if( this.customInterpreter !== null){
MIDI.track[t-1].event[e-1].data = this.customInterpreter( MIDI.track[t-1].event[e-1].type, file , false);
}
// if no customInterpretr is provided, or returned
// false (=apply default), perform default action
if(this.customInterpreter === null || MIDI.track[t-1].event[e-1].data === false){
let event_length = file.readIntVLV();
MIDI.track[t-1].event[e-1].data = file.readInt(event_length);
if (this.debug) console.info('Unimplemented 0xF exclusive events! data block readed as Integer');
}
break;
}
case 0xA: // Note Aftertouch
case 0xB: // Controller
case 0xE: // Pitch Bend Event
case 0x8: // Note off
case 0x9: // Note On
MIDI.track[t-1].event[e-1].data = [];
MIDI.track[t-1].event[e-1].data[0] = file.readInt(1);
MIDI.track[t-1].event[e-1].data[1] = file.readInt(1);
break;
case 0xC: // Program Change
case 0xD: // Channel Aftertouch
MIDI.track[t-1].event[e-1].data = file.readInt(1);
break;
case -1: // EOF
endOfTrack = true; // change FLAG to force track reading loop breaking
break;
default:
// if user provided a custom interpreter, call it
// and assign to event the returned data
if( this.customInterpreter !== null){
MIDI.track[t-1].event[e-1].data = this.customInterpreter( MIDI.track[t-1].event[e-1].metaType, file , false);
}
// if no customInterpretr is provided, or returned
// false (=apply default), perform default action
if(this.customInterpreter === null || MIDI.track[t-1].event[e-1].data === false){
console.log('Unknown EVENT detected... reading cancelled!');
return false;
}
}
}
}
}
return MIDI;
},
/**
* custom function to handle unimplemented, or custom midi messages.
* If message is a meta-event, the value of metaEventLength will be >0.
* Function must return the value to store, and pointer of dataView needs
* to be manually increased
* If you want default action to be performed, return false
*/
customInterpreter : null // function( e_type , arrayByffer, metaEventLength){ return e_data_int }
};
// if running in NODE export module
if(typeof module !== 'undefined') module.exports = MidiParser;
else{
// if running in Browser, set a global variable.
let _global = typeof window === 'object' && window.self === window && window ||
typeof self === 'object' && self.self === self && self ||
typeof global === 'object' && global.global === global && global;
_global.MidiParser = MidiParser;
}
})();

View File

@@ -123,7 +123,7 @@ label {
color: #AAF;
}
input,textarea {
.header input {
color: #EEE;
background-color: #555;
font-size: 1.2em;

View File

@@ -12,7 +12,7 @@ And in ```the src/``` folder there is also another class included:
## LGraphNode
LGraphNode is the base class used for all the nodes classes.
To extend the other classes all the methods contained in LGraphNode.prototype are copyed to the classes when registered.
To extend the other classes all the methods contained in LGraphNode.prototype are copied to the classes when registered.
When you create a new node type you do not have to inherit from that class, when the node is registered all the methods are copied to your node prototype. This is done inside the functions ```LiteGraph.registerNodeType(...)```.
@@ -59,12 +59,15 @@ LiteGraph.registerNodeType("basic/sum", MyAddNode );
There are several settings that could be defined or modified per node:
* **size**: ```[width,height]``` the size of the area inside the node (excluding title). Every row is LiteGraph.NODE_SLOT_HEIGHT pixels height.
* **properties**: object containing the properties that could be configured by the user, and serialized when saving the graph
* **shape**: the shape of the object (could be LiteGraph.BOX,LiteGraph.ROUND,LiteGraph.CARD)
* **flags**: several flags
* **resizable**: if it can be resized dragging the corner
* **horizontal**: if the slots should be placed horizontally on the top and bottom of the node
* **clip_area**: clips the content when rendering the node
* **shape**: the shape of the object (could be LiteGraph.BOX_SHAPE,LiteGraph.ROUND_SHAPE,LiteGraph.CARD_SHAPE)
* **flags**: flags that can be changed by the user and will be stored when serialized
* **collapsed**: if it is shown collapsed (small)
* **redraw_on_mouse**: forces a redraw if the mouse passes over the widget
* **widgets_up**: widgets do not start after the slots
* **widgets_start_y**: widgets should start being drawn from this Y
* **clip_area**: clips the content when rendering the node
* **resizable**: if it can be resized dragging the corner
* **horizontal**: if the slots should be placed horizontally on the top and bottom of the node
There are several callbacks that could be defined by the user:
* **onAdded**: called when added to graph
@@ -102,7 +105,7 @@ Slots have the next information:
* **name**: string with the name of the slot (used also to show in the canvas)
* **type**: string specifying the data type traveling through this link
* **link or links**: depending if the slot is input or ouput contains the id of the link or an array of ids
* **link or links**: depending if the slot is input or output contains the id of the link or an array of ids
* **label**: optional, string used to rename the name as shown in the canvas.
* **dir**: optional, could be LiteGraph.UP, LiteGraph.RIGHT, LiteGraph.DOWN, LiteGraph.LEFT
* **color_on**: color to render when it is connected
@@ -186,7 +189,14 @@ function MyNodeType()
This is the list of supported widgets:
* **"number"** to change a value of a number, the syntax is ```this.addWidget("number","Number", current_value, callback, { min: 0, max: 100, step: 1} );```
* **"slider"** to change a number by draging the mouse, the syntax is the same as number.
* **"combo"** to select between multiple choices, the syntax is: ```this.addWidget("combo","Combo", "red", callback, { values:["red","green","blue"]} );```
* **"combo"** to select between multiple choices, the syntax is:
```this.addWidget("combo","Combo", "red", callback, { values:["red","green","blue"]} );```
or if you want to use objects:
```this.addWidget("combo","Combo", value1, callback, { values: { "title1":value1, "title2":value2 } );```
* **"text"** to edit a short string
* **"toggle"** like a checkbox
* **"button"**
@@ -244,3 +254,63 @@ If you want to start the graph then:
```js
graph.start();
```
## Events
When we run a step in a graph (using ```graph.runStep()```) every node onExecute method will be called.
But sometimes you want that actions are only performed when some trigger is activated, for this situations you can use Events.
Events allow to trigger executions in nodes only when an event is dispatched from one node.
To define slots for nodes you must use the type LiteGraph.ACTION for inputs, and LIteGraph.EVENT for outputs:
```js
function MyNode()
{
this.addInput("play", LiteGraph.ACTION );
this.addInput("onFinish", LiteGraph.EVENT );
}
```
Now to execute some code when an event is received from an input, you must define the method onAction:
```js
MyNode.prototype.onAction = function(action, data)
{
if(action == "play")
{
//do your action...
}
}
```
And the last thing is to trigger events when something in your node happens. You could trigger them from inside the onExecute or from any other interaction:
```js
MyNode.prototype.onAction = function(action, data)
{
if( this.button_was_clicked )
this.triggerSlot(0); //triggers event in slot 0
}
```
There are some nodes already available to handle events, like delaying, counting, etc.
```

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

@@ -1,7 +1,7 @@
{
"name": "litegraph.js",
"version": "0.7.3",
"description": "A graph node editor similar to PD or UDK Blueprints, it works in a HTML5 Canvas and allow to exported graphs to be included in applications.",
"version": "0.7.8",
"description": "A graph node editor similar to PD or UDK Blueprints. It works in an HTML5 Canvas and allows to export graphs to be included in applications.",
"main": "build/litegraph.js",
"types": "src/litegraph.d.ts",
"directories": {
@@ -31,11 +31,11 @@
"bugs": {
"url": "https://github.com/jagenjo/litegraph.js/issues"
},
"homepage": "https://github.com/kriffe/litegraph.js#readme",
"homepage": "https://github.com/jagenjo/litegraph.js#readme",
"devDependencies": {
"express": "^4.17.1",
"google-closure-compiler": "^20171112.0.0",
"grunt": "^1.0.4",
"grunt": "^1.1.0",
"grunt-cli": "^1.2.0",
"grunt-closure-tools": "^1.0.0",
"grunt-contrib-concat": "^1.0.1",

View File

@@ -3,19 +3,17 @@ function Editor(container_id, options) {
options = options || {};
//fill container
var html =
"<div class='header'><div class='tools tools-left'></div><div class='tools tools-right'></div></div>";
html +=
"<div class='content'><div class='editor-area'><canvas class='graphcanvas' width='1000' height='500' tabindex=10></canvas></div></div>";
html +=
"<div class='footer'><div class='tools tools-left'></div><div class='tools tools-right'></div></div>";
var html = "<div class='header'><div class='tools tools-left'></div><div class='tools tools-right'></div></div>";
html += "<div class='content'><div class='editor-area'><canvas class='graphcanvas' width='1000' height='500' tabindex=10></canvas></div></div>";
html += "<div class='footer'><div class='tools tools-left'></div><div class='tools tools-right'></div></div>";
var root = document.createElement("div");
this.root = root;
root.className = "litegraph-editor";
root.className = "litegraph litegraph-editor";
root.innerHTML = html;
this.tools = root.querySelector(".tools");
this.content = root.querySelector(".content");
this.footer = root.querySelector(".footer");
var canvas = root.querySelector(".graphcanvas");
@@ -28,6 +26,8 @@ function Editor(container_id, options) {
graphcanvas.draw(true);
};
graphcanvas.onDropItem = this.onDropItem.bind(this);
//add stuff
//this.addToolsButton("loadsession_button","Load","imgs/icon-load.png", this.onLoadButton.bind(this), ".tools-left" );
//this.addToolsButton("savesession_button","Save","imgs/icon-save.png", this.onSaveButton.bind(this), ".tools-left" );
@@ -104,56 +104,31 @@ Editor.prototype.addLoadCounter = function() {
}, 200);
};
Editor.prototype.addToolsButton = function(
id,
name,
icon_url,
callback,
container
) {
Editor.prototype.addToolsButton = function( id, name, icon_url, callback, container ) {
if (!container) {
container = ".tools";
}
var button = this.createButton(name, icon_url);
var button = this.createButton(name, icon_url, callback);
button.id = id;
button.addEventListener("click", callback);
this.root.querySelector(container).appendChild(button);
};
Editor.prototype.createPanel = function(title, options) {
var root = document.createElement("div");
root.className = "dialog";
root.innerHTML =
"<div class='dialog-header'><span class='dialog-title'>" +
title +
"</span></div><div class='dialog-content'></div><div class='dialog-footer'></div>";
root.header = root.querySelector(".dialog-header");
root.content = root.querySelector(".dialog-content");
root.footer = root.querySelector(".dialog-footer");
return root;
};
Editor.prototype.createButton = function(name, icon_url) {
Editor.prototype.createButton = function(name, icon_url, callback) {
var button = document.createElement("button");
if (icon_url) {
button.innerHTML = "<img src='" + icon_url + "'/> ";
}
button.classList.add("btn");
button.innerHTML += name;
if(callback)
button.addEventListener("click", callback );
return button;
};
Editor.prototype.onLoadButton = function() {
var panel = this.createPanel("Load session");
var close = this.createButton("Close");
close.style.float = "right";
close.addEventListener("click", function() {
panel.parentNode.removeChild(panel);
});
panel.header.appendChild(close);
panel.content.innerHTML = "test";
var panel = this.graphcanvas.createPanel("Load session",{closable:true});
//TO DO
this.root.appendChild(panel);
};
@@ -192,6 +167,25 @@ Editor.prototype.onLiveButton = function() {
: "<img src='imgs/icon-gear.png'/> Edit";
};
Editor.prototype.onDropItem = function(e)
{
var that = this;
for(var i = 0; i < e.dataTransfer.files.length; ++i)
{
var file = e.dataTransfer.files[i];
var ext = LGraphCanvas.getFileExtension(file.name);
var reader = new FileReader();
if(ext == "json")
{
reader.onload = function(event) {
var data = JSON.parse( event.target.result );
that.graph.configure(data);
};
reader.readAsText(file);
}
}
}
Editor.prototype.goFullscreen = function() {
if (this.root.requestFullscreen) {
this.root.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
@@ -225,7 +219,7 @@ Editor.prototype.addMiniWindow = function(w, h) {
var canvas = miniwindow.querySelector("canvas");
var that = this;
var graphcanvas = new LGraphCanvas(canvas, this.graph);
var graphcanvas = new LGraphCanvas( canvas, this.graph );
graphcanvas.show_info = false;
graphcanvas.background_image = "imgs/grid.png";
graphcanvas.scale = 0.25;
@@ -263,7 +257,7 @@ Editor.prototype.addMiniWindow = function(w, h) {
var close_button = document.createElement("div");
close_button.className = "corner-button";
close_button.innerHTML = "X";
close_button.innerHTML = "&#10060;";
close_button.addEventListener("click", function(e) {
graphcanvas.setGraph(null);
miniwindow.parentNode.removeChild(miniwindow);

108
src/litegraph.d.ts vendored
View File

@@ -11,11 +11,17 @@ export type widgetTypes =
| "text"
| "toggle"
| "button";
export type SlotShape =
| typeof LiteGraph.BOX_SHAPE
| typeof LiteGraph.CIRCLE_SHAPE
| typeof LiteGraph.ARROW_SHAPE
| typeof LiteGraph.SQUARE_SHAPE
| number; // For custom shapes
/** https://github.com/jagenjo/litegraph.js/tree/master/guides#node-slots */
export interface INodeSlot {
name: string;
type: string;
type: string | -1;
label?: string;
dir?:
| typeof LiteGraph.UP
@@ -24,6 +30,7 @@ export interface INodeSlot {
| typeof LiteGraph.LEFT;
color_on?: string;
color_off?: string;
shape?: SlotShape;
locked?: boolean;
nameLocked?: boolean;
}
@@ -68,11 +75,12 @@ export interface IWidget<TValue = any, TOptions = any> {
* https://github.com/jagenjo/litegraph.js/issues/76
*/
mouse?(
ctx: undefined,
event: MouseEvent,
pos: Vector2,
node: LGraphNode
): void;
): boolean;
/** Called by `LGraphNode.computeSize` */
computeSize?(width: number): [number, number];
}
export interface IButtonWidget extends IWidget<null, {}> {
type: "button";
@@ -173,6 +181,7 @@ export const LiteGraph: {
CIRCLE_SHAPE: 3;
CARD_SHAPE: 4;
ARROW_SHAPE: 5;
SQUARE_SHAPE: 6;
//enums
INPUT: 1;
@@ -228,6 +237,10 @@ export const LiteGraph: {
createNode<T extends LGraphNode = LGraphNode>(type: string): T;
/** Register a node class so it can be listed when the user wants to create a new one */
registerNodeType(type: string, base: { new (): LGraphNode }): void;
/** removes a node type from the system */
unregisterNodeType(type: string): void;
/** Removes all previously registered node's types. */
clearRegisteredTypes(): void;
/**
* Create a new node type by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function.
* Useful to wrap simple methods that do not require properties, and that only process some input to generate an output.
@@ -240,7 +253,7 @@ export const LiteGraph: {
wrapFunctionAsNode(
name: string,
func: (...args: any[]) => any,
param_types?: [],
param_types?: string[],
return_type?: string,
properties?: object
): void;
@@ -431,6 +444,11 @@ export declare class LGraph {
* @param node the instance of the node
*/
add(node: LGraphNode, skip_compute_order?: boolean): void;
/**
* Called when a new node is added
* @param node the instance of the node
*/
onNodeAdded(node: LGraphNode): void;
/** Removes a node from the graph */
remove(node: LGraphNode): void;
/** Returns a node by its id. */
@@ -593,7 +611,9 @@ export declare class LGraphNode {
properties: Record<string, any>;
properties_info: any[];
flags: object;
flags: Partial<{
collapsed: boolean
}>;
color: string;
bgcolor: string;
@@ -615,6 +635,17 @@ export declare class LGraphNode {
| typeof LiteGraph.NEVER
| typeof LiteGraph.ALWAYS;
/** If set to true widgets do not start after the slots */
widgets_up: boolean;
/** widgets start at y distance from the top of the node */
widgets_start_y: number;
/** if you render outside the node, it will be clipped */
clip_area: boolean;
/** if set to false it wont be resizable with the mouse */
resizable: boolean;
/** slots are distributed horizontally */
horizontal: boolean;
/** configure a node from an object containing the serialized info */
configure(info: SerializedLGraphNode): void;
/** serialize the content */
@@ -707,7 +738,7 @@ export declare class LGraphNode {
*/
addOutput(
name: string,
type: string,
type: string | -1,
extra_info?: Partial<INodeOutputSlot>
): void;
/**
@@ -715,7 +746,7 @@ export declare class LGraphNode {
* @param array of triplets like [[name,type,extra_info],[...]]
*/
addOutputs(
array: [string, string, Partial<INodeOutputSlot> | undefined][]
array: [string, string | -1, Partial<INodeOutputSlot> | undefined][]
): void;
/** remove an existing output slot */
removeOutput(slot: number): void;
@@ -727,7 +758,7 @@ export declare class LGraphNode {
*/
addInput(
name: string,
type: string,
type: string | -1,
extra_info?: Partial<INodeInputSlot>
): void;
/**
@@ -735,7 +766,7 @@ export declare class LGraphNode {
* @param array of triplets like [[name,type,extra_info],[...]]
*/
addInputs(
array: [string, string, Partial<INodeInputSlot> | undefined][]
array: [string, string | -1, Partial<INodeInputSlot> | undefined][]
): void;
/** remove an existing input slot */
removeInput(slot: number): void;
@@ -769,7 +800,7 @@ export declare class LGraphNode {
type: T["type"],
name: string,
value: T["value"],
callback?: WidgetCallback<T>,
callback?: WidgetCallback<T> | string,
options?: T["options"]
): T;
@@ -863,12 +894,12 @@ export declare class LGraphNode {
// https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-appearance
onDrawBackground?(
canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D
ctx: CanvasRenderingContext2D,
canvas: HTMLCanvasElement
): void;
onDrawForeground?(
canvas: HTMLCanvasElement,
ctx: CanvasRenderingContext2D
ctx: CanvasRenderingContext2D,
canvas: HTMLCanvasElement
): void;
// https://github.com/jagenjo/litegraph.js/blob/master/guides/README.md#custom-node-behaviour
@@ -926,10 +957,34 @@ export declare class LGraphNode {
onConnectInput?(
inputIndex: number,
type: INodeOutputSlot["type"],
outputSlot: INodeOutputSlot
outputSlot: INodeOutputSlot,
_this: this,
slotIndex: number
): boolean;
/**
* Called just before connection (or disconnect - if input is linked).
* A convenient place to switch to another input, or create new one.
* This allow for ability to automatically add slots if needed
* @param inputIndex
* @return selected input slot index, can differ from parameter value
*/
onBeforeConnectInput?(
inputIndex: number
): number;
/** a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info or output_info ) */
onConnectionsChange(
type: number,
slotIndex: number,
isConnected: boolean,
link: LLink,
ioSlot: (INodeOutputSlot | INodeInputSlot)
): void;
/** Called by `LGraphCanvas.processContextMenu` */
getMenuOptions?(graphCanvas: LGraphCanvas): ContextMenuItem[];
getSlotMenuOptions?(slot: INodeSlot): ContextMenuItem[];
}
export type LGraphNodeConstructor<T extends LGraphNode = LGraphNode> = {
@@ -1017,6 +1072,7 @@ export declare class LGraphCanvas {
/** Create menu for `Add Node` */
static onMenuAdd: ContextMenuEventListener;
static showMenuNodeOptionalInputs: ContextMenuEventListener;
static showMenuNodeOptionalOutputs: ContextMenuEventListener;
static onShowMenuNodeProperties: ContextMenuEventListener;
static onResizeNode: ContextMenuEventListener;
static onMenuNodeCollapse: ContextMenuEventListener;
@@ -1089,7 +1145,7 @@ export declare class LGraphCanvas {
last_mouse_position: Vector2;
/** Timestamp of last mouse click, defaults to 0 */
last_mouseclick: number;
link_render_mode:
links_render_mode:
| typeof LiteGraph.STRAIGHT_LINK
| typeof LiteGraph.LINEAR_LINK
| typeof LiteGraph.SPLINE_LINK;
@@ -1111,6 +1167,20 @@ export declare class LGraphCanvas {
onDrawOverlay: ((ctx: CanvasRenderingContext2D) => void) | null;
/** Called by `LGraphCanvas.processMouseDown` */
onMouse: ((event: MouseEvent) => boolean) | null;
/** Called by `LGraphCanvas.drawFrontCanvas` and `LGraphCanvas.drawLinkTooltip` */
onDrawLinkTooltip: ((ctx: CanvasRenderingContext2D, link: LLink, _this: this) => void) | null;
/** Called by `LGraphCanvas.selectNodes` */
onNodeMoved: ((node: LGraphNode) => void) | null;
/** Called by `LGraphCanvas.processNodeSelected` */
onNodeSelected: ((node: LGraphNode) => void) | null;
/** Called by `LGraphCanvas.deselectNode` */
onNodeDeselected: ((node: LGraphNode) => void) | null;
/** Called by `LGraphCanvas.processNodeDblClicked` */
onShowNodePanel: ((node: LGraphNode) => void) | null;
/** Called by `LGraphCanvas.processNodeDblClicked` */
onNodeDblClicked: ((node: LGraphNode) => void) | null;
/** Called by `LGraphCanvas.selectNodes` */
onSelectionChange: ((nodes: Record<number, LGraphNode>) => void) | null;
/** Called by `LGraphCanvas.showSearchBox` */
onSearchBox:
| ((
@@ -1213,7 +1283,7 @@ export declare class LGraphCanvas {
/** selects a given node (or adds it to the current selection) */
selectNode(node: LGraphNode, add?: boolean): void;
/** selects several nodes (or adds them to the current selection) */
selectNodes(nodes: LGraphNode[], add?: boolean): void;
selectNodes(nodes?: LGraphNode[], add?: boolean): void;
/** removes a node from the current selection */
deselectNode(node: LGraphNode): void;
/** removes all nodes from the current selection */
@@ -1236,11 +1306,13 @@ export declare class LGraphCanvas {
/** draws the front canvas (the one containing all the nodes) */
drawFrontCanvas(): void;
/** draws some useful stats in the corner of the canvas */
renderInfo(): void;
renderInfo(ctx: CanvasRenderingContext2D, x: number, y: number): void;
/** draws the back canvas (the one containing the background and the connections) */
drawBackCanvas(): void;
/** draws the given node inside the canvas */
drawNode(node: LGraphNode, ctx: CanvasRenderingContext2D): void;
/** draws graphic for node's slot */
drawSlotGraphic(ctx: CanvasRenderingContext2D, pos: number[], shape: SlotShape, horizontal: boolean): void;
/** draws the shape of the given node in the canvas */
drawNodeShape(
node: LGraphNode,

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -275,6 +275,7 @@
}
}
LGAudioSource.desc = "Plays an audio file";
LGAudioSource["@src"] = { widget: "resource" };
LGAudioSource.supported_extensions = ["wav", "ogg", "mp3"];
@@ -290,7 +291,7 @@
}
if (this.properties.autoplay) {
this.playBuffer(this._audiobuffer);
this.playBuffer(this._audiobuffer);
}
};
@@ -345,8 +346,10 @@
if (v === undefined) {
continue;
}
if (input.name == "gain") {
if (input.name == "gain")
this.audionode.gain.value = v;
else if (input.name == "src") {
this.setProperty("src",v);
} else if (input.name == "playbackRate") {
this.properties.playbackRate = v;
for (var j = 0; j < this._audionodes.length; ++j) {
@@ -401,7 +404,10 @@
audionode.playbackRate.value = this.properties.playbackRate;
this._audionodes.push(audionode);
audionode.connect(this.audionode); //connect to gain
this._audionodes.push(audionode);
this._audionodes.push(audionode);
this.trigger("start");
audionode.onended = function() {
//console.log("ended!");
@@ -458,13 +464,14 @@
LGAudioSource.prototype.onGetInputs = function() {
return [
["playbackRate", "number"],
["src","string"],
["Play", LiteGraph.ACTION],
["Stop", LiteGraph.ACTION]
];
};
LGAudioSource.prototype.onGetOutputs = function() {
return [["buffer", "audiobuffer"], ["ended", LiteGraph.EVENT]];
return [["buffer", "audiobuffer"], ["start", LiteGraph.EVENT], ["ended", LiteGraph.EVENT]];
};
LGAudioSource.prototype.onDropFile = function(file) {
@@ -1357,7 +1364,7 @@ LiteGraph.registerNodeType("audio/waveShaper", LGAudioWaveShaper);
}
};
LGAudioScript["@code"] = { widget: "code" };
LGAudioScript["@code"] = { widget: "code", type: "code" };
LGAudioScript.prototype.onStart = function() {
this.audionode.onaudioprocess = this._callback;

View File

@@ -32,18 +32,15 @@
this.subgraph.onTrigger = this.onSubgraphTrigger.bind(this);
//nodes input node added inside
this.subgraph.onInputAdded = this.onSubgraphNewInput.bind(this);
this.subgraph.onInputRenamed = this.onSubgraphRenamedInput.bind(this);
this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind(
this
);
this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind(this);
this.subgraph.onInputRemoved = this.onSubgraphRemovedInput.bind(this);
this.subgraph.onOutputAdded = this.onSubgraphNewOutput.bind(this);
this.subgraph.onOutputRenamed = this.onSubgraphRenamedOutput.bind(this);
this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind(
this
);
this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind(this);
this.subgraph.onOutputRemoved = this.onSubgraphRemovedOutput.bind(this);
}
@@ -55,6 +52,7 @@
return [["enabled", "boolean"]];
};
/*
Subgraph.prototype.onDrawTitle = function(ctx) {
if (this.flags.collapsed) {
return;
@@ -71,6 +69,7 @@
ctx.lineTo(x + w * 0.5, -w * 0.3);
ctx.fill();
};
*/
Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) {
var that = this;
@@ -79,6 +78,7 @@
}, 10);
};
/*
Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) {
if (
!this.flags.collapsed &&
@@ -91,6 +91,7 @@
}, 10);
}
};
*/
Subgraph.prototype.onAction = function(action, param) {
this.subgraph.onAction(action, param);
@@ -130,6 +131,46 @@
}
};
Subgraph.prototype.onDrawBackground = function(ctx, graphcanvas, canvas, pos)
{
if(this.flags.collapsed)
return;
var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;
//button
var over = LiteGraph.isInsideRectangle(pos[0],pos[1],this.pos[0],this.pos[1] + y,this.size[0],LiteGraph.NODE_TITLE_HEIGHT);
ctx.fillStyle = over ? "#555" : "#222";
ctx.beginPath();
if (this._shape == LiteGraph.BOX_SHAPE)
ctx.rect(0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT);
else
ctx.roundRect( 0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT, 0, 8);
ctx.fill();
//button
ctx.textAlign = "center";
ctx.font = "24px Arial";
ctx.fillStyle = over ? "#DDD" : "#999";
ctx.fillText( "+", this.size[0] * 0.5, y + 24 );
}
Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas)
{
var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;
if(localpos[1] > y)
{
graphcanvas.showSubgraphPropertiesDialog(this);
}
}
Subgraph.prototype.computeSize = function()
{
var num_inputs = this.inputs ? this.inputs.length : 0;
var num_outputs = this.outputs ? this.outputs.length : 0;
return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ];
}
//**** INPUTS ***********************************
Subgraph.prototype.onSubgraphTrigger = function(event, param) {
var slot = this.findOutputSlot(event);
@@ -224,7 +265,7 @@
};
Subgraph.prototype.serialize = function() {
var data = LGraphNode.prototype.serialize.call(this);
var data = LiteGraph.LGraphNode.prototype.serialize.call(this);
data.subgraph = this.subgraph.serialize();
return data;
};
@@ -240,6 +281,95 @@
return node;
};
Subgraph.prototype.buildFromNodes = function(nodes)
{
//clear all?
//TODO
//nodes that connect data between parent graph and subgraph
var subgraph_inputs = [];
var subgraph_outputs = [];
//mark inner nodes
var ids = {};
var min_x = 0;
var max_x = 0;
for(var i = 0; i < nodes.length; ++i)
{
var node = nodes[i];
ids[ node.id ] = node;
min_x = Math.min( node.pos[0], min_x );
max_x = Math.max( node.pos[0], min_x );
}
var last_input_y = 0;
var last_output_y = 0;
for(var i = 0; i < nodes.length; ++i)
{
var node = nodes[i];
//check inputs
if( node.inputs )
for(var j = 0; j < node.inputs.length; ++j)
{
var input = node.inputs[j];
if( !input || !input.link )
continue;
var link = node.graph.links[ input.link ];
if(!link)
continue;
if( ids[ link.origin_id ] )
continue;
//this.addInput(input.name,link.type);
this.subgraph.addInput(input.name,link.type);
/*
var input_node = LiteGraph.createNode("graph/input");
this.subgraph.add( input_node );
input_node.pos = [min_x - 200, last_input_y ];
last_input_y += 100;
*/
}
//check outputs
if( node.outputs )
for(var j = 0; j < node.outputs.length; ++j)
{
var output = node.outputs[j];
if( !output || !output.links || !output.links.length )
continue;
var is_external = false;
for(var k = 0; k < output.links.length; ++k)
{
var link = node.graph.links[ output.links[k] ];
if(!link)
continue;
if( ids[ link.target_id ] )
continue;
is_external = true;
break;
}
if(!is_external)
continue;
//this.addOutput(output.name,output.type);
/*
var output_node = LiteGraph.createNode("graph/output");
this.subgraph.add( output_node );
output_node.pos = [max_x + 50, last_output_y ];
last_output_y += 100;
*/
}
}
//detect inputs and outputs
//split every connection in two data_connection nodes
//keep track of internal connections
//connect external connections
//clone nodes inside subgraph and try to reconnect them
//connect edge subgraph nodes to extarnal connections nodes
}
LiteGraph.Subgraph = Subgraph;
LiteGraph.registerNodeType("graph/subgraph", Subgraph);
@@ -297,16 +427,27 @@
this.updateType();
}
//ensures the type in the node output and the type in the associated graph input are the same
GraphInput.prototype.updateType = function()
{
var type = this.properties.type;
this.type_widget.value = type;
//update output
if(this.outputs[0].type != type)
{
if (!LiteGraph.isValidConnection(this.outputs[0].type,type))
this.disconnectOutput(0);
this.outputs[0].type = type;
}
//update widget
if(type == "number")
{
this.value_widget.type = "number";
this.value_widget.value = 0;
}
else if(type == "bool")
else if(type == "boolean")
{
this.value_widget.type = "toggle";
this.value_widget.value = true;
@@ -322,8 +463,14 @@
this.value_widget.value = null;
}
this.properties.value = this.value_widget.value;
//update graph
if (this.graph && this.name_in_graph) {
this.graph.changeInputType(this.name_in_graph, type);
}
}
//this is executed AFTER the property has changed
GraphInput.prototype.onPropertyChanged = function(name,v)
{
if( name == "name" )
@@ -345,8 +492,7 @@
}
else if( name == "type" )
{
v = v || "";
this.updateType(v);
this.updateType();
}
else if( name == "value" )
{
@@ -372,8 +518,10 @@
var data = this.graph.inputs[name];
if (!data) {
this.setOutputData(0, this.properties.value );
return;
}
this.setOutputData(0, data.value === undefined ? this.properties.value : data.value);
this.setOutputData(0, data.value !== undefined ? data.value : this.properties.value );
};
GraphInput.prototype.onRemoved = function() {
@@ -421,6 +569,8 @@
if (v == "action" || v == "event") {
v = LiteGraph.ACTION;
}
if (!LiteGraph.isValidConnection(that.inputs[0].type,v))
that.disconnectInput(0);
that.inputs[0].type = v;
if (that.name_in_graph) {
//already added
@@ -434,27 +584,8 @@
enumerable: true
});
this.name_widget = this.addWidget(
"text",
"Name",
this.properties.name,
function(v) {
if (!v) {
return;
}
that.properties.name = v;
}
);
this.type_widget = this.addWidget(
"text",
"Type",
this.properties.type,
function(v) {
v = v || "";
that.properties.type = v;
}
);
this.name_widget = this.addWidget("text","Name",this.properties.name,"name");
this.type_widget = this.addWidget("text","Type",this.properties.type,"type");
this.widgets_up = true;
this.size = [180, 60];
}
@@ -493,6 +624,9 @@
function ConstantNumber() {
this.addOutput("value", "number");
this.addProperty("value", 1.0);
this.widget = this.addWidget("number","value",1,"value");
this.widgets_up = true;
this.size = [180, 30];
}
ConstantNumber.title = "Const Number";
@@ -509,9 +643,10 @@
return this.title;
};
ConstantNumber.prototype.setValue = function(v) {
this.properties.value = v;
};
ConstantNumber.prototype.setValue = function(v)
{
this.setProperty("value",v);
}
ConstantNumber.prototype.onDrawBackground = function(ctx) {
//show the current value
@@ -520,47 +655,170 @@
LiteGraph.registerNodeType("basic/const", ConstantNumber);
function ConstantBoolean() {
this.addOutput("", "boolean");
this.addProperty("value", true);
this.widget = this.addWidget("toggle","value",true,"value");
this.widgets_up = true;
this.size = [140, 30];
}
ConstantBoolean.title = "Const Boolean";
ConstantBoolean.desc = "Constant boolean";
ConstantBoolean.prototype.getTitle = ConstantNumber.prototype.getTitle;
ConstantBoolean.prototype.onExecute = function() {
this.setOutputData(0, this.properties["value"]);
};
ConstantBoolean.prototype.setValue = ConstantNumber.prototype.setValue;
ConstantBoolean.prototype.onGetInputs = function() {
return [["toggle", LiteGraph.ACTION]];
};
ConstantBoolean.prototype.onAction = function(action)
{
this.setValue( !this.properties.value );
}
LiteGraph.registerNodeType("basic/boolean", ConstantBoolean);
function ConstantString() {
this.addOutput("", "string");
this.addProperty("value", "");
this.widget = this.addWidget(
"text",
"value",
"",
this.setValue.bind(this)
);
this.widget = this.addWidget("text","value","","value"); //link to property value
this.widgets_up = true;
this.size = [100, 30];
this.size = [180, 30];
}
ConstantString.title = "Const String";
ConstantString.desc = "Constant string";
ConstantString.prototype.setValue = function(v) {
this.properties.value = v;
};
ConstantString.prototype.onPropertyChanged = function(name, value) {
this.widget.value = value;
};
ConstantString.prototype.getTitle = ConstantNumber.prototype.getTitle;
ConstantString.prototype.onExecute = function() {
this.setOutputData(0, this.properties["value"]);
};
ConstantString.prototype.setValue = ConstantNumber.prototype.setValue;
ConstantString.prototype.onDropFile = function(file)
{
var that = this;
var reader = new FileReader();
reader.onload = function(e)
{
that.setProperty("value",e.target.result);
}
reader.readAsText(file);
}
LiteGraph.registerNodeType("basic/string", ConstantString);
function ConstantFile() {
this.addInput("url", "");
this.addOutput("", "");
this.addProperty("url", "");
this.addProperty("type", "text");
this.widget = this.addWidget("text","url","","url");
this._data = null;
}
ConstantFile.title = "Const File";
ConstantFile.desc = "Fetches a file from an url";
ConstantFile["@type"] = { type: "enum", values: ["text","arraybuffer","blob","json"] };
ConstantFile.prototype.onPropertyChanged = function(name, value) {
if (name == "url")
{
if( value == null || value == "")
this._data = null;
else
{
this.fetchFile(value);
}
}
}
ConstantFile.prototype.onExecute = function() {
var url = this.getInputData(0) || this.properties.url;
if(url && (url != this._url || this._type != this.properties.type))
this.fetchFile(url);
this.setOutputData(0, this._data );
};
ConstantFile.prototype.setValue = ConstantNumber.prototype.setValue;
ConstantFile.prototype.fetchFile = function(url) {
var that = this;
if(!url || url.constructor !== String)
{
that._data = null;
that.boxcolor = null;
return;
}
this._url = url;
this._type = this.properties.type;
if (url.substr(0, 4) == "http" && LiteGraph.proxy) {
url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3);
}
fetch(url)
.then(function(response) {
if(!response.ok)
throw new Error("File not found");
if(that.properties.type == "arraybuffer")
return response.arrayBuffer();
else if(that.properties.type == "text")
return response.text();
else if(that.properties.type == "json")
return response.json();
else if(that.properties.type == "blob")
return response.blob();
})
.then(function(data) {
that._data = data;
that.boxcolor = "#AEA";
})
.catch(function(error) {
that._data = null;
that.boxcolor = "red";
console.error("error fetching file:",url);
});
};
ConstantFile.prototype.onDropFile = function(file)
{
var that = this;
this._url = file.name;
this._type = this.properties.type;
this.properties.url = file.name;
var reader = new FileReader();
reader.onload = function(e)
{
that.boxcolor = "#AEA";
var v = e.target.result;
if( that.properties.type == "json" )
v = JSON.parse(v);
that._data = v;
}
if(that.properties.type == "arraybuffer")
reader.readAsArrayBuffer(file);
else if(that.properties.type == "text" || that.properties.type == "json")
reader.readAsText(file);
else if(that.properties.type == "blob")
return reader.readAsBinaryString(file);
}
LiteGraph.registerNodeType("basic/file", ConstantFile);
//to store json objects
function ConstantData() {
this.addOutput("", "");
this.addProperty("value", "");
this.widget = this.addWidget(
"text",
"json",
"",
this.setValue.bind(this)
);
this.widget = this.addWidget("text","json","","value");
this.widgets_up = true;
this.size = [140, 30];
this._value = null;
@@ -569,11 +827,6 @@
ConstantData.title = "Const Data";
ConstantData.desc = "Constant Data";
ConstantData.prototype.setValue = function(v) {
this.properties.value = v;
this.onPropertyChanged("value", v);
};
ConstantData.prototype.onPropertyChanged = function(name, value) {
this.widget.value = value;
if (value == null || value == "") {
@@ -592,18 +845,120 @@
this.setOutputData(0, this._value);
};
ConstantData.prototype.setValue = ConstantNumber.prototype.setValue;
LiteGraph.registerNodeType("basic/data", ConstantData);
//to store json objects
function ConstantArray() {
this.addInput("", "");
this.addOutput("", "array");
this.addOutput("length", "number");
this.addProperty("value", "");
this.widget = this.addWidget("text","array","","value");
this.widgets_up = true;
this.size = [140, 50];
this._value = null;
}
ConstantArray.title = "Const Array";
ConstantArray.desc = "Constant Array";
ConstantArray.prototype.onPropertyChanged = function(name, value) {
this.widget.value = value;
if (value == null || value == "") {
return;
}
try {
if(value[0] != "[")
this._value = JSON.parse("[" + value + "]");
else
this._value = JSON.parse(value);
this.boxcolor = "#AEA";
} catch (err) {
this.boxcolor = "red";
}
};
ConstantArray.prototype.onExecute = function() {
var v = this.getInputData(0);
if(v && v.length) //clone
{
if(!this._value)
this._value = new Array();
this._value.length = v.length;
for(var i = 0; i < v.length; ++i)
this._value[i] = v[i];
}
this.setOutputData(0, this._value);
this.setOutputData(1, this._value ? ( this._value.length || 0) : 0 );
};
ConstantArray.prototype.setValue = ConstantNumber.prototype.setValue;
LiteGraph.registerNodeType("basic/array", ConstantArray);
function ArrayElement() {
this.addInput("array", "array,table,string");
this.addInput("index", "number");
this.addOutput("value", "");
this.addProperty("index",0);
}
ArrayElement.title = "Array[i]";
ArrayElement.desc = "Returns an element from an array";
ArrayElement.prototype.onExecute = function() {
var array = this.getInputData(0);
var index = this.getInputData(1);
if(index == null)
index = this.properties.index;
if(array == null || index == null )
return;
this.setOutputData(0, array[Math.floor(Number(index))] );
};
LiteGraph.registerNodeType("basic/array[]", ArrayElement);
function TableElement() {
this.addInput("table", "table");
this.addInput("row", "number");
this.addInput("col", "number");
this.addOutput("value", "");
this.addProperty("row",0);
this.addProperty("column",0);
}
TableElement.title = "Table[row][col]";
TableElement.desc = "Returns an element from a table";
TableElement.prototype.onExecute = function() {
var table = this.getInputData(0);
var row = this.getInputData(1);
var col = this.getInputData(2);
if(row == null)
row = this.properties.row;
if(col == null)
col = this.properties.column;
if(table == null || row == null || col == null)
return;
var row = table[Math.floor(Number(row))];
if(row)
this.setOutputData(0, row[Math.floor(Number(col))] );
else
this.setOutputData(0, null );
};
LiteGraph.registerNodeType("basic/table[][]", TableElement);
function ObjectProperty() {
this.addInput("obj", "");
this.addOutput("", "");
this.addProperty("value", "");
this.widget = this.addWidget(
"text",
"prop.",
"",
this.setValue.bind(this)
);
this.widget = this.addWidget("text","prop.","",this.setValue.bind(this) );
this.widgets_up = true;
this.size = [140, 30];
this._value = null;
@@ -690,28 +1045,70 @@
this.size = [60, 30];
this.addInput("in");
this.addOutput("out");
this.properties = { varname: "myname", global: false };
this.properties = { varname: "myname", container: Variable.LITEGRAPH };
this.value = null;
}
Variable.title = "Variable";
Variable.desc = "store/read variable value";
Variable.LITEGRAPH = 0; //between all graphs
Variable.GRAPH = 1; //only inside this graph
Variable.GLOBALSCOPE = 2; //attached to Window
Variable["@container"] = { type: "enum", values: {"litegraph":Variable.LITEGRAPH, "graph":Variable.GRAPH,"global": Variable.GLOBALSCOPE} };
Variable.prototype.onExecute = function() {
this.value = this.getInputData(0);
if(this.graph)
this.graph.vars[ this.properties.varname ] = this.value;
if(this.properties.global)
global[this.properties.varname] = this.value;
this.setOutputData(0, this.value );
var container = this.getContainer();
if(this.isInputConnected(0))
{
this.value = this.getInputData(0);
container[ this.properties.varname ] = this.value;
this.setOutputData(0, this.value );
return;
}
this.setOutputData( 0, container[ this.properties.varname ] );
};
Variable.prototype.getContainer = function()
{
switch(this.properties.container)
{
case Variable.GRAPH:
if(this.graph)
return this.graph.vars;
return {};
break;
case Variable.GLOBALSCOPE:
return global;
break;
case Variable.LITEGRAPH:
default:
return LiteGraph.Globals;
break;
}
}
Variable.prototype.getTitle = function() {
return this.properties.varname;
};
LiteGraph.registerNodeType("basic/variable", Variable);
function length(v) {
if(v && v.length != null)
return Number(v.length);
return 0;
}
LiteGraph.wrapFunctionAsNode(
"basic/length",
length,
[""],
"number"
);
function DownloadData() {
this.size = [60, 30];

View File

@@ -20,22 +20,30 @@
//convert to Event if the value is true
function TriggerEvent() {
this.size = [60, 30];
this.addInput("in", "");
this.addInput("if", "");
this.addOutput("true", LiteGraph.EVENT);
this.addOutput("change", LiteGraph.EVENT);
this.was_true = false;
this.addOutput("false", LiteGraph.EVENT);
this.properties = { only_on_change: true };
this.prev = 0;
}
TriggerEvent.title = "TriggerEvent";
TriggerEvent.desc = "Triggers event if value is true";
TriggerEvent.desc = "Triggers event if input evaluates to true";
TriggerEvent.prototype.onExecute = function(action, param) {
var v = this.getInputData(0);
if(v)
var changed = (v != this.prev);
if(this.prev === 0)
changed = false;
var must_resend = (changed && this.properties.only_on_change) || (!changed && !this.properties.only_on_change);
if(v && must_resend )
this.triggerSlot(0, param);
if(v && !this.was_true)
if(!v && must_resend)
this.triggerSlot(2, param);
if(changed)
this.triggerSlot(1, param);
this.was_true = v;
this.prev = v;
};
LiteGraph.registerNodeType("events/trigger", TriggerEvent);
@@ -118,6 +126,29 @@
LiteGraph.registerNodeType("events/filter", FilterEvent);
function EventBranch() {
this.addInput("in", LiteGraph.ACTION);
this.addInput("cond", "boolean");
this.addOutput("true", LiteGraph.EVENT);
this.addOutput("false", LiteGraph.EVENT);
this.size = [120, 60];
this._value = false;
}
EventBranch.title = "Branch";
EventBranch.desc = "If condition is true, outputs triggers true, otherwise false";
EventBranch.prototype.onExecute = function() {
this._value = this.getInputData(1);
}
EventBranch.prototype.onAction = function(action, param) {
this.triggerSlot(this._value ? 0 : 1);
}
LiteGraph.registerNodeType("events/branch", EventBranch);
//Show value inside the debug console
function EventCounter() {
this.addInput("inc", LiteGraph.ACTION);

View File

@@ -65,8 +65,9 @@
LGraphPoints3D.OBJECT = 20;
LGraphPoints3D.OBJECT_UNIFORMLY = 21;
LGraphPoints3D.OBJECT_INSIDE = 22;
LGraphPoints3D.MODE_VALUES = { "rectangle":LGraphPoints3D.RECTANGLE, "circle":LGraphPoints3D.CIRCLE, "cube":LGraphPoints3D.CUBE, "sphere":LGraphPoints3D.SPHERE, "hemisphere":LGraphPoints3D.HEMISPHERE, "inside_sphere":LGraphPoints3D.INSIDE_SPHERE, "object":LGraphPoints3D.OBJECT, "object_uniformly":LGraphPoints3D.OBJECT_UNIFORMLY };
LGraphPoints3D.MODE_VALUES = { "rectangle":LGraphPoints3D.RECTANGLE, "circle":LGraphPoints3D.CIRCLE, "cube":LGraphPoints3D.CUBE, "sphere":LGraphPoints3D.SPHERE, "hemisphere":LGraphPoints3D.HEMISPHERE, "inside_sphere":LGraphPoints3D.INSIDE_SPHERE, "object":LGraphPoints3D.OBJECT, "object_uniformly":LGraphPoints3D.OBJECT_UNIFORMLY, "object_inside":LGraphPoints3D.OBJECT_INSIDE };
LGraphPoints3D.widgets_info = {
mode: { widget: "combo", values: LGraphPoints3D.MODE_VALUES }
@@ -164,7 +165,7 @@
if(normals)
{
for(var i = 0; i < normals.length; i+=3)
normals.set(i, UP);
normals.set(UP, i);
}
}
else if( mode == LGraphPoints3D.SPHERE)
@@ -195,7 +196,7 @@
if(normals)
{
for(var i = 0; i < normals.length; i+=3)
normals.set(i, UP);
normals.set(UP, i);
}
}
}
@@ -212,7 +213,7 @@
if(normals)
{
for(var i = 0; i < normals.length; i+=3)
normals.set(i, UP);
normals.set(UP, i);
}
}
else if( mode == LGraphPoints3D.CUBE)
@@ -226,7 +227,7 @@
if(normals)
{
for(var i = 0; i < normals.length; i+=3)
normals.set(i, UP);
normals.set(UP, i);
}
}
else if( mode == LGraphPoints3D.SPHERE)
@@ -261,6 +262,12 @@
{
LGraphPoints3D.generateFromObject( points, normals, size, obj, true );
}
else if( mode == LGraphPoints3D.OBJECT_INSIDE)
{
LGraphPoints3D.generateFromInsideObject( points, size, obj );
//if(normals)
// LGraphPoints3D.generateSphericalNormals( points, normals );
}
else
console.warn("wrong mode in LGraphPoints3D");
}
@@ -466,6 +473,36 @@
}
}
LGraphPoints3D.generateFromInsideObject = function( points, size, mesh )
{
if(!mesh || mesh.constructor !== GL.Mesh)
return;
var aabb = mesh.getBoundingBox();
if(!mesh.octree)
mesh.octree = new GL.Octree( mesh );
var octree = mesh.octree;
var origin = vec3.create();
var direction = vec3.fromValues(1,0,0);
var temp = vec3.create();
var i = 0;
var tries = 0;
while(i < size && tries < points.length * 10) //limit to avoid problems
{
tries += 1
var r = vec3.random(temp); //random point inside the aabb
r[0] = (r[0] * 2 - 1) * aabb[3] + aabb[0];
r[1] = (r[1] * 2 - 1) * aabb[4] + aabb[1];
r[2] = (r[2] * 2 - 1) * aabb[5] + aabb[2];
origin.set(r);
var hit = octree.testRay( origin, direction, 0, 10000, true, GL.Octree.ALL );
if(!hit || hit.length % 2 == 0) //not inside
continue;
points.set( r, i );
i+=3;
}
}
LiteGraph.registerNodeType( "geometry/points3D", LGraphPoints3D );
@@ -474,11 +511,13 @@
this.addInput("points", "geometry");
this.addOutput("instances", "[mat4]");
this.properties = {
mode: 1
mode: 1,
autoupdate: true
};
this.must_update = true;
this.matrices = [];
this.first_time = true;
}
LGraphPointsToInstances.NORMAL = 0;
@@ -506,8 +545,13 @@
if( !this.isOutputConnected(0) )
return;
if( geo._version != this._version || geo._id != this._geometry_id )
var has_changed = (geo._version != this._version || geo._id != this._geometry_id);
if( has_changed && this.properties.autoupdate || this.first_time )
{
this.first_time = false;
this.updateInstances( geo );
}
this.setOutputData( 0, this.matrices );
}
@@ -611,7 +655,8 @@
this.geometry = {
type: "triangles",
vertices: null,
_id: generateGeometryId()
_id: generateGeometryId(),
_version: 0
};
this._last_geometry_id = -1;
@@ -730,7 +775,7 @@
this.addInput("sides", "number");
this.addInput("radius", "number");
this.addOutput("out", "geometry");
this.properties = { sides: 6, radius: 1 }
this.properties = { sides: 6, radius: 1, uvs: false }
this.geometry = {
type: "line_loop",
@@ -768,6 +813,13 @@
if( !vertices || vertices.length != num )
vertices = this.geometry.vertices = new Float32Array( 3*sides );
var delta = (Math.PI * 2) / sides;
var gen_uvs = this.properties.uvs;
if(gen_uvs)
{
uvs = this.geometry.coords = new Float32Array( 3*sides );
}
for(var i = 0; i < sides; ++i)
{
var angle = delta * -i;
@@ -777,6 +829,12 @@
vertices[i*3] = x;
vertices[i*3+1] = y;
vertices[i*3+2] = z;
if(gen_uvs)
{
}
}
this.geometry._id = ++this.geometry_id;
this.geometry._version = ++this.version;

View File

@@ -1,5 +1,6 @@
(function(global) {
var LiteGraph = global.LiteGraph;
var LGraphTexture = global.LGraphTexture;
//Works with Litegl.js to create WebGL nodes
if (typeof GL != "undefined") {

1588
src/nodes/glshaders.js Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
(function(global) {
var LiteGraph = global.LiteGraph;
var LGraphCanvas = global.LGraphCanvas;
//Works with Litegl.js to create WebGL nodes
global.LGraphTexture = null;
@@ -33,14 +34,16 @@
LGraphTexture.image_preview_size = 256;
//flags to choose output texture type
LGraphTexture.PASS_THROUGH = 1; //do not apply FX
LGraphTexture.UNDEFINED = 0; //not specified
LGraphTexture.PASS_THROUGH = 1; //do not apply FX (like disable but passing the in to the out)
LGraphTexture.COPY = 2; //create new texture with the same properties as the origin texture
LGraphTexture.LOW = 3; //create new texture with low precision (byte)
LGraphTexture.HIGH = 4; //create new texture with high precision (half-float)
LGraphTexture.REUSE = 5; //reuse input texture
LGraphTexture.DEFAULT = 2;
LGraphTexture.DEFAULT = 2; //use the default
LGraphTexture.MODE_VALUES = {
"undefined": LGraphTexture.UNDEFINED,
"pass through": LGraphTexture.PASS_THROUGH,
copy: LGraphTexture.COPY,
low: LGraphTexture.LOW,
@@ -113,11 +116,12 @@
!target ||
target.width != origin.width ||
target.height != origin.height ||
target.type != tex_type
target.type != tex_type ||
target.format != origin.format
) {
target = new GL.Texture(origin.width, origin.height, {
type: tex_type,
format: gl.RGBA,
format: origin.format,
filter: gl.LINEAR
});
}
@@ -659,7 +663,7 @@
u_texture: 0,
u_textureB: 1,
value: value,
texSize: [width, height],
texSize: [width, height,1/width,1/height],
time: time
})
.draw(mesh);
@@ -674,7 +678,7 @@
uniform sampler2D u_texture;\n\
uniform sampler2D u_textureB;\n\
varying vec2 v_coord;\n\
uniform vec2 texSize;\n\
uniform vec4 texSize;\n\
uniform float time;\n\
uniform float value;\n\
\n\
@@ -711,6 +715,20 @@
LGraphTextureOperation.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz");
LGraphTextureOperation.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0");
LGraphTextureOperation.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )");
LGraphTextureOperation.registerPreset("normalmap","\n\
float z0 = texture2D(u_texture, uv + vec2(-texSize.z, -texSize.w) ).x;\n\
float z1 = texture2D(u_texture, uv + vec2(0.0, -texSize.w) ).x;\n\
float z2 = texture2D(u_texture, uv + vec2(texSize.z, -texSize.w) ).x;\n\
float z3 = texture2D(u_texture, uv + vec2(-texSize.z, 0.0) ).x;\n\
float z4 = color.x;\n\
float z5 = texture2D(u_texture, uv + vec2(texSize.z, 0.0) ).x;\n\
float z6 = texture2D(u_texture, uv + vec2(-texSize.z, texSize.w) ).x;\n\
float z7 = texture2D(u_texture, uv + vec2(0.0, texSize.w) ).x;\n\
float z8 = texture2D(u_texture, uv + vec2(texSize.z, texSize.w) ).x;\n\
vec3 normal = vec3( z2 + 2.0*z4 + z7 - z0 - 2.0*z3 - z5, z5 + 2.0*z6 + z7 -z0 - 2.0*z1 - z2, 1.0 );\n\
normal.xy *= value;\n\
result.xyz = normalize(normal) * 0.5 + vec3(0.5);\n\
");
LGraphTextureOperation.registerPreset("threshold","vec3(color.x > colorB.x * value ? 1.0 : 0.0,color.y > colorB.y * value ? 1.0 : 0.0,color.z > colorB.z * value ? 1.0 : 0.0)");
//webglstudio stuff...
@@ -742,15 +760,14 @@
precision: LGraphTexture.DEFAULT
};
this.properties.code =
"//time: time in seconds\n//texSize: vec2 with res\nuniform float u_value;\nuniform vec4 u_color;\n\nvoid main() {\n vec2 uv = v_coord;\n vec3 color = vec3(0.0);\n //your code here\n color.xy=uv;\n\ngl_FragColor = vec4(color, 1.0);\n}\n";
this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec2.create(), time: 0 };
this.properties.code = LGraphTextureShader.pixel_shader;
this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec4.create(), time: 0 };
}
LGraphTextureShader.title = "Shader";
LGraphTextureShader.desc = "Texture shader";
LGraphTextureShader.widgets_info = {
code: { type: "code" },
code: { type: "code", lang: "glsl" },
precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }
};
@@ -853,10 +870,7 @@
}
this._shader_code = this.properties.code;
this._shader = new GL.Shader(
Shader.SCREEN_VERTEX_SHADER,
LGraphTextureShader.pixel_shader + this.properties.code
);
this._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, this.properties.code );
if (!this._shader) {
this.boxcolor = "red";
return null;
@@ -913,6 +927,8 @@
}
uniforms.texSize[0] = w;
uniforms.texSize[1] = h;
uniforms.texSize[2] = 1/w;
uniforms.texSize[3] = 1/h;
uniforms.time = this.graph.getTime();
uniforms.u_value = this.properties.u_value;
uniforms.u_color.set( this.properties.u_color );
@@ -929,10 +945,20 @@
};
LGraphTextureShader.pixel_shader =
"precision highp float;\n\
\n\
varying vec2 v_coord;\n\
uniform float time;\n\
"precision highp float;\n\
\n\
varying vec2 v_coord;\n\
uniform float time; //time in seconds\n\
uniform vec4 texSize; //tex resolution\n\
uniform float u_value;\n\
uniform vec4 u_color;\n\n\
void main() {\n\
vec2 uv = v_coord;\n\
vec3 color = vec3(0.0);\n\
//your code here\n\
color.xy=uv;\n\n\
gl_FragColor = vec4(color, 1.0);\n\
}\n\
";
LiteGraph.registerNodeType("texture/shader", LGraphTextureShader);
@@ -1206,6 +1232,20 @@
LGraphTextureToViewport.desc = "Texture to viewport";
LGraphTextureToViewport._prev_viewport = new Float32Array(4);
LGraphTextureToViewport.prototype.onDrawBackground = function( ctx )
{
if ( this.flags.collapsed || this.size[1] <= 40 )
return;
var tex = this.getInputData(0);
if (!tex) {
return;
}
ctx.drawImage( ctx == gl ? tex : gl.canvas, 10,30, this.size[0] -20, this.size[1] -40);
}
LGraphTextureToViewport.prototype.onExecute = function() {
var tex = this.getInputData(0);
if (!tex) {
@@ -1573,6 +1613,69 @@
LGraphTextureDownsample
);
function LGraphTextureResize() {
this.addInput("Texture", "Texture");
this.addOutput("", "Texture");
this.properties = {
size: [512,512],
generate_mipmaps: false,
precision: LGraphTexture.DEFAULT
};
}
LGraphTextureResize.title = "Resize";
LGraphTextureResize.desc = "Resize Texture";
LGraphTextureResize.widgets_info = {
iterations: { type: "number", step: 1, precision: 0, min: 0 },
precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureResize.prototype.onExecute = function() {
var tex = this.getInputData(0);
if (!tex && !this._temp_texture) {
return;
}
if (!this.isOutputConnected(0)) {
return;
} //saves work
//we do not allow any texture different than texture 2D
if (!tex || tex.texture_type !== GL.TEXTURE_2D) {
return;
}
var width = this.properties.size[0] | 0;
var height = this.properties.size[1] | 0;
if(width == 0)
width = tex.width;
if(height == 0)
height = tex.height;
var type = tex.type;
if (this.properties.precision === LGraphTexture.LOW) {
type = gl.UNSIGNED_BYTE;
} else if (this.properties.precision === LGraphTexture.HIGH) {
type = gl.HIGH_PRECISION_FORMAT;
}
if( !this._texture || this._texture.width != width || this._texture.height != height || this._texture.type != type )
this._texture = new GL.Texture( width, height, { type: type } );
tex.copyTo( this._texture );
if (this.properties.generate_mipmaps) {
this._texture.bind(0);
gl.generateMipmap(this._texture.texture_type);
this._texture.unbind(0);
}
this.setOutputData(0, this._texture);
};
LiteGraph.registerNodeType( "texture/resize", LGraphTextureResize );
// Texture Average *****************************************
function LGraphTextureAverage() {
this.addInput("Texture", "Texture");
@@ -2234,6 +2337,130 @@
LiteGraph.registerNodeType("texture/LUT", LGraphTextureLUT);
// Texture LUT *****************************************
function LGraphTextureEncode() {
this.addInput("Texture", "Texture");
this.addInput("Atlas", "Texture");
this.addOutput("", "Texture");
this.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, generate_mipmaps: false, texture: null };
if (!LGraphTextureEncode._shader) {
LGraphTextureEncode._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEncode.pixel_shader );
}
this._uniforms = {
u_texture: 0,
u_textureB: 1,
u_row_simbols: 4,
u_simbol_size: 16,
u_res: vec2.create()
};
}
LGraphTextureEncode.widgets_info = {
texture: { widget: "texture" },
precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureEncode.title = "Encode";
LGraphTextureEncode.desc = "Apply a texture atlas to encode a texture";
LGraphTextureEncode.prototype.onExecute = function() {
if (!this.isOutputConnected(0)) {
return;
} //saves work
var tex = this.getInputData(0);
if (this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false) {
this.setOutputData(0, tex);
return;
}
if (!tex) {
return;
}
var symbols_tex = this.getInputData(1);
if (!symbols_tex) {
symbols_tex = LGraphTexture.getTexture(this.properties.texture);
}
if (!symbols_tex) {
this.setOutputData(0, tex);
return;
}
symbols_tex.bind(0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST );
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST );
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE );
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE );
gl.bindTexture(gl.TEXTURE_2D, null);
var uniforms = this._uniforms;
uniforms.u_row_simbols = Math.floor(this.properties.num_row_symbols);
uniforms.u_symbol_size = this.properties.symbol_size;
uniforms.u_brightness = this.properties.brightness;
uniforms.u_invert = this.properties.invert ? 1 : 0;
uniforms.u_colorize = this.properties.colorize ? 1 : 0;
this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision );
uniforms.u_res[0] = this._tex.width;
uniforms.u_res[1] = this._tex.height;
this._tex.bind(0);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST );
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST );
this._tex.drawTo(function() {
symbols_tex.bind(1);
tex.toViewport(LGraphTextureEncode._shader, uniforms);
});
if (this.properties.generate_mipmaps) {
this._tex.bind(0);
gl.generateMipmap(this._tex.texture_type);
this._tex.unbind(0);
}
this.setOutputData(0, this._tex);
};
LGraphTextureEncode.pixel_shader =
"precision highp float;\n\
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform sampler2D u_textureB;\n\
uniform float u_row_simbols;\n\
uniform float u_symbol_size;\n\
uniform float u_brightness;\n\
uniform float u_invert;\n\
uniform float u_colorize;\n\
uniform vec2 u_res;\n\
\n\
void main() {\n\
vec2 total_symbols = u_res / u_symbol_size;\n\
vec2 uv = floor(v_coord * total_symbols) / total_symbols; //pixelate \n\
vec2 local_uv = mod(v_coord * u_res, u_symbol_size) / u_symbol_size;\n\
lowp vec4 textureColor = texture2D(u_texture, uv );\n\
float lum = clamp(u_brightness * (textureColor.x + textureColor.y + textureColor.z)/3.0,0.0,1.0);\n\
if( u_invert == 1.0 ) lum = 1.0 - lum;\n\
float index = floor( lum * (u_row_simbols * u_row_simbols - 1.0));\n\
float col = mod( index, u_row_simbols );\n\
float row = u_row_simbols - floor( index / u_row_simbols ) - 1.0;\n\
vec2 simbol_uv = ( vec2( col, row ) + local_uv ) / u_row_simbols;\n\
vec4 color = texture2D( u_textureB, simbol_uv );\n\
if(u_colorize == 1.0)\n\
color *= textureColor;\n\
gl_FragColor = color;\n\
}\n\
";
LiteGraph.registerNodeType("texture/encode", LGraphTextureEncode);
// Texture Channels *****************************************
function LGraphTextureChannels() {
this.addInput("Texture", "Texture");
@@ -3044,8 +3271,8 @@
};
this._uniforms = {
u_texture: 0,
u_near: 0.1,
u_far: 10000
u_camera_planes: null, //filled later
u_ires: vec2.create()
};
}
@@ -3077,9 +3304,6 @@
}
var uniforms = this._uniforms;
uniforms.u_near = tex.near_far_planes[0];
uniforms.u_far = tex.near_far_planes[1];
uniforms.u_invert = this.properties.invert ? 1 : 0;
gl.disable(gl.BLEND);
@@ -3099,6 +3323,8 @@
planes = [0.1, 1000];
} //hardcoded
uniforms.u_camera_planes = planes;
//uniforms.u_ires.set([1/tex.width, 1/tex.height]);
uniforms.u_ires.set([0,0]);
this._temp_texture.drawTo(function() {
tex.bind(0);
@@ -3114,15 +3340,14 @@
precision highp float;\n\
varying vec2 v_coord;\n\
uniform sampler2D u_texture;\n\
uniform float u_near;\n\
uniform float u_far;\n\
uniform vec2 u_camera_planes;\n\
uniform int u_invert;\n\
uniform vec2 u_ires;\n\
\n\
void main() {\n\
float zNear = u_near;\n\
float zFar = u_far;\n\
float depth = texture2D(u_texture, v_coord).x;\n\
depth = depth * 2.0 - 1.0;\n\
float zNear = u_camera_planes.x;\n\
float zFar = u_camera_planes.y;\n\
float depth = texture2D(u_texture, v_coord + u_ires*0.5).x * 2.0 - 1.0;\n\
float f = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\
if( u_invert == 1 )\n\
f = 1.0 - f;\n\
@@ -4256,6 +4481,54 @@ void main(void){\n\
LiteGraph.registerNodeType("texture/lensfx", LGraphLensFX);
function LGraphTextureFromData() {
this.addInput("in", "");
this.properties = { precision: LGraphTexture.LOW, width: 0, height: 0, channels: 1 };
this.addOutput("out", "Texture");
}
LGraphTextureFromData.title = "Data->Tex";
LGraphTextureFromData.desc = "Generates or applies a curve to a texture";
LGraphTextureFromData.widgets_info = {
precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureFromData.prototype.onExecute = function() {
if (!this.isOutputConnected(0)) {
return;
} //saves work
var data = this.getInputData(0);
if(!data)
return;
var channels = this.properties.channels;
var w = this.properties.width;
var h = this.properties.height;
if(!w || !h)
{
w = Math.floor(data.length / channels);
h = 1;
}
var format = gl.RGBA;
if( channels == 3 )
format = gl.RGB;
else if( channels == 1 )
format = gl.LUMINANCE;
var temp = this._temp_texture;
var type = LGraphTexture.getTextureType( this.properties.precision );
if ( !temp || temp.width != w || temp.height != h || temp.type != type ) {
temp = this._temp_texture = new GL.Texture( w, h, { type: type, format: format, filter: gl.LINEAR } );
}
temp.uploadData( data );
this.setOutputData(0, temp);
}
LiteGraph.registerNodeType("texture/fromdata", LGraphTextureFromData);
//applies a curve (or generates one)
function LGraphTextureCurve() {
this.addInput("in", "Texture");
this.addOutput("out", "Texture");
@@ -4279,22 +4552,33 @@ void main(void){\n\
}
LGraphTextureCurve.title = "Curve";
LGraphTextureCurve.desc = "Generates or applies a curve to a texture";
LGraphTextureCurve.widgets_info = {
precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }
};
LGraphTextureCurve.prototype.onExecute = function() {
var tex = this.getInputData(0);
if (!tex) {
return;
}
if (!this.isOutputConnected(0)) {
return;
} //saves work
var tex = this.getInputData(0);
var temp = this._temp_texture;
if ( !temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type ) {
temp = this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR } );
if(!tex) //generate one texture, nothing else
{
if(this._must_update || !this._curve_texture )
this.updateCurve();
this.setOutputData(0, this._curve_texture);
return;
}
var type = LGraphTexture.getTextureType( this.properties.precision, tex );
//apply curve to input texture
if ( !temp || temp.type != type || temp.width != tex.width || temp.height != tex.height || temp.format != tex.format)
temp = this._temp_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR } );
var shader = LGraphTextureCurve._shader;
if (!shader) {
shader = LGraphTextureCurve._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureCurve.pixel_shader );
@@ -4315,7 +4599,7 @@ void main(void){\n\
});
this.setOutputData(0, temp);
};
}
LGraphTextureCurve.prototype.sampleCurve = function(f,points)
{
@@ -4732,18 +5016,18 @@ void main(void){\n\
LGraphTexturePerlin.widgets_info = {
precision: { widget: "combo", values: LGraphTexture.MODE_VALUES },
width: { type: "Number", precision: 0, step: 1 },
height: { type: "Number", precision: 0, step: 1 },
octaves: { type: "Number", precision: 0, step: 1, min: 1, max: 50 }
width: { type: "number", precision: 0, step: 1 },
height: { type: "number", precision: 0, step: 1 },
octaves: { type: "number", precision: 0, step: 1, min: 1, max: 50 }
};
LGraphTexturePerlin.prototype.onGetInputs = function() {
return [
["seed", "Number"],
["persistence", "Number"],
["octaves", "Number"],
["scale", "Number"],
["amplitude", "Number"],
["seed", "number"],
["persistence", "number"],
["octaves", "number"],
["scale", "number"],
["amplitude", "number"],
["offset", "vec2"]
];
};
@@ -4893,7 +5177,7 @@ void main(void){\n\
this.addInput("v");
this.addOutput("out", "Texture");
this.properties = {
code: "",
code: LGraphTextureCanvas2D.default_code,
width: 512,
height: 512,
clear: true,
@@ -4902,17 +5186,20 @@ void main(void){\n\
};
this._func = null;
this._temp_texture = null;
this.compileCode();
}
LGraphTextureCanvas2D.title = "Canvas2D";
LGraphTextureCanvas2D.desc = "Executes Canvas2D code inside a texture or the viewport.";
LGraphTextureCanvas2D.help = "Set width and height to 0 to match viewport size.";
LGraphTextureCanvas2D.default_code = "//vars: canvas,ctx,time\nctx.fillStyle='red';\nctx.fillRect(0,0,50,50);\n";
LGraphTextureCanvas2D.widgets_info = {
precision: { widget: "combo", values: LGraphTexture.MODE_VALUES },
code: { type: "code" },
width: { type: "Number", precision: 0, step: 1 },
height: { type: "Number", precision: 0, step: 1 }
width: { type: "number", precision: 0, step: 1 },
height: { type: "number", precision: 0, step: 1 }
};
LGraphTextureCanvas2D.prototype.onPropertyChanged = function( name, value ) {

View File

@@ -143,9 +143,7 @@
if (callback) {
callback(this);
}
that.trace(
"Image loaded, size: " + that.img.width + "x" + that.img.height
);
console.log( "Image loaded, size: " + that.img.width + "x" + that.img.height );
this.dirty = true;
that.boxcolor = "#9F9";
that.setDirtyCanvas(true);
@@ -427,7 +425,7 @@
if (name == "scale") {
this.properties[name] = parseFloat(value);
if (this.properties[name] == 0) {
this.trace("Error in scale");
console.error("Error in scale");
this.properties[name] = 1.0;
}
} else {
@@ -597,10 +595,23 @@
ImageVideo.prototype.loadVideo = function(url) {
this._video_url = url;
var pos = url.substr(0,10).indexOf(":");
var protocol = "";
if(pos != -1)
protocol = url.substr(0,pos);
var host = "";
if(protocol)
{
host = url.substr(0,url.indexOf("/",protocol.length + 3));
host = host.substr(protocol.length+3);
}
if (
this.properties.use_proxy &&
url.substr(0, 4) == "http" &&
LiteGraph.proxy
protocol &&
LiteGraph.proxy &&
host != location.host
) {
url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3);
}
@@ -615,41 +626,38 @@
var that = this;
this._video.addEventListener("loadedmetadata", function(e) {
//onload
that.trace("Duration: " + this.duration + " seconds");
that.trace("Size: " + this.videoWidth + "," + this.videoHeight);
console.log("Duration: " + this.duration + " seconds");
console.log("Size: " + this.videoWidth + "," + this.videoHeight);
that.setDirtyCanvas(true);
this.width = this.videoWidth;
this.height = this.videoHeight;
});
this._video.addEventListener("progress", function(e) {
//onload
//that.trace("loading...");
console.log("video loading...");
});
this._video.addEventListener("error", function(e) {
console.log("Error loading video: " + this.src);
that.trace("Error loading video: " + this.src);
console.error("Error loading video: " + this.src);
if (this.error) {
switch (this.error.code) {
case this.error.MEDIA_ERR_ABORTED:
that.trace("You stopped the video.");
console.error("You stopped the video.");
break;
case this.error.MEDIA_ERR_NETWORK:
that.trace("Network error - please try again later.");
console.error("Network error - please try again later.");
break;
case this.error.MEDIA_ERR_DECODE:
that.trace("Video is broken..");
console.error("Video is broken..");
break;
case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:
that.trace(
"Sorry, your browser can't play this video."
);
console.error("Sorry, your browser can't play this video.");
break;
}
}
});
this._video.addEventListener("ended", function(e) {
that.trace("Ended.");
console.log("Video Ended.");
this.play(); //loop
});
@@ -666,7 +674,7 @@
};
ImageVideo.prototype.play = function() {
if (this._video) {
if (this._video && this._video.videoWidth ) { //is loaded
this._video.play();
}
};
@@ -694,7 +702,7 @@
if (!this._video) {
return;
}
this.trace("Video paused");
console.log("Video paused");
this._video.pause();
};

View File

@@ -110,6 +110,7 @@
function MathRange() {
this.addInput("in", "number", { locked: true });
this.addOutput("out", "number", { locked: true });
this.addOutput("clamped", "number", { locked: true });
this.addProperty("in", 0);
this.addProperty("in_min", 0);
@@ -117,7 +118,7 @@
this.addProperty("out_min", 0);
this.addProperty("out_max", 1);
this.size = [80, 30];
this.size = [120, 50];
}
MathRange.title = "Range";
@@ -151,10 +152,22 @@
var in_max = this.properties.in_max;
var out_min = this.properties.out_min;
var out_max = this.properties.out_max;
/*
if( in_min > in_max )
{
in_min = in_max;
in_max = this.properties.in_min;
}
if( out_min > out_max )
{
out_min = out_max;
out_max = this.properties.out_min;
}
*/
this._last_v =
((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min;
this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min;
this.setOutputData(0, this._last_v);
this.setOutputData(1, Math.clamp( this._last_v, out_min, out_max ));
};
MathRange.prototype.onDrawBackground = function(ctx) {
@@ -223,6 +236,10 @@
this.addProperty("min", 0);
this.addProperty("max", 1);
this.addProperty("smooth", true);
this.addProperty("seed", 0);
this.addProperty("octaves", 1);
this.addProperty("persistence", 0.8);
this.addProperty("speed", 1);
this.size = [90, 30];
}
@@ -253,7 +270,22 @@
MathNoise.prototype.onExecute = function() {
var f = this.getInputData(0) || 0;
var r = MathNoise.getValue(f, this.properties.smooth);
var iterations = this.properties.octaves || 1;
var r = 0;
var amp = 1;
var seed = this.properties.seed || 0;
f += seed;
var speed = this.properties.speed || 1;
var total_amp = 0;
for(var i = 0; i < iterations; ++i)
{
r += MathNoise.getValue(f * (1+i) * speed, this.properties.smooth) * amp;
total_amp += amp;
amp *= this.properties.persistence;
if(amp < 0.001)
break;
}
r /= total_amp;
var min = this.properties.min;
var max = this.properties.max;
this._last_v = r * (max - min) + min;
@@ -595,12 +627,14 @@
//Math operation
function MathOperation() {
this.addInput("A", "number");
this.addInput("A", "number,array,object");
this.addInput("B", "number");
this.addOutput("=", "number");
this.addProperty("A", 1);
this.addProperty("B", 1);
this.addProperty("OP", "+", "enum", { values: MathOperation.values });
this._func = function(A,B) { return A + B; };
this._result = []; //only used for arrays
}
MathOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min"];
@@ -627,11 +661,34 @@
this.properties["value"] = v;
};
MathOperation.prototype.onPropertyChanged = function(name, value)
{
if (name != "OP")
return;
switch (this.properties.OP) {
case "+": this._func = function(A,B) { return A + B; }; break;
case "-": this._func = function(A,B) { return A - B; }; break;
case "x":
case "X":
case "*": this._func = function(A,B) { return A * B; }; break;
case "/": this._func = function(A,B) { return A / B; }; break;
case "%": this._func = function(A,B) { return A % B; }; break;
case "^": this._func = function(A,B) { return Math.pow(A, B); }; break;
case "max": this._func = function(A,B) { return Math.max(A, B); }; break;
case "min": this._func = function(A,B) { return Math.min(A, B); }; break;
default:
console.warn("Unknown operation: " + this.properties.OP);
this._func = function(A) { return A; };
break;
}
}
MathOperation.prototype.onExecute = function() {
var A = this.getInputData(0);
var B = this.getInputData(1);
if (A != null) {
this.properties["A"] = A;
if ( A != null ) {
if( A.constructor === Number )
this.properties["A"] = A;
} else {
A = this.properties["A"];
}
@@ -642,38 +699,26 @@
B = this.properties["B"];
}
var result = 0;
switch (this.properties.OP) {
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;
case "max":
result = Math.max(A, B);
break;
case "min":
result = Math.min(A, B);
break;
default:
console.warn("Unknown operation: " + this.properties.OP);
}
this.setOutputData(0, result);
var result;
if(A.constructor === Number)
{
result = 0;
result = this._func(A,B);
}
else if(A.constructor === Array)
{
result = this._result;
result.length = A.length;
for(var i = 0; i < A.length; ++i)
result[i] = this._func(A[i],B);
}
else
{
result = {};
for(var i in A)
result[i] = this._func(A[i],B);
}
this.setOutputData(0, result);
};
MathOperation.prototype.onDrawBackground = function(ctx) {
@@ -704,6 +749,7 @@
title: "MIN()"
});
//Math compare
function MathCompare() {
this.addInput("A", "number");
@@ -808,6 +854,7 @@
this.addProperty("A", 1);
this.addProperty("B", 1);
this.addProperty("OP", ">", "enum", { values: MathCondition.values });
this.addWidget("combo","Cond.",this.properties.OP,{ property: "OP", values: MathCondition.values } );
this.size = [80, 60];
}
@@ -875,6 +922,37 @@
LiteGraph.registerNodeType("math/condition", MathCondition);
function MathBranch() {
this.addInput("in", "");
this.addInput("cond", "boolean");
this.addOutput("true", "");
this.addOutput("false", "");
this.size = [80, 60];
}
MathBranch.title = "Branch";
MathBranch.desc = "If condition is true, outputs IN in true, otherwise in false";
MathBranch.prototype.onExecute = function() {
var V = this.getInputData(0);
var cond = this.getInputData(1);
if(cond)
{
this.setOutputData(0, V);
this.setOutputData(1, null);
}
else
{
this.setOutputData(0, null);
this.setOutputData(1, V);
}
}
LiteGraph.registerNodeType("math/branch", MathBranch);
function MathAccumulate() {
this.addInput("inc", "number");
this.addOutput("total", "number");
@@ -1247,141 +1325,4 @@
LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4);
//if glMatrix is installed...
if (global.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";
Math3DRotation.desc = "quaternion rotation";
Math3DRotation.prototype.onExecute = function() {
var angle = this.getInputData(0);
if (angle == null) {
angle = this.properties.angle;
}
var axis = this.getInputData(1);
if (axis == null) {
axis = this.properties.axis;
}
var R = quat.setAxisAngle(this._value, axis, angle * 0.0174532925);
this.setOutputData(0, R);
};
LiteGraph.registerNodeType("math3d/rotation", Math3DRotation);
//Math3D rotate vec3
function Math3DRotateVec3() {
this.addInputs([["vec3", "vec3"], ["quat", "quat"]]);
this.addOutput("result", "vec3");
this.properties = { vec: [0, 0, 1] };
}
Math3DRotateVec3.title = "Rot. Vec3";
Math3DRotateVec3.desc = "rotate a point";
Math3DRotateVec3.prototype.onExecute = function() {
var vec = this.getInputData(0);
if (vec == null) {
vec = this.properties.vec;
}
var quat = this.getInputData(1);
if (quat == null) {
this.setOutputData(vec);
} else {
this.setOutputData(
0,
vec3.transformQuat(vec3.create(), vec, quat)
);
}
};
LiteGraph.registerNodeType("math3d/rotate_vec3", Math3DRotateVec3);
function Math3DMultQuat() {
this.addInputs([["A", "quat"], ["B", "quat"]]);
this.addOutput("A*B", "quat");
this._value = quat.create();
}
Math3DMultQuat.title = "Mult. Quat";
Math3DMultQuat.desc = "rotate quaternion";
Math3DMultQuat.prototype.onExecute = function() {
var A = this.getInputData(0);
if (A == null) {
return;
}
var B = this.getInputData(1);
if (B == null) {
return;
}
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
})(this);

View File

@@ -68,12 +68,22 @@
function Math3DOperation() {
this.addInput("A", "number,vec3");
this.addInput("B", "number,vec3");
this.addOutput("=", "vec3");
this.addOutput("=", "number,vec3");
this.addProperty("OP", "+", "enum", { values: Math3DOperation.values });
this._result = vec3.create();
}
Math3DOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min"];
Math3DOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min","dot","cross"];
LiteGraph.registerSearchboxExtra("math3d/operation", "CROSS()", {
properties: {"OP":"cross"},
title: "CROSS()"
});
LiteGraph.registerSearchboxExtra("math3d/operation", "DOT()", {
properties: {"OP":"dot"},
title: "DOT()"
});
Math3DOperation.title = "Operation";
Math3DOperation.desc = "Easy math 3D operators";
@@ -135,6 +145,11 @@
result[0] = Math.min(A[0],B[0]);
result[1] = Math.min(A[1],B[1]);
result[2] = Math.min(A[2],B[2]);
case "dot":
result = vec3.dot(A,B);
break;
case "cross":
vec3.cross(result,A,B);
break;
default:
console.warn("Unknown operation: " + this.properties.OP);
@@ -352,6 +367,53 @@
LiteGraph.registerNodeType("math3d/rotation", Math3DRotation);
function MathEulerToQuat() {
this.addInput("euler", "vec3");
this.addOutput("quat", "quat");
this.properties = { euler:[0,0,0], use_yaw_pitch_roll: false };
this._degs = vec3.create();
this._value = quat.create();
}
MathEulerToQuat.title = "Euler->Quat";
MathEulerToQuat.desc = "Converts euler angles (in degrees) to quaternion";
MathEulerToQuat.prototype.onExecute = function() {
var euler = this.getInputData(0);
if (euler == null) {
euler = this.properties.euler;
}
vec3.scale( this._degs, euler, DEG2RAD );
if(this.properties.use_yaw_pitch_roll)
this._degs = [this._degs[2],this._degs[0],this._degs[1]];
var R = quat.fromEuler(this._value, this._degs);
this.setOutputData(0, R);
};
LiteGraph.registerNodeType("math3d/euler_to_quat", MathEulerToQuat);
function MathQuatToEuler() {
this.addInput(["quat", "quat"]);
this.addOutput("euler", "vec3");
this._value = vec3.create();
}
MathQuatToEuler.title = "Euler->Quat";
MathQuatToEuler.desc = "Converts rotX,rotY,rotZ in degrees to quat";
MathQuatToEuler.prototype.onExecute = function() {
var q = this.getInputData(0);
if(!q)
return;
var R = quat.toEuler(this._value, q);
vec3.scale( this._value, this._value, DEG2RAD );
this.setOutputData(0, this._value);
};
LiteGraph.registerNodeType("math3d/quat_to_euler", MathQuatToEuler);
//Math3D rotate vec3
function Math3DRotateVec3() {
this.addInputs([["vec3", "vec3"], ["quat", "quat"]]);
@@ -464,6 +526,21 @@
var target_min = this.properties.target_min;
var target_max = this.properties.target_max;
//swap to avoid errors
/*
if(range_min > range_max)
{
range_min = range_max;
range_max = this.properties.range_min;
}
if(target_min > target_max)
{
target_min = target_max;
target_max = this.properties.target_min;
}
*/
for(var i = 0; i < 3; ++i)
{
var r = range_max[i] - range_min[i];

View File

@@ -328,7 +328,7 @@
MIDIEvent.commands_reversed[MIDIEvent.commands[i]] = i;
}
//MIDI wrapper
//MIDI wrapper, instantiate by MIDIIn and MIDIOut
function MIDIInterface(on_ready, on_error) {
if (!navigator.requestMIDIAccess) {
this.error = "not suppoorted";
@@ -347,9 +347,12 @@
cc: []
};
navigator
.requestMIDIAccess()
.then(this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this));
this.input_ports = null;
this.input_ports_info = [];
this.output_ports = null;
this.output_ports_info = [];
navigator.requestMIDIAccess().then(this.onMIDISuccess.bind(this), this.onMIDIFailure.bind(this));
}
MIDIInterface.input = null;
@@ -370,80 +373,34 @@
MIDIInterface.prototype.updatePorts = function() {
var midi = this.midi;
this.input_ports = midi.inputs;
this.input_ports_info = [];
this.output_ports = midi.outputs;
this.output_ports_info = [];
var num = 0;
var it = this.input_ports.values();
var it_value = it.next();
while (it_value && it_value.done === false) {
var port_info = it_value.value;
console.log(
"Input port [type:'" +
port_info.type +
"'] id:'" +
port_info.id +
"' manufacturer:'" +
port_info.manufacturer +
"' name:'" +
port_info.name +
"' version:'" +
port_info.version +
"'"
);
this.input_ports_info.push(port_info);
console.log( "Input port [type:'" + port_info.type + "'] id:'" + port_info.id + "' manufacturer:'" + port_info.manufacturer + "' name:'" + port_info.name + "' version:'" + port_info.version + "'" );
num++;
it_value = it.next();
}
this.num_input_ports = num;
num = 0;
this.output_ports = midi.outputs;
var it = this.output_ports.values();
var it_value = it.next();
while (it_value && it_value.done === false) {
var port_info = it_value.value;
console.log(
"Output port [type:'" +
port_info.type +
"'] id:'" +
port_info.id +
"' manufacturer:'" +
port_info.manufacturer +
"' name:'" +
port_info.name +
"' version:'" +
port_info.version +
"'"
);
this.output_ports_info.push(port_info);
console.log( "Output port [type:'" + port_info.type + "'] id:'" + port_info.id + "' manufacturer:'" + port_info.manufacturer + "' name:'" + port_info.name + "' version:'" + port_info.version + "'" );
num++;
it_value = it.next();
}
this.num_output_ports = num;
/* OLD WAY
for (var i = 0; i < this.input_ports.size; ++i) {
var input = this.input_ports.get(i);
if(!input)
continue; //sometimes it is null?!
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);
if(!output)
continue;
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) {
@@ -493,7 +450,7 @@
return;
}
var output_port = this.output_ports.get("output-" + port);
var output_port = this.output_ports_info[port];//this.output_ports.get("output-" + port);
if (!output_port) {
return;
}
@@ -540,10 +497,9 @@
if (name == "port") {
var values = {};
for (var i = 0; i < this._midi.input_ports.size; ++i) {
var input = this._midi.input_ports.get("input-" + i);
values[i] =
i + ".- " + input.name + " version:" + input.version;
for (var i = 0; i < this._midi.input_ports_info.length; ++i) {
var input = this._midi.input_ports_info[i];
values[i] = i + ".- " + input.name + " version:" + input.version;
}
return { type: "enum", values: values };
}
@@ -641,7 +597,10 @@
var that = this;
new MIDIInterface(function(midi) {
that._midi = midi;
that.widget.options.values = that.getMIDIOutputs();
});
this.widget = this.addWidget("combo","Device",this.properties.port,{ property: "port", values: this.getMIDIOutputs.bind(this) });
this.size = [340,60];
}
LGMIDIOut.MIDIInterface = MIDIInterface;
@@ -650,21 +609,33 @@
LGMIDIOut.desc = "Sends MIDI to output channel";
LGMIDIOut.color = MIDI_COLOR;
LGMIDIOut.prototype.getPropertyInfo = function(name) {
LGMIDIOut.prototype.onGetPropertyInfo = 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;
}
var values = this.getMIDIOutputs();
return { type: "enum", values: values };
}
};
LGMIDIOut.default_ports = {0:"unknown"};
LGMIDIOut.prototype.getMIDIOutputs = function()
{
var values = {};
if(!this._midi)
return LGMIDIOut.default_ports;
if(this._midi.output_ports_info)
for (var i = 0; i < this._midi.output_ports_info.length; ++i) {
var output = this._midi.output_ports_info[i];
if(!output)
continue;
var name = i + ".- " + output.name + " version:" + output.version;
values[i] = name;
}
return values;
}
LGMIDIOut.prototype.onAction = function(event, midi_event) {
//console.log(midi_event);
@@ -672,7 +643,7 @@
return;
}
if (event == "send") {
this._midi.sendMIDI(this.port, midi_event);
this._midi.sendMIDI(this.properties.port, midi_event);
}
this.trigger("midi", midi_event);
};
@@ -687,6 +658,7 @@
LiteGraph.registerNodeType("midi/output", LGMIDIOut);
function LGMIDIShow() {
this.addInput("on_midi", LiteGraph.EVENT);
this._str = "";
@@ -910,6 +882,24 @@
this.properties.value1 = (v | 0) % 255;
}
break;
case "cmd":
var v = this.getInputData(i);
if (v != null) {
this.properties.cmd = v;
}
break;
case "value1":
var v = this.getInputData(i);
if (v != null) {
this.properties.value1 = Math.clamp(v|0,0,127);
}
break;
case "value2":
var v = this.getInputData(i);
if (v != null) {
this.properties.value2 = Math.clamp(v|0,0,127);
}
break;
}
}
}
@@ -978,7 +968,7 @@
};
LGMIDIEvent.prototype.onGetInputs = function() {
return [["note", "number"]];
return [["cmd", "number"],["note", "number"],["value1", "number"],["value2", "number"]];
};
LGMIDIEvent.prototype.onGetOutputs = function() {
@@ -1235,6 +1225,122 @@
LiteGraph.registerNodeType("midi/quantize", LGMIDIQuantize);
function LGMIDIFromFile() {
this.properties = {
url: "",
autoplay: true
};
this.addInput("play", LiteGraph.ACTION);
this.addInput("pause", LiteGraph.ACTION);
this.addOutput("note", LiteGraph.EVENT);
this._midi = null;
this._current_time = 0;
this._playing = false;
if (typeof MidiParser == "undefined") {
console.error(
"midi-parser.js not included, LGMidiPlay requires that library: https://raw.githubusercontent.com/colxi/midi-parser-js/master/src/main.js"
);
this.boxcolor = "red";
}
}
LGMIDIFromFile.title = "MIDI fromFile";
LGMIDIFromFile.desc = "Plays a MIDI file";
LGMIDIFromFile.color = MIDI_COLOR;
LGMIDIFromFile.prototype.onAction = function( name )
{
if(name == "play")
this.play();
else if(name == "pause")
this._playing = !this._playing;
}
LGMIDIFromFile.prototype.onPropertyChanged = function(name,value)
{
if(name == "url")
this.loadMIDIFile(value);
}
LGMIDIFromFile.prototype.onExecute = function() {
if(!this._midi)
return;
if(!this._playing)
return;
this._current_time += this.graph.elapsed_time;
var current_time = this._current_time * 100;
for(var i = 0; i < this._midi.tracks; ++i)
{
var track = this._midi.track[i];
if(!track._last_pos)
{
track._last_pos = 0;
track._time = 0;
}
var elem = track.event[ track._last_pos ];
if(elem && (track._time + elem.deltaTime) <= current_time )
{
track._last_pos++;
track._time += elem.deltaTime;
if(elem.data)
{
var midi_cmd = elem.type << 4 + elem.channel;
var midi_event = new MIDIEvent();
midi_event.setup([midi_cmd, elem.data[0], elem.data[1]]);
this.trigger("note", midi_event);
}
}
}
};
LGMIDIFromFile.prototype.play = function()
{
this._playing = true;
this._current_time = 0;
if(!this._midi)
return;
for(var i = 0; i < this._midi.tracks; ++i)
{
var track = this._midi.track[i];
track._last_pos = 0;
track._time = 0;
}
}
LGMIDIFromFile.prototype.loadMIDIFile = function(url)
{
var that = this;
LiteGraph.fetchFile( url, "arraybuffer", function(data)
{
that.boxcolor = "#AFA";
that._midi = MidiParser.parse( new Uint8Array(data) );
if(that.properties.autoplay)
that.play();
}, function(err){
that.boxcolor = "#FAA";
that._midi = null;
});
}
LGMIDIFromFile.prototype.onDropFile = function(file)
{
this.properties.url = "";
this.loadMIDIFile( file );
}
LiteGraph.registerNodeType("midi/fromFile", LGMIDIFromFile);
function LGMIDIPlay() {
this.properties = {
volume: 0.5,

View File

@@ -86,10 +86,10 @@
this._ws.onmessage = function(e) {
that.boxcolor = "#AFA";
var data = JSON.parse(e.data);
if (data.room && data.room != this.properties.room) {
if (data.room && data.room != that.properties.room) {
return;
}
if (e.data.type == 1) {
if (data.type == 1) {
if (
data.data.object_class &&
LiteGraph[data.data.object_class]
@@ -105,7 +105,7 @@
that.triggerSlot(0, data.data);
}
} else {
that._last_received_data[e.data.channel || 0] = data.data;
that._last_received_data[data.channel || 0] = data.data;
}
};
this._ws.onerror = function(e) {

View File

@@ -6,7 +6,7 @@
return String(a);
}
LiteGraph.wrapFunctionAsNode("string/toString", compare, ["*"], "String");
LiteGraph.wrapFunctionAsNode("string/toString", compare, [""], "String");
function compare(a, b) {
return a == b;
@@ -15,8 +15,8 @@
LiteGraph.wrapFunctionAsNode(
"string/compare",
compare,
["String", "String"],
"Boolean"
["string", "string"],
"boolean"
);
function concatenate(a, b) {
@@ -32,8 +32,8 @@
LiteGraph.wrapFunctionAsNode(
"string/concatenate",
concatenate,
["String", "String"],
"String"
["string", "string"],
"string"
);
function contains(a, b) {
@@ -46,8 +46,8 @@
LiteGraph.wrapFunctionAsNode(
"string/contains",
contains,
["String", "String"],
"Boolean"
["string", "string"],
"boolean"
);
function toUpperCase(a) {
@@ -60,22 +60,33 @@
LiteGraph.wrapFunctionAsNode(
"string/toUpperCase",
toUpperCase,
["String"],
"String"
["string"],
"string"
);
function split(a, b) {
if (a != null && a.constructor === String) {
return a.split(b || " ");
}
return [a];
function split(str, separator) {
if(separator == null)
separator = this.properties.separator;
if (str == null )
return [];
if( str.constructor === String )
return str.split(separator || " ");
else if( str.constructor === Array )
{
var r = [];
for(var i = 0; i < str.length; ++i)
r[i] = str[i].split(separator || " ");
return r;
}
return null;
}
LiteGraph.wrapFunctionAsNode(
"string/split",
toUpperCase,
["String", "String"],
"Array"
split,
["string,array", "string"],
"array",
{ separator: "," }
);
function toFixed(a) {
@@ -88,8 +99,39 @@
LiteGraph.wrapFunctionAsNode(
"string/toFixed",
toFixed,
["Number"],
"String",
["number"],
"string",
{ precision: 0 }
);
function StringToTable() {
this.addInput("", "string");
this.addOutput("table", "table");
this.addOutput("rows", "number");
this.addProperty("value", "");
this.addProperty("separator", ",");
this._table = null;
}
StringToTable.title = "toTable";
StringToTable.desc = "Splits a string to table";
StringToTable.prototype.onExecute = function() {
var input = this.getInputData(0);
if(!input)
return;
var separator = this.properties.separator || ",";
if(input != this._str || separator != this._last_separator )
{
this._last_separator = separator;
this._str = input;
this._table = input.split("\n").map(function(a){ return a.trim().split(separator)});
}
this.setOutputData(0, this._table );
this.setOutputData(1, this._table ? this._table.length : 0 );
};
LiteGraph.registerNodeType("string/toTable", StringToTable);
})(this);

View File

@@ -9,6 +9,7 @@
../src/nodes/logic.js
../src/nodes/graphics.js
../src/nodes/gltextures.js
../src/nodes/glshaders.js
../src/nodes/geometry.js
../src/nodes/glfx.js
../src/nodes/midi.js

View File

@@ -7,4 +7,4 @@ app.use('/external', express.static('external'))
app.use('/demo', express.static('demo'))
app.use('/', express.static('demo'))
app.listen(80, () => console.log('Example app listening on port 80!'))
app.listen(8000, () => console.log('Example app listening on port 8000!'))