mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-08 00:50:05 +00:00
fix in widgets
This commit is contained in:
@@ -394,7 +394,6 @@
|
||||
* @method getNodeTypesCategories
|
||||
* @return {Array} array with all the names of the categories
|
||||
*/
|
||||
|
||||
getNodeTypesCategories: function( filter ) {
|
||||
var categories = { "": 1 };
|
||||
for (var i in this.registered_node_types) {
|
||||
@@ -474,6 +473,13 @@
|
||||
return target;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns if the types of two slots are compatible (taking into account wildcards, etc)
|
||||
* @method isValidConnection
|
||||
* @param {String} type_a
|
||||
* @param {String} type_b
|
||||
* @return {Boolean} true if they can be connected
|
||||
*/
|
||||
isValidConnection: function(type_a, type_b) {
|
||||
if (
|
||||
!type_a || //generic output
|
||||
@@ -509,13 +515,85 @@
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a string in the search box so when the user types it it will recommend this node
|
||||
* @method registerSearchboxExtra
|
||||
* @param {String} node_type the node recommended
|
||||
* @param {String} description text to show next to it
|
||||
* @param {Object} data it could contain info of how the node should be configured
|
||||
* @return {Boolean} true if they can be connected
|
||||
*/
|
||||
registerSearchboxExtra: function(node_type, description, data) {
|
||||
this.searchbox_extras[description.toLowerCase()] = {
|
||||
type: node_type,
|
||||
desc: description,
|
||||
data: data
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper to load files (from url using fetch or from file using FileReader)
|
||||
* @method fetchFile
|
||||
* @param {String|File|Blob} url the url of the file (or the file itself)
|
||||
* @param {String} type an string to know how to fetch it: "text","arraybuffer","json","blob"
|
||||
* @param {Function} on_complete callback(data)
|
||||
* @param {Function} on_error in case of an error
|
||||
* @return {FileReader|Promise} returns the object used to
|
||||
*/
|
||||
fetchFile: function( url, type, on_complete, on_error ) {
|
||||
var that = this;
|
||||
if(!url)
|
||||
return null;
|
||||
|
||||
type = type || "text";
|
||||
if( url.constructor === String )
|
||||
{
|
||||
if (url.substr(0, 4) == "http" && LiteGraph.proxy) {
|
||||
url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3);
|
||||
}
|
||||
return fetch(url)
|
||||
.then(function(response) {
|
||||
if(!response.ok)
|
||||
throw new Error("File not found"); //it will be catch below
|
||||
if(type == "arraybuffer")
|
||||
return response.arrayBuffer();
|
||||
else if(type == "text" || type == "string")
|
||||
return response.text();
|
||||
else if(type == "json")
|
||||
return response.json();
|
||||
else if(type == "blob")
|
||||
return response.blob();
|
||||
})
|
||||
.then(function(data) {
|
||||
if(on_complete)
|
||||
on_complete(data);
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error("error fetching file:",url);
|
||||
if(on_error)
|
||||
on_error(error);
|
||||
});
|
||||
}
|
||||
else if( url.constructor === File || url.constructor === Blob)
|
||||
{
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e)
|
||||
{
|
||||
var v = e.target.result;
|
||||
if( type == "json" )
|
||||
v = JSON.parse(v);
|
||||
if(on_complete)
|
||||
on_complete(v);
|
||||
}
|
||||
if(type == "arraybuffer")
|
||||
return reader.readAsArrayBuffer(url);
|
||||
else if(type == "text" || type == "json")
|
||||
return reader.readAsText(url);
|
||||
else if(type == "blob")
|
||||
return reader.readAsBinaryString(url);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
//timer that works everywhere
|
||||
@@ -8415,7 +8493,10 @@ LGraphNode.prototype.executeAction = function(action)
|
||||
if (values && values.constructor === Function) {
|
||||
values = w.options.values(w, node);
|
||||
}
|
||||
var values_list = values.constructor === Array ? values : Object.keys(values);
|
||||
var values_list = null;
|
||||
|
||||
if( w.type != "number")
|
||||
values_list = values.constructor === Array ? values : Object.keys(values);
|
||||
|
||||
var delta = x < 40 ? -1 : x > width - 40 ? 1 : 0;
|
||||
if (w.type == "number") {
|
||||
@@ -11481,6 +11562,15 @@ if (typeof exports != "undefined") {
|
||||
|
||||
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() {
|
||||
@@ -13751,6 +13841,7 @@ if (typeof exports != "undefined") {
|
||||
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);
|
||||
@@ -13758,7 +13849,7 @@ if (typeof exports != "undefined") {
|
||||
this.addProperty("out_min", 0);
|
||||
this.addProperty("out_max", 1);
|
||||
|
||||
this.size = [80, 30];
|
||||
this.size = [120, 50];
|
||||
}
|
||||
|
||||
MathRange.title = "Range";
|
||||
@@ -13792,10 +13883,22 @@ if (typeof exports != "undefined") {
|
||||
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) {
|
||||
@@ -15356,6 +15459,21 @@ if (typeof exports != "undefined") {
|
||||
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];
|
||||
@@ -25253,6 +25371,24 @@ function LGraphGeometryDisplace() {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25321,7 +25457,7 @@ function LGraphGeometryDisplace() {
|
||||
};
|
||||
|
||||
LGMIDIEvent.prototype.onGetInputs = function() {
|
||||
return [["note", "number"]];
|
||||
return [["cmd", "number"],["note", "number"],["value1", "number"],["value2", "number"]];
|
||||
};
|
||||
|
||||
LGMIDIEvent.prototype.onGetOutputs = function() {
|
||||
@@ -25578,6 +25714,119 @@ function LGraphGeometryDisplace() {
|
||||
|
||||
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;
|
||||
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,
|
||||
|
||||
1340
build/litegraph.min.js
vendored
1340
build/litegraph.min.js
vendored
File diff suppressed because it is too large
Load Diff
@@ -251,6 +251,21 @@
|
||||
color: #AAA;
|
||||
}
|
||||
|
||||
.litegraph-editor .dialog .dialog-content h3 {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.litegraph-editor .dialog .dialog-content .connections {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.litegraph-editor .dialog .dialog-content .connections .connections_side {
|
||||
width: calc(50% - 5px);
|
||||
min-height: 100px;
|
||||
background-color: black;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.litegraph-editor .dialog .node_type {
|
||||
font-size: 1.2em;
|
||||
display: block;
|
||||
|
||||
@@ -16,7 +16,9 @@
|
||||
<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>
|
||||
|
||||
356
demo/js/libs/midi-parser.js
Normal file
356
demo/js/libs/midi-parser.js
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
})();
|
||||
@@ -142,6 +142,16 @@ Editor.prototype.createPanel = function(title, options) {
|
||||
this.parentNode.removeChild(this);
|
||||
}
|
||||
|
||||
root.addHTML = function(code, classname)
|
||||
{
|
||||
var elem = document.createElement("div");
|
||||
if(classname)
|
||||
elem.className = classname;
|
||||
elem.innerHTML = code;
|
||||
root.content.appendChild(elem);
|
||||
return elem;
|
||||
}
|
||||
|
||||
root.addButton = function( name, callback, options )
|
||||
{
|
||||
var elem = document.createElement("button");
|
||||
@@ -152,6 +162,13 @@ Editor.prototype.createPanel = function(title, options) {
|
||||
return elem;
|
||||
}
|
||||
|
||||
root.addSeparator = function()
|
||||
{
|
||||
var elem = document.createElement("div");
|
||||
elem.className = "separator";
|
||||
root.content.appendChild(elem);
|
||||
}
|
||||
|
||||
root.addWidget = function( type, name, value, options, callback )
|
||||
{
|
||||
options = options || {};
|
||||
@@ -332,92 +349,40 @@ Editor.prototype.onShowNodePanel = function(node)
|
||||
function inner_refresh()
|
||||
{
|
||||
panel.content.innerHTML = ""; //clear
|
||||
var elem = document.createElement("div");
|
||||
elem.innerHTML = "<span class='node_type'>"+node.type+"</span><span class='node_desc'>"+(node.constructor.desc || "")+"</span><span class='separator'></span>";
|
||||
panel.content.appendChild(elem);
|
||||
panel.addHTML("<span class='node_type'>"+node.type+"</span><span class='node_desc'>"+(node.constructor.desc || "")+"</span><span class='separator'></span>");
|
||||
|
||||
panel.addHTML("<h3>Properties</h3>");
|
||||
|
||||
for(var i in node.properties)
|
||||
{
|
||||
var value = node.properties[i];
|
||||
var info = node.getPropertyInfo(i);
|
||||
var type = info.type || "string";
|
||||
|
||||
//in case the user wants control over the side panel widget
|
||||
if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(i,panel) )
|
||||
continue;
|
||||
|
||||
panel.addWidget( info.widget || info.type, i, value, info, function(name,value){
|
||||
node.setProperty(name,value);
|
||||
graphcanvas.dirty_canvas = true;
|
||||
});
|
||||
}
|
||||
|
||||
panel.addSeparator();
|
||||
|
||||
/*
|
||||
panel.addHTML("<h3>Connections</h3>");
|
||||
var connection_containers = panel.addHTML("<div class='inputs connections_side'></div><div class='outputs connections_side'></div>","connections");
|
||||
var inputs = connection_containers.querySelector(".inputs");
|
||||
var outputs = connection_containers.querySelector(".outputs");
|
||||
*/
|
||||
|
||||
|
||||
panel.addButton("Delete",function(){
|
||||
node.graph.remove(node);
|
||||
panel.close();
|
||||
}).classList.add("delete");
|
||||
|
||||
/*
|
||||
for(var i in node.properties)
|
||||
{
|
||||
var value = node.properties[i];
|
||||
var type = "string";
|
||||
var info = node.getPropertyInfo(i);
|
||||
var type = info.type;
|
||||
|
||||
var str_value = String(value);
|
||||
if(type == "number")
|
||||
str_value = value.toFixed(3);
|
||||
|
||||
var elem = document.createElement("div");
|
||||
elem.className = "property";
|
||||
elem.innerHTML = "<span class='property_name'></span><span class='property_value'></span>";
|
||||
elem.querySelector(".property_name").innerText = i;
|
||||
var value_element = elem.querySelector(".property_value");
|
||||
value_element.innerText = str_value;
|
||||
elem.dataset["property"] = i;
|
||||
elem.dataset["type"] = type;
|
||||
elem.datainfo = info;
|
||||
|
||||
if( type == "code" )
|
||||
elem.addEventListener("click", function(){ inner_showCodePad( node, this.dataset["property"] ); });
|
||||
else if (type == "boolean")
|
||||
elem.addEventListener("click", function(){
|
||||
var v = node.properties[this.dataset["property"]];
|
||||
node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false";
|
||||
});
|
||||
else if (type == "string" || type == "number")
|
||||
{
|
||||
value_element.setAttribute("contenteditable",true);
|
||||
value_element.addEventListener("change", function(){
|
||||
var v = this.innerText;
|
||||
var propname = this.parentNode.dataset["property"];
|
||||
if( propname == "number")
|
||||
v = Number(v);
|
||||
node.setProperty(propname,v);
|
||||
that.dirty_canvas = true;
|
||||
});
|
||||
}
|
||||
else if (type == "enum")
|
||||
value_element.addEventListener("click", function(event){
|
||||
var values = this.parentNode.datainfo.values || [];
|
||||
var propname = this.parentNode.dataset["property"];
|
||||
var elem_that = this;
|
||||
var menu = new LiteGraph.ContextMenu(values,{
|
||||
event: event,
|
||||
className: "dark",
|
||||
callback: inner_clicked
|
||||
},
|
||||
ref_window);
|
||||
function inner_clicked(v, option, event) {
|
||||
node.setProperty(propname,v);
|
||||
elem_that.innerText = v;
|
||||
graphcanvas.dirty_canvas = true;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
else //generic
|
||||
elem.addEventListener("click", function(){
|
||||
that.graphcanvas.showEditPropertyValue( node, this.dataset["property"], {onclose: inner_refresh} );
|
||||
});
|
||||
panel.content.appendChild(elem);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
function inner_showCodePad( node, propname )
|
||||
|
||||
@@ -392,7 +392,6 @@
|
||||
* @method getNodeTypesCategories
|
||||
* @return {Array} array with all the names of the categories
|
||||
*/
|
||||
|
||||
getNodeTypesCategories: function( filter ) {
|
||||
var categories = { "": 1 };
|
||||
for (var i in this.registered_node_types) {
|
||||
@@ -472,6 +471,13 @@
|
||||
return target;
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns if the types of two slots are compatible (taking into account wildcards, etc)
|
||||
* @method isValidConnection
|
||||
* @param {String} type_a
|
||||
* @param {String} type_b
|
||||
* @return {Boolean} true if they can be connected
|
||||
*/
|
||||
isValidConnection: function(type_a, type_b) {
|
||||
if (
|
||||
!type_a || //generic output
|
||||
@@ -507,13 +513,85 @@
|
||||
return false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Register a string in the search box so when the user types it it will recommend this node
|
||||
* @method registerSearchboxExtra
|
||||
* @param {String} node_type the node recommended
|
||||
* @param {String} description text to show next to it
|
||||
* @param {Object} data it could contain info of how the node should be configured
|
||||
* @return {Boolean} true if they can be connected
|
||||
*/
|
||||
registerSearchboxExtra: function(node_type, description, data) {
|
||||
this.searchbox_extras[description.toLowerCase()] = {
|
||||
type: node_type,
|
||||
desc: description,
|
||||
data: data
|
||||
};
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Wrapper to load files (from url using fetch or from file using FileReader)
|
||||
* @method fetchFile
|
||||
* @param {String|File|Blob} url the url of the file (or the file itself)
|
||||
* @param {String} type an string to know how to fetch it: "text","arraybuffer","json","blob"
|
||||
* @param {Function} on_complete callback(data)
|
||||
* @param {Function} on_error in case of an error
|
||||
* @return {FileReader|Promise} returns the object used to
|
||||
*/
|
||||
fetchFile: function( url, type, on_complete, on_error ) {
|
||||
var that = this;
|
||||
if(!url)
|
||||
return null;
|
||||
|
||||
type = type || "text";
|
||||
if( url.constructor === String )
|
||||
{
|
||||
if (url.substr(0, 4) == "http" && LiteGraph.proxy) {
|
||||
url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3);
|
||||
}
|
||||
return fetch(url)
|
||||
.then(function(response) {
|
||||
if(!response.ok)
|
||||
throw new Error("File not found"); //it will be catch below
|
||||
if(type == "arraybuffer")
|
||||
return response.arrayBuffer();
|
||||
else if(type == "text" || type == "string")
|
||||
return response.text();
|
||||
else if(type == "json")
|
||||
return response.json();
|
||||
else if(type == "blob")
|
||||
return response.blob();
|
||||
})
|
||||
.then(function(data) {
|
||||
if(on_complete)
|
||||
on_complete(data);
|
||||
})
|
||||
.catch(function(error) {
|
||||
console.error("error fetching file:",url);
|
||||
if(on_error)
|
||||
on_error(error);
|
||||
});
|
||||
}
|
||||
else if( url.constructor === File || url.constructor === Blob)
|
||||
{
|
||||
var reader = new FileReader();
|
||||
reader.onload = function(e)
|
||||
{
|
||||
var v = e.target.result;
|
||||
if( type == "json" )
|
||||
v = JSON.parse(v);
|
||||
if(on_complete)
|
||||
on_complete(v);
|
||||
}
|
||||
if(type == "arraybuffer")
|
||||
return reader.readAsArrayBuffer(url);
|
||||
else if(type == "text" || type == "json")
|
||||
return reader.readAsText(url);
|
||||
else if(type == "blob")
|
||||
return reader.readAsBinaryString(url);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
//timer that works everywhere
|
||||
@@ -8413,7 +8491,10 @@ LGraphNode.prototype.executeAction = function(action)
|
||||
if (values && values.constructor === Function) {
|
||||
values = w.options.values(w, node);
|
||||
}
|
||||
var values_list = values.constructor === Array ? values : Object.keys(values);
|
||||
var values_list = null;
|
||||
|
||||
if( w.type != "number")
|
||||
values_list = values.constructor === Array ? values : Object.keys(values);
|
||||
|
||||
var delta = x < 40 ? -1 : x > width - 40 ? 1 : 0;
|
||||
if (w.type == "number") {
|
||||
|
||||
@@ -530,6 +530,15 @@
|
||||
|
||||
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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -464,6 +464,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];
|
||||
|
||||
@@ -882,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -950,7 +968,7 @@
|
||||
};
|
||||
|
||||
LGMIDIEvent.prototype.onGetInputs = function() {
|
||||
return [["note", "number"]];
|
||||
return [["cmd", "number"],["note", "number"],["value1", "number"],["value2", "number"]];
|
||||
};
|
||||
|
||||
LGMIDIEvent.prototype.onGetOutputs = function() {
|
||||
@@ -1207,6 +1225,119 @@
|
||||
|
||||
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;
|
||||
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,
|
||||
|
||||
Reference in New Issue
Block a user