repo_description: A graph node engine and editor written in Javascript similar to PD or UDK Blueprints, comes with its own editor in HTML5 Canvas2D. The engine can run client side or server side using Node. It allows to export graphs as JSONs to be included in applications independently.
repo_topics: blueprints canvas2d editor graph graphs javascript modular nocode node-graph nodejs nodes visual visual-programming workflow
code: (function(global) {
      var LiteGraph = global.LiteGraph;
      var MIDI_COLOR = "#243";
  
      function MIDIEvent(data) {
          this.channel = 0;
          this.cmd = 0;
          this.data = new Uint32Array(3);
  
          if (data) {
              this.setup(data);
          }
      }
  
      LiteGraph.MIDIEvent = MIDIEvent;
  
      MIDIEvent.prototype.fromJSON = function(o) {
          this.setup(o.data);
      };
  
      MIDIEvent.prototype.setup = function(data) {
          var raw_data = data;
          if (data.constructor === Object) {
              raw_data = data.data;
          }
  
          this.data.set(raw_data);
  
          var midiStatus = raw_data[0];
          this.status = midiStatus;
  
          var midiCommand = midiStatus & 0xf0;
  
          if (midiStatus >= 0xf0) {
              this.cmd = midiStatus;
          } else {
              this.cmd = midiCommand;
          }
  
          if (this.cmd == MIDIEvent.NOTEON && this.velocity == 0) {
              this.cmd = MIDIEvent.NOTEOFF;
          }
  
          this.cmd_str = MIDIEvent.commands[this.cmd] || "";
  
          if (
              midiCommand >= MIDIEvent.NOTEON ||
              midiCommand <= MIDIEvent.NOTEOFF
          ) {
              this.channel = midiStatus & 0x0f;
          }
      };
  
      Object.defineProperty(MIDIEvent.prototype, "velocity", {
          get: function() {
              if (this.cmd == MIDIEvent.NOTEON) {
                  return this.data[2];
              }
              return -1;
          },
          set: function(v) {
              this.data[2] = v; //  v / 127;
          },
          enumerable: true
      });
  
      MIDIEvent.notes = [
          "A",
          "A#",
          "B",
          "C",
          "C#",
          "D",
          "D#",
          "E",
          "F",
          "F#",
          "G",
          "G#"
      ];
      MIDIEvent.note_to_index = {
          A: 0,
          "A#": 1,
          B: 2,
          C: 3,
          "C#": 4,
          D: 5,
          "D#": 6,
          E: 7,
          F: 8,
          "F#": 9,
          G: 10,
          "G#": 11
      };
  
      Object.defineProperty(MIDIEvent.prototype, "note", {
          get: function() {
              if (this.cmd != MIDIEvent.NOTEON) {
                  return -1;
              }
              return MIDIEvent.toNoteString(this.data[1], true);
          },
          set: function(v) {
              throw "notes cannot be assigned this way, must modify the data[1]";
          },
          enumerable: true
      });
  
      Object.defineProperty(MIDIEvent.prototype, "octave", {
          get: function() {
              if (this.cmd != MIDIEvent.NOTEON) {
                  return -1;
              }
              var octave = this.data[1] - 24;
              return Math.floor(octave / 12 + 1);
          },
          set: function(v) {
              throw "octave cannot be assigned this way, must modify the data[1]";
          },
          enumerable: true
      });
  
      //returns HZs
      MIDIEvent.prototype.getPitch = function() {
          return Math.pow(2, (this.data[1] - 69) / 12) * 440;
      };
  
      MIDIEvent.computePitch = function(note) {
          return Math.pow(2, (note - 69) / 12) * 440;
      };
  
      MIDIEvent.prototype.getCC = function() {
          return this.data[1];
      };
  
      MIDIEvent.prototype.getCCValue = function() {
          return this.data[2];
      };
  
      //not tested, there is a formula missing here
      MIDIEvent.prototype.getPitchBend = function() {
          return this.data[1] + (this.data[2] << 7) - 8192;
      };
  
      MIDIEvent.computePitchBend = function(v1, v2) {
          return v1 + (v2 << 7) - 8192;
      };
  
      MIDIEvent.prototype.setCommandFromString = function(str) {
          this.cmd = MIDIEvent.computeCommandFromString(str);
      };
  
      MIDIEvent.computeCommandFromString = function(str) {
          if (!str) {
              return 0;
          }
  
          if (str && str.constructor === Number) {
              return str;
          }
  
          str = str.toUpperCase();
          switch (str) {
              case "NOTE ON":
              case "NOTEON":
                  return MIDIEvent.NOTEON;
                  break;
              case "NOTE OFF":
              case "NOTEOFF":
                  return MIDIEvent.NOTEON;
                  break;
              case "KEY PRESSURE":
              case "KEYPRESSURE":
                  return MIDIEvent.KEYPRESSURE;
                  break;
              case "CONTROLLER CHANGE":
              case "CONTROLLERCHANGE":
              case "CC":
                  return MIDIEvent.CONTROLLERCHANGE;
                  break;
              case "PROGRAM CHANGE":
              case "PROGRAMCHANGE":
              case "PC":
                  return MIDIEvent.PROGRAMCHANGE;
                  break;
              case "CHANNEL PRESSURE":
              case "CHANNELPRESSURE":
                  return MIDIEvent.CHANNELPRESSURE;
                  break;
              case "PITCH BEND":
              case "PITCHBEND":
                  return MIDIEvent.PITCHBEND;
                  break;
              case "TIME TICK":
              case "TIMETICK":
                  return MIDIEvent.TIMETICK;
                  break;
              default:
                  return Number(str); //assume its a hex code
          }
      };
  
      //transform from a pitch number to string like "C4"
      MIDIEvent.toNoteString = function(d, skip_octave) {
          d = Math.round(d); //in case it has decimals
          var note = d - 21;
          var octave = Math.floor((d - 24) / 12 + 1);
          note = note % 12;
          if (note < 0) {
              note = 12 + note;
          }
          return MIDIEvent.notes[note] + (skip_octave ? "" : octave);
      };
  
      MIDIEvent.NoteStringToPitch = function(str) {
          str = str.toUpperCase();
          var note = str[0];
          var octave = 4;
  
          if (str[1] == "#") {
              note += "#";
              if (str.length > 2) {
                  octave = Number(str[2]);
              }
          } else {
              if (str.length > 1) {
                  octave = Number(str[1]);
              }
          }
          var pitch = MIDIEvent.note_to_index[note];
          if (pitch == null) {
              return null;
          }
          return (octave - 1) * 12 + pitch + 21;
      };
  
      MIDIEvent.prototype.toString = function() {
          var str = "" + this.channel + ". ";
          switch (this.cmd) {
              case MIDIEvent.NOTEON:
                  str += "NOTEON " + MIDIEvent.toNoteString(this.data[1]);
                  break;
              case MIDIEvent.NOTEOFF:
                  str += "NOTEOFF " + MIDIEvent.toNoteString(this.data[1]);
                  break;
              case MIDIEvent.CONTROLLERCHANGE:
                  str += "CC " + this.data[1] + " " + this.data[2];
                  break;
              case MIDIEvent.PROGRAMCHANGE:
                  str += "PC " + this.data[1];
                  break;
              case MIDIEvent.PITCHBEND:
                  str += "PITCHBEND " + this.getPitchBend();
                  break;
              case MIDIEvent.KEYPRESSURE:
                  str += "KEYPRESS " + this.data[1];
                  break;
          }
  
          return str;
      };
  
      MIDIEvent.prototype.toHexString = function() {
          var str = "";
          for (var i = 0; i < this.data.length; i++) {
              str += this.data[i].toString(16) + " ";
          }
      };
  
      MIDIEvent.prototype.toJSON = function() {
          return {
              data: [this.data[0], this.data[1], this.data[2]],
              object_class: "MIDIEvent"
          };
      };
  
      MIDIEvent.NOTEOFF = 0x80;
      MIDIEvent.NOTEON = 0x90;
      MIDIEvent.KEYPRESSURE = 0xa0;
      MIDIEvent.CONTROLLERCHANGE = 0xb0;
      MIDIEvent.PROGRAMCHANGE = 0xc0;
      MIDIEvent.CHANNELPRESSURE = 0xd0;
      MIDIEvent.PITCHBEND = 0xe0;
      MIDIEvent.TIMETICK = 0xf8;
  
      MIDIEvent.commands = {
          0x80: "note off",
          0x90: "note on",
          0xa0: "key pressure",
          0xb0: "controller change",
          0xc0: "program change",
          0xd0: "channel pressure",
          0xe0: "pitch bend",
          0xf0: "system",
          0xf2: "Song pos",
          0xf3: "Song select",
          0xf6: "Tune request",
          0xf8: "time tick",
          0xfa: "Start Song",
          0xfb: "Continue Song",
          0xfc: "Stop Song",
          0xfe: "Sensing",
          0xff: "Reset"
      };
  
      MIDIEvent.commands_short = {
          0x80: "NOTEOFF",
          0x90: "NOTEOFF",
          0xa0: "KEYP",
          0xb0: "CC",
          0xc0: "PC",
          0xd0: "CP",
          0xe0: "PB",
          0xf0: "SYS",
          0xf2: "POS",
          0xf3: "SELECT",
          0xf6: "TUNEREQ",
          0xf8: "TT",
          0xfa: "START",
          0xfb: "CONTINUE",
          0xfc: "STOP",
          0xfe: "SENS",
          0xff: "RESET"
      };
  
      MIDIEvent.commands_reversed = {};
      for (var i in MIDIEvent.commands) {
          MIDIEvent.commands_reversed[MIDIEvent.commands[i]] = i;
      }
  
      //MIDI wrapper, instantiate by MIDIIn and MIDIOut
      function MIDIInterface(on_ready, on_error) {
          if (!navigator.requestMIDIAccess) {
              this.error = "not suppoorted";
              if (on_error) {
                  on_error("Not supported");
              } else {
                  console.error("MIDI NOT SUPPORTED, enable by chrome://flags");
              }
              return;
          }
  
          this.on_ready = on_ready;
  
          this.state = {
              note: [],
              cc: []
          };
  
  		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;
  
      MIDIInterface.MIDIEvent = MIDIEvent;
  
      MIDIInterface.prototype.onMIDISuccess = function(midiAccess) {
          console.log("MIDI ready!");
          console.log(midiAccess);
          this.midi = midiAccess; // store in the global (in real usage, would probably keep in an object instance)
          this.updatePorts();
  
          if (this.on_ready) {
              this.on_ready(this);
          }
      };
  
      MIDIInterface.prototype.updatePorts = function() {
          var midi = this.midi;
          this.input_ports = midi.inputs;
  		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;
  			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;
          var it = this.output_ports.values();
          var it_value = it.next();
          while (it_value && it_value.done === false) {
              var port_info = it_value.value;
  			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;
      };
  
      MIDIInterface.prototype.onMIDIFailure = function(msg) {
          console.error("Failed to get MIDI access - " + msg);
      };
  
      MIDIInterface.prototype.openInputPort = function(port, callback) {
          var input_port = this.input_ports.get("input-" + port);
          if (!input_port) {
              return false;
          }
          MIDIInterface.input = this;
          var that = this;
  
          input_port.onmidimessage = function(a) {
              var midi_event = new MIDIEvent(a.data);
              that.updateState(midi_event);
              if (callback) {
                  callback(a.data, midi_event);
              }
              if (MIDIInterface.on_message) {
                  MIDIInterface.on_message(a.data, midi_event);
              }
          };
          console.log("port open: ", input_port);
          return true;
      };
  
      MIDIInterface.parseMsg = function(data) {};
  
      MIDIInterface.prototype.updateState = function(midi_event) {
          switch (midi_event.cmd) {
              case MIDIEvent.NOTEON:
                  this.state.note[midi_event.value1 | 0] = midi_event.value2;
                  break;
              case MIDIEvent.NOTEOFF:
                  this.state.note[midi_event.value1 | 0] = 0;
                  break;
              case MIDIEvent.CONTROLLERCHANGE:
                  this.state.cc[midi_event.getCC()] = midi_event.getCCValue();
                  break;
          }
      };
  
      MIDIInterface.prototype.sendMIDI = function(port, midi_data) {
          if (!midi_data) {
              return;
          }
  
          var output_port = this.output_ports_info[port];//this.output_ports.get("output-" + port);
          if (!output_port) {
              return;
          }
  
          MIDIInterface.output = this;
  
          if (midi_data.constructor === MIDIEvent) {
              output_port.send(midi_data.data);
          } else {
              output_port.send(midi_data);
          }
      };
  
      function LGMIDIIn() {
          this.addOutput("on_midi", LiteGraph.EVENT);
          this.addOutput("out", "midi");
          this.properties = { port: 0 };
          this._last_midi_event = null;
          this._current_midi_event = null;
          this.boxcolor = "#AAA";
          this._last_time = 0;
  
          var that = this;
          new MIDIInterface(function(midi) {
              //open
              that._midi = midi;
              if (that._waiting) {
                  that.onStart();
              }
              that._waiting = false;
          });
      }
  
      LGMIDIIn.MIDIInterface = MIDIInterface;
  
      LGMIDIIn.title = "MIDI Input";
      LGMIDIIn.desc = "Reads MIDI from a input port";
      LGMIDIIn.color = MIDI_COLOR;
  
      LGMIDIIn.prototype.getPropertyInfo = function(name) {
          if (!this._midi) {
              return;
          }
  
          if (name == "port") {
              var values = {};
              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 };
          }
      };
  
      LGMIDIIn.prototype.onStart = function() {
          if (this._midi) {
              this._midi.openInputPort(
                  this.properties.port,
                  this.onMIDIEvent.bind(this)
              );
          } else {
              this._waiting = true;
          }
      };
  
      LGMIDIIn.prototype.onMIDIEvent = function(data, midi_event) {
          this._last_midi_event = midi_event;
          this.boxcolor = "#AFA";
          this._last_time = LiteGraph.getTime();
          this.trigger("on_midi", midi_event);
          if (midi_event.cmd == MIDIEvent.NOTEON) {
              this.trigger("on_noteon", midi_event);
          } else if (midi_event.cmd == MIDIEvent.NOTEOFF) {
              this.trigger("on_noteoff", midi_event);
          } else if (midi_event.cmd == MIDIEvent.CONTROLLERCHANGE) {
              this.trigger("on_cc", midi_event);
          } else if (midi_event.cmd == MIDIEvent.PROGRAMCHANGE) {
              this.trigger("on_pc", midi_event);
          } else if (midi_event.cmd == MIDIEvent.PITCHBEND) {
              this.trigger("on_pitchbend", midi_event);
          }
      };
  
      LGMIDIIn.prototype.onDrawBackground = function(ctx) {
          this.boxcolor = "#AAA";
          if (!this.flags.collapsed && this._last_midi_event) {
              ctx.fillStyle = "white";
              var now = LiteGraph.getTime();
              var f = 1.0 - Math.max(0, (now - this._last_time) * 0.001);
              if (f > 0) {
                  var t = ctx.globalAlpha;
                  ctx.globalAlpha *= f;
                  ctx.font = "12px Tahoma";
                  ctx.fillText(
                      this._last_midi_event.toString(),
                      2,
                      this.size[1] * 0.5 + 3
                  );
                  //ctx.fillRect(0,0,this.size[0],this.size[1]);
                  ctx.globalAlpha = t;
              }
          }
      };
  
      LGMIDIIn.prototype.onExecute = function() {
          if (this.outputs) {
              var last = this._last_midi_event;
              for (var i = 0; i < this.outputs.length; ++i) {
                  var output = this.outputs[i];
                  var v = null;
                  switch (output.name) {
                      case "midi":
                          v = this._midi;
                          break;
                      case "last_midi":
                          v = last;
                          break;
                      default:
                          continue;
                  }
                  this.setOutputData(i, v);
              }
          }
      };
  
      LGMIDIIn.prototype.onGetOutputs = function() {
          return [
              ["last_midi", "midi"],
              ["on_midi", LiteGraph.EVENT],
              ["on_noteon", LiteGraph.EVENT],
              ["on_noteoff", LiteGraph.EVENT],
              ["on_cc", LiteGraph.EVENT],
              ["on_pc", LiteGraph.EVENT],
              ["on_pitchbend", LiteGraph.EVENT]
          ];
      };
  
      LiteGraph.registerNodeType("midi/input", LGMIDIIn);
  
      function LGMIDIOut() {
          this.addInput("send", LiteGraph.EVENT);
          this.properties = { port: 0 };
  
          var that = this;
          new MIDIInterface(function(midi) {
              that._midi = midi;
  			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;
  
      LGMIDIOut.title = "MIDI Output";
      LGMIDIOut.desc = "Sends MIDI to output channel";
      LGMIDIOut.color = MIDI_COLOR;
  
      LGMIDIOut.prototype.onGetPropertyInfo = function(name) {
          if (!this._midi) {
              return;
          }
  
          if (name == "port") {
  			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);
          if (!this._midi) {
              return;
          }
          if (event == "send") {
              this._midi.sendMIDI(this.properties.port, midi_event);
          }
          this.trigger("midi", midi_event);
      };
  
      LGMIDIOut.prototype.onGetInputs = function() {
          return [["send", LiteGraph.ACTION]];
      };
  
      LGMIDIOut.prototype.onGetOutputs = function() {
          return [["on_midi", LiteGraph.EVENT]];
      };
  
      LiteGraph.registerNodeType("midi/output", LGMIDIOut);
  
  
      function LGMIDIShow() {
          this.addInput("on_midi", LiteGraph.EVENT);
          this._str = "";
          this.size = [200, 40];
      }
  
      LGMIDIShow.title = "MIDI Show";
      LGMIDIShow.desc = "Shows MIDI in the graph";
      LGMIDIShow.color = MIDI_COLOR;
  
      LGMIDIShow.prototype.getTitle = function() {
          if (this.flags.collapsed) {
              return this._str;
          }
          return this.title;
      };
  
      LGMIDIShow.prototype.onAction = function(event, midi_event) {
          if (!midi_event) {
              return;
          }
          if (midi_event.constructor === MIDIEvent) {
              this._str = midi_event.toString();
          } else {
              this._str = "???";
          }
      };
  
      LGMIDIShow.prototype.onDrawForeground = function(ctx) {
          if (!this._str || this.flags.collapsed) {
              return;
          }
  
          ctx.font = "30px Arial";
          ctx.fillText(this._str, 10, this.size[1] * 0.8);
      };
  
      LGMIDIShow.prototype.onGetInputs = function() {
          return [["in", LiteGraph.ACTION]];
      };
  
      LGMIDIShow.prototype.onGetOutputs = function() {
          return [["on_midi", LiteGraph.EVENT]];
      };
  
      LiteGraph.registerNodeType("midi/show", LGMIDIShow);
  
      function LGMIDIFilter() {
          this.properties = {
              channel: -1,
              cmd: -1,
              min_value: -1,
              max_value: -1
          };
  
          var that = this;
          this._learning = false;
          this.addWidget("button", "Learn", "", function() {
              that._learning = true;
              that.boxcolor = "#FA3";
          });
  
          this.addInput("in", LiteGraph.EVENT);
          this.addOutput("on_midi", LiteGraph.EVENT);
          this.boxcolor = "#AAA";
      }
  
      LGMIDIFilter.title = "MIDI Filter";
      LGMIDIFilter.desc = "Filters MIDI messages";
      LGMIDIFilter.color = MIDI_COLOR;
  
      LGMIDIFilter["@cmd"] = {
          type: "enum",
          title: "Command",
          values: MIDIEvent.commands_reversed
      };
  
      LGMIDIFilter.prototype.getTitle = function() {
          var str = null;
          if (this.properties.cmd == -1) {
              str = "Nothing";
          } else {
              str = MIDIEvent.commands_short[this.properties.cmd] || "Unknown";
          }
  
          if (
              this.properties.min_value != -1 &&
              this.properties.max_value != -1
          ) {
              str +=
                  " " +
                  (this.properties.min_value == this.properties.max_value
                      ? this.properties.max_value
                      : this.properties.min_value +
                        ".." +
                        this.properties.max_value);
          }
  
          return "Filter: " + str;
      };
  
      LGMIDIFilter.prototype.onPropertyChanged = function(name, value) {
          if (name == "cmd") {
              var num = Number(value);
              if (isNaN(num)) {
                  num = MIDIEvent.commands[value] || 0;
              }
              this.properties.cmd = num;
          }
      };
  
      LGMIDIFilter.prototype.onAction = function(event, midi_event) {
          if (!midi_event || midi_event.constructor !== MIDIEvent) {
              return;
          }
  
          if (this._learning) {
              this._learning = false;
              this.boxcolor = "#AAA";
              this.properties.channel = midi_event.channel;
              this.properties.cmd = midi_event.cmd;
              this.properties.min_value = this.properties.max_value =
                  midi_event.data[1];
          } else {
              if (
                  this.properties.channel != -1 &&
                  midi_event.channel != this.properties.channel
              ) {
                  return;
              }
              if (
                  this.properties.cmd != -1 &&
                  midi_event.cmd != this.properties.cmd
              ) {
                  return;
              }
              if (
                  this.properties.min_value != -1 &&
                  midi_event.data[1] < this.properties.min_value
              ) {
                  return;
              }
              if (
                  this.properties.max_value != -1 &&
                  midi_event.data[1] > this.properties.max_value
              ) {
                  return;
              }
          }
  
          this.trigger("on_midi", midi_event);
      };
  
      LiteGraph.registerNodeType("midi/filter", LGMIDIFilter);
  
      function LGMIDIEvent() {
          this.properties = {
              channel: 0,
              cmd: 144, //0x90
              value1: 1,
              value2: 1
          };
  
          this.addInput("send", LiteGraph.EVENT);
          this.addInput("assign", LiteGraph.EVENT);
          this.addOutput("on_midi", LiteGraph.EVENT);
  
          this.midi_event = new MIDIEvent();
          this.gate = false;
      }
  
      LGMIDIEvent.title = "MIDIEvent";
      LGMIDIEvent.desc = "Create a MIDI Event";
      LGMIDIEvent.color = MIDI_COLOR;
  
      LGMIDIEvent.prototype.onAction = function(event, midi_event) {
          if (event == "assign") {
              this.properties.channel = midi_event.channel;
              this.properties.cmd = midi_event.cmd;
              this.properties.value1 = midi_event.data[1];
              this.properties.value2 = midi_event.data[2];
              if (midi_event.cmd == MIDIEvent.NOTEON) {
                  this.gate = true;
              } else if (midi_event.cmd == MIDIEvent.NOTEOFF) {
                  this.gate = false;
              }
              return;
          }
  
          //send
          var midi_event = this.midi_event;
          midi_event.channel = this.properties.channel;
          if (this.properties.cmd && this.properties.cmd.constructor === String) {
              midi_event.setCommandFromString(this.properties.cmd);
          } else {
              midi_event.cmd = this.properties.cmd;
          }
          midi_event.data[0] = midi_event.cmd | midi_event.channel;
          midi_event.data[1] = Number(this.properties.value1);
          midi_event.data[2] = Number(this.properties.value2);
  
          this.trigger("on_midi", midi_event);
      };
  
      LGMIDIEvent.prototype.onExecute = function() {
          var props = this.properties;
  
          if (this.inputs) {
              for (var i = 0; i < this.inputs.length; ++i) {
                  var input = this.inputs[i];
                  if (input.link == -1) {
                      continue;
                  }
                  switch (input.name) {
                      case "note":
                          var v = this.getInputData(i);
                          if (v != null) {
                              if (v.constructor === String) {
                                  v = MIDIEvent.NoteStringToPitch(v);
                              }
                              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 = clamp(v|0,0,127);
                          }
                          break;
                      case "value2":
                          var v = this.getInputData(i);
                          if (v != null) {
                              this.properties.value2 = clamp(v|0,0,127);
                          }
                          break;
                  }
              }
          }
  
          if (this.outputs) {
              for (var i = 0; i < this.outputs.length; ++i) {
                  var output = this.outputs[i];
                  var v = null;
                  switch (output.name) {
                      case "midi":
                          v = new MIDIEvent();
                          v.setup([props.cmd, props.value1, props.value2]);
                          v.channel = props.channel;
                          break;
                      case "command":
                          v = props.cmd;
                          break;
                      case "cc":
                          v = props.value1;
                          break;
                      case "cc_value":
                          v = props.value2;
                          break;
                      case "note":
                          v =
                              props.cmd == MIDIEvent.NOTEON ||
                              props.cmd == MIDIEvent.NOTEOFF
                                  ? props.value1
                                  : null;
                          break;
                      case "velocity":
                          v = props.cmd == MIDIEvent.NOTEON ? props.value2 : null;
                          break;
                      case "pitch":
                          v =
                              props.cmd == MIDIEvent.NOTEON
                                  ? MIDIEvent.computePitch(props.value1)
                                  : null;
                          break;
                      case "pitchbend":
                          v =
                              props.cmd == MIDIEvent.PITCHBEND
                                  ? MIDIEvent.computePitchBend(
                                        props.value1,
                                        props.value2
                                    )
                                  : null;
                          break;
                      case "gate":
                          v = this.gate;
                          break;
                      default:
                          continue;
                  }
                  if (v !== null) {
                      this.setOutputData(i, v);
                  }
              }
          }
      };
  
      LGMIDIEvent.prototype.onPropertyChanged = function(name, value) {
          if (name == "cmd") {
              this.properties.cmd = MIDIEvent.computeCommandFromString(value);
          }
      };
  
      LGMIDIEvent.prototype.onGetInputs = function() {
          return [["cmd", "number"],["note", "number"],["value1", "number"],["value2", "number"]];
      };
  
      LGMIDIEvent.prototype.onGetOutputs = function() {
          return [
              ["midi", "midi"],
              ["on_midi", LiteGraph.EVENT],
              ["command", "number"],
              ["note", "number"],
              ["velocity", "number"],
              ["cc", "number"],
              ["cc_value", "number"],
              ["pitch", "number"],
              ["gate", "bool"],
              ["pitchbend", "number"]
          ];
      };
  
      LiteGraph.registerNodeType("midi/event", LGMIDIEvent);
  
      function LGMIDICC() {
          this.properties = {
              //		channel: 0,
              cc: 1,
              value: 0
          };
  
          this.addOutput("value", "number");
      }
  
      LGMIDICC.title = "MIDICC";
      LGMIDICC.desc = "gets a Controller Change";
      LGMIDICC.color = MIDI_COLOR;
  
      LGMIDICC.prototype.onExecute = function() {
          var props = this.properties;
          if (MIDIInterface.input) {
              this.properties.value =
                  MIDIInterface.input.state.cc[this.properties.cc];
          }
          this.setOutputData(0, this.properties.value);
      };
  
      LiteGraph.registerNodeType("midi/cc", LGMIDICC);
  
      function LGMIDIGenerator() {
          this.addInput("generate", LiteGraph.ACTION);
          this.addInput("scale", "string");
          this.addInput("octave", "number");
          this.addOutput("note", LiteGraph.EVENT);
          this.properties = {
              notes: "A,A#,B,C,C#,D,D#,E,F,F#,G,G#",
              octave: 2,
              duration: 0.5,
              mode: "sequence"
          };
  
          this.notes_pitches = LGMIDIGenerator.processScale(
              this.properties.notes
          );
          this.sequence_index = 0;
      }
  
      LGMIDIGenerator.title = "MIDI Generator";
      LGMIDIGenerator.desc = "Generates a random MIDI note";
      LGMIDIGenerator.color = MIDI_COLOR;
  
      LGMIDIGenerator.processScale = function(scale) {
          var notes = scale.split(",");
          for (var i = 0; i < notes.length; ++i) {
              var n = notes[i];
              if ((n.length == 2 && n[1] != "#") || n.length > 2) {
                  notes[i] = -LiteGraph.MIDIEvent.NoteStringToPitch(n);
              } else {
                  notes[i] = MIDIEvent.note_to_index[n] || 0;
              }
          }
          return notes;
      };
  
      LGMIDIGenerator.prototype.onPropertyChanged = function(name, value) {
          if (name == "notes") {
              this.notes_pitches = LGMIDIGenerator.processScale(value);
          }
      };
  
      LGMIDIGenerator.prototype.onExecute = function() {
          var octave = this.getInputData(2);
          if (octave != null) {
              this.properties.octave = octave;
          }
  
          var scale = this.getInputData(1);
          if (scale) {
              this.notes_pitches = LGMIDIGenerator.processScale(scale);
          }
      };
  
      LGMIDIGenerator.prototype.onAction = function(event, midi_event) {
          //var range = this.properties.max - this.properties.min;
          //var pitch = this.properties.min + ((Math.random() * range)|0);
          var pitch = 0;
          var range = this.notes_pitches.length;
          var index = 0;
  
          if (this.properties.mode == "sequence") {
              index = this.sequence_index = (this.sequence_index + 1) % range;
          } else if (this.properties.mode == "random") {
              index = Math.floor(Math.random() * range);
          }
  
          var note = this.notes_pitches[index];
          if (note >= 0) {
              pitch = note + (this.properties.octave - 1) * 12 + 33;
          } else {
              pitch = -note;
          }
  
          var midi_event = new MIDIEvent();
          midi_event.setup([MIDIEvent.NOTEON, pitch, 10]);
          var duration = this.properties.duration || 1;
          this.trigger("note", midi_event);
  
          //noteoff
          setTimeout(
              function() {
                  var midi_event = new MIDIEvent();
                  midi_event.setup([MIDIEvent.NOTEOFF, pitch, 0]);
                  this.trigger("note", midi_event);
              }.bind(this),
              duration * 1000
          );
      };
  
      LiteGraph.registerNodeType("midi/generator", LGMIDIGenerator);
  
      function LGMIDITranspose() {
          this.properties = {
              amount: 0
          };
          this.addInput("in", LiteGraph.ACTION);
          this.addInput("amount", "number");
          this.addOutput("out", LiteGraph.EVENT);
  
          this.midi_event = new MIDIEvent();
      }
  
      LGMIDITranspose.title = "MIDI Transpose";
      LGMIDITranspose.desc = "Transpose a MIDI note";
      LGMIDITranspose.color = MIDI_COLOR;
  
      LGMIDITranspose.prototype.onAction = function(event, midi_event) {
          if (!midi_event || midi_event.constructor !== MIDIEvent) {
              return;
          }
  
          if (
              midi_event.data[0] == MIDIEvent.NOTEON ||
              midi_event.data[0] == MIDIEvent.NOTEOFF
          ) {
              this.midi_event = new MIDIEvent();
              this.midi_event.setup(midi_event.data);
              this.midi_event.data[1] = Math.round(
                  this.midi_event.data[1] + this.properties.amount
              );
              this.trigger("out", this.midi_event);
          } else {
              this.trigger("out", midi_event);
          }
      };
  
      LGMIDITranspose.prototype.onExecute = function() {
          var amount = this.getInputData(1);
          if (amount != null) {
              this.properties.amount = amount;
          }
      };
  
      LiteGraph.registerNodeType("midi/transpose", LGMIDITranspose);
  
      function LGMIDIQuantize() {
          this.properties = {
              scale: "A,A#,B,C,C#,D,D#,E,F,F#,G,G#"
          };
          this.addInput("note", LiteGraph.ACTION);
          this.addInput("scale", "string");
          this.addOutput("out", LiteGraph.EVENT);
  
          this.valid_notes = new Array(12);
          this.offset_notes = new Array(12);
          this.processScale(this.properties.scale);
      }
  
      LGMIDIQuantize.title = "MIDI Quantize Pitch";
      LGMIDIQuantize.desc = "Transpose a MIDI note tp fit an scale";
      LGMIDIQuantize.color = MIDI_COLOR;
  
      LGMIDIQuantize.prototype.onPropertyChanged = function(name, value) {
          if (name == "scale") {
              this.processScale(value);
          }
      };
  
      LGMIDIQuantize.prototype.processScale = function(scale) {
          this._current_scale = scale;
          this.notes_pitches = LGMIDIGenerator.processScale(scale);
          for (var i = 0; i < 12; ++i) {
              this.valid_notes[i] = this.notes_pitches.indexOf(i) != -1;
          }
          for (var i = 0; i < 12; ++i) {
              if (this.valid_notes[i]) {
                  this.offset_notes[i] = 0;
                  continue;
              }
              for (var j = 1; j < 12; ++j) {
                  if (this.valid_notes[(i - j) % 12]) {
                      this.offset_notes[i] = -j;
                      break;
                  }
                  if (this.valid_notes[(i + j) % 12]) {
                      this.offset_notes[i] = j;
                      break;
                  }
              }
          }
      };
  
      LGMIDIQuantize.prototype.onAction = function(event, midi_event) {
          if (!midi_event || midi_event.constructor !== MIDIEvent) {
              return;
          }
  
          if (
              midi_event.data[0] == MIDIEvent.NOTEON ||
              midi_event.data[0] == MIDIEvent.NOTEOFF
          ) {
              this.midi_event = new MIDIEvent();
              this.midi_event.setup(midi_event.data);
              var note = midi_event.note;
              var index = MIDIEvent.note_to_index[note];
              var offset = this.offset_notes[index];
              this.midi_event.data[1] += offset;
              this.trigger("out", this.midi_event);
          } else {
              this.trigger("out", midi_event);
          }
      };
  
      LGMIDIQuantize.prototype.onExecute = function() {
          var scale = this.getInputData(1);
          if (scale != null && scale != this._current_scale) {
              this.processScale(scale);
          }
      };
  
      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,
              duration: 1
          };
          this.addInput("note", LiteGraph.ACTION);
          this.addInput("volume", "number");
          this.addInput("duration", "number");
          this.addOutput("note", LiteGraph.EVENT);
  
          if (typeof AudioSynth == "undefined") {
              console.error(
                  "Audiosynth.js not included, LGMidiPlay requires that library"
              );
              this.boxcolor = "red";
          } else {
              var Synth = (this.synth = new AudioSynth());
              this.instrument = Synth.createInstrument("piano");
          }
      }
  
      LGMIDIPlay.title = "MIDI Play";
      LGMIDIPlay.desc = "Plays a MIDI note";
      LGMIDIPlay.color = MIDI_COLOR;
  
      LGMIDIPlay.prototype.onAction = function(event, midi_event) {
          if (!midi_event || midi_event.constructor !== MIDIEvent) {
              return;
          }
  
          if (this.instrument && midi_event.data[0] == MIDIEvent.NOTEON) {
              var note = midi_event.note; //C#
              if (!note || note == "undefined" || note.constructor !== String) {
                  return;
              }
              this.instrument.play(
                  note,
                  midi_event.octave,
                  this.properties.duration,
                  this.properties.volume
              );
          }
          this.trigger("note", midi_event);
      };
  
      LGMIDIPlay.prototype.onExecute = function() {
          var volume = this.getInputData(1);
          if (volume != null) {
              this.properties.volume = volume;
          }
  
          var duration = this.getInputData(2);
          if (duration != null) {
              this.properties.duration = duration;
          }
      };
  
      LiteGraph.registerNodeType("midi/play", LGMIDIPlay);
  
      function LGMIDIKeys() {
          this.properties = {
              num_octaves: 2,
              start_octave: 2
          };
          this.addInput("note", LiteGraph.ACTION);
          this.addInput("reset", LiteGraph.ACTION);
          this.addOutput("note", LiteGraph.EVENT);
          this.size = [400, 100];
          this.keys = [];
          this._last_key = -1;
      }
  
      LGMIDIKeys.title = "MIDI Keys";
      LGMIDIKeys.desc = "Keyboard to play notes";
      LGMIDIKeys.color = MIDI_COLOR;
  
      LGMIDIKeys.keys = [
          { x: 0, w: 1, h: 1, t: 0 },
          { x: 0.75, w: 0.5, h: 0.6, t: 1 },
          { x: 1, w: 1, h: 1, t: 0 },
          { x: 1.75, w: 0.5, h: 0.6, t: 1 },
          { x: 2, w: 1, h: 1, t: 0 },
          { x: 2.75, w: 0.5, h: 0.6, t: 1 },
          { x: 3, w: 1, h: 1, t: 0 },
          { x: 4, w: 1, h: 1, t: 0 },
          { x: 4.75, w: 0.5, h: 0.6, t: 1 },
          { x: 5, w: 1, h: 1, t: 0 },
          { x: 5.75, w: 0.5, h: 0.6, t: 1 },
          { x: 6, w: 1, h: 1, t: 0 }
      ];
  
      LGMIDIKeys.prototype.onDrawForeground = function(ctx) {
          if (this.flags.collapsed) {
              return;
          }
  
          var num_keys = this.properties.num_octaves * 12;
          this.keys.length = num_keys;
          var key_width = this.size[0] / (this.properties.num_octaves * 7);
          var key_height = this.size[1];
  
          ctx.globalAlpha = 1;
  
          for (
              var k = 0;
              k < 2;
              k++ //draw first whites (0) then blacks (1)
          ) {
              for (var i = 0; i < num_keys; ++i) {
                  var key_info = LGMIDIKeys.keys[i % 12];
                  if (key_info.t != k) {
                      continue;
                  }
                  var octave = Math.floor(i / 12);
                  var x = octave * 7 * key_width + key_info.x * key_width;
                  if (k == 0) {
                      ctx.fillStyle = this.keys[i] ? "#CCC" : "white";
                  } else {
                      ctx.fillStyle = this.keys[i] ? "#333" : "black";
                  }
                  ctx.fillRect(
                      x + 1,
                      0,
                      key_width * key_info.w - 2,
                      key_height * key_info.h
                  );
              }
          }
      };
  
      LGMIDIKeys.prototype.getKeyIndex = function(pos) {
          var num_keys = this.properties.num_octaves * 12;
          var key_width = this.size[0] / (this.properties.num_octaves * 7);
          var key_height = this.size[1];
  
          for (
              var k = 1;
              k >= 0;
              k-- //test blacks first (1) then whites (0)
          ) {
              for (var i = 0; i < this.keys.length; ++i) {
                  var key_info = LGMIDIKeys.keys[i % 12];
                  if (key_info.t != k) {
                      continue;
                  }
                  var octave = Math.floor(i / 12);
                  var x = octave * 7 * key_width + key_info.x * key_width;
                  var w = key_width * key_info.w;
                  var h = key_height * key_info.h;
                  if (pos[0] < x || pos[0] > x + w || pos[1] > h) {
                      continue;
                  }
                  return i;
              }
          }
          return -1;
      };
  
      LGMIDIKeys.prototype.onAction = function(event, params) {
          if (event == "reset") {
              for (var i = 0; i < this.keys.length; ++i) {
                  this.keys[i] = false;
              }
              return;
          }
  
          if (!params || params.constructor !== MIDIEvent) {
              return;
          }
          var midi_event = params;
          var start_note = (this.properties.start_octave - 1) * 12 + 29;
          var index = midi_event.data[1] - start_note;
          if (index >= 0 && index < this.keys.length) {
              if (midi_event.data[0] == MIDIEvent.NOTEON) {
                  this.keys[index] = true;
              } else if (midi_event.data[0] == MIDIEvent.NOTEOFF) {
                  this.keys[index] = false;
              }
          }
  
          this.trigger("note", midi_event);
      };
  
      LGMIDIKeys.prototype.onMouseDown = function(e, pos) {
          if (pos[1] < 0) {
              return;
          }
          var index = this.getKeyIndex(pos);
          this.keys[index] = true;
          this._last_key = index;
          var pitch = (this.properties.start_octave - 1) * 12 + 29 + index;
          var midi_event = new MIDIEvent();
          midi_event.setup([MIDIEvent.NOTEON, pitch, 100]);
          this.trigger("note", midi_event);
          return true;
      };
  
      LGMIDIKeys.prototype.onMouseMove = function(e, pos) {
          if (pos[1] < 0 || this._last_key == -1) {
              return;
          }
          this.setDirtyCanvas(true);
          var index = this.getKeyIndex(pos);
          if (this._last_key == index) {
              return true;
          }
          this.keys[this._last_key] = false;
          var pitch =
              (this.properties.start_octave - 1) * 12 + 29 + this._last_key;
          var midi_event = new MIDIEvent();
          midi_event.setup([MIDIEvent.NOTEOFF, pitch, 100]);
          this.trigger("note", midi_event);
  
          this.keys[index] = true;
          var pitch = (this.properties.start_octave - 1) * 12 + 29 + index;
          var midi_event = new MIDIEvent();
          midi_event.setup([MIDIEvent.NOTEON, pitch, 100]);
          this.trigger("note", midi_event);
  
          this._last_key = index;
          return true;
      };
  
      LGMIDIKeys.prototype.onMouseUp = function(e, pos) {
          if (pos[1] < 0) {
              return;
          }
          var index = this.getKeyIndex(pos);
          this.keys[index] = false;
          this._last_key = -1;
          var pitch = (this.properties.start_octave - 1) * 12 + 29 + index;
          var midi_event = new MIDIEvent();
          midi_event.setup([MIDIEvent.NOTEOFF, pitch, 100]);
          this.trigger("note", midi_event);
          return true;
      };
  
      LiteGraph.registerNodeType("midi/keys", LGMIDIKeys);
  
      function now() {
          return window.performance.now();
      }
  })(this);
  