Files
ComfyUI_frontend/src/nodes/midi.js
2020-06-11 17:07:47 +02:00

1587 lines
48 KiB
JavaScript

(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); //asume 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 = 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;
}
}
}
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);