mpp-frontend-ts/src/MIDI/MIDIHandler.ts

281 lines
9.8 KiB
TypeScript

import { Knob, mixin } from "../Misc/util";
import { Notification } from "../Interface/Notification";
import { Keyboard } from "../Pianoctor/Keyboard";
import { MIDI } from "./MIDI";
interface MIDIPort {
id: string;
manufacturer ? : string;
name ? : string;
type: string;
version ? : string;
state: string;
connection: string;
onstatechange: Function;
};
/*interface MIDIConnectionEvent {
port: MIDIPort;
};*/
interface MIDIHandlerNode {
keyboard: Keyboard;
midiCls: MIDI;
gMidiVolumeTest: "" | RegExpMatchArray | null;
}
export class MIDIHandler {
midi: WebMidi.MIDIAccess;
connectionsNotification: Notification;
devices_json: string;
midiHandlerNode: MIDIHandlerNode;
constructor(midi: WebMidi.MIDIAccess, midiHandlerNode: MIDIHandlerNode) {
this.midi = midi;
this.midiHandlerNode = midiHandlerNode;
this.devices_json;
this.midiHandlerNode = midiHandlerNode;
}
midimessagehandler(evt: WebMidi.MIDIMessageEvent) {
if (!(evt.target as any).enabled) return;
//console.log(evt);
let channel = evt.data[0] & 0xf;
let cmd = evt.data[0] >> 4;
let note_number = evt.data[1];
let vel = evt.data[2];
//console.log(channel, cmd, note_number, vel);
if (cmd === 8 || (cmd === 9 && vel === 0)) {
// NOTE_OFF
this.midiHandlerNode.keyboard.release(this.midiHandlerNode.midiCls.MIDI_KEY_NAMES[note_number - 9 + this.midiHandlerNode.midiCls.MIDI_TRANSPOSE]);
} else if (cmd === 9) {
// NOTE_ON
if ((evt.target as any).volume !== undefined)
vel *= (evt.target as any).volume;
this.midiHandlerNode.keyboard.press(this.midiHandlerNode.midiCls.MIDI_KEY_NAMES[note_number - 9 + this.midiHandlerNode.midiCls.MIDI_TRANSPOSE], vel / 100);
} else if (cmd === 11) {
// CONTROL_CHANGE
if (!this.midiHandlerNode.keyboard.gAutoSustain) {
if (note_number === 64) {
if (vel > 0) {
this.midiHandlerNode.keyboard.pressSustain();
} else {
this.midiHandlerNode.keyboard.releaseSustain();
}
}
}
}
}
deviceInfo(dev: WebMidi.MIDIInput | WebMidi.MIDIOutput) {
return {
type: dev.type,
//id: dev.id,
manufacturer: dev.manufacturer,
name: dev.name,
version: dev.version,
//connection: dev.connection,
//state: dev.state,
enabled: (dev as any).enabled as boolean,
volume: (dev as any).volume as number
};
}
updateDevices() {
let list = [];
if (this.midi.inputs.size > 0) {
let inputs = this.midi.inputs.values();
for (let input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) {
let input = input_it.value;
list.push(this.deviceInfo(input));
}
}
if (this.midi.outputs.size > 0) {
let outputs = this.midi.outputs.values();
for (let output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) {
let output = output_it.value;
list.push(this.deviceInfo(output));
}
}
let new_json = JSON.stringify(list);
if (new_json !== this.devices_json) {
this.devices_json = new_json;
this.midiHandlerNode.midiCls.sendDevices();
}
}
plug() {
if (this.midi.inputs.size > 0) {
let inputs = this.midi.inputs.values();
for (let input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) {
let input = input_it.value;
//input.removeEventListener("midimessage", midimessagehandler);
//input.addEventListener("midimessage", midimessagehandler);
input.onmidimessage = this.midimessagehandler.bind(this);
if ((input as any).enabled !== false) {
(input as any).enabled = true;
}
if (typeof (input as any).volume === "undefined") {
(input as any).volume = 1.0;
}
console.log("input", input);
}
}
if (this.midi.outputs.size > 0) {
let outputs = this.midi.outputs.values();
for (let output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) {
let output = output_it.value;
//output.enabled = false; // edit: don't touch
if (typeof (output as any).volume === "undefined") {
(output as any).volume = 1.0;
}
console.log("output", output);
}
}
this.showConnections(false);
this.updateDevices();
}
gMidiOutTest(note_name: string, vel: number, delay_ms: number) {
let note_number = this.midiHandlerNode.midiCls.MIDI_KEY_NAMES.indexOf(note_name);
if (note_number == -1) return;
note_number = note_number + 9 - this.midiHandlerNode.midiCls.MIDI_TRANSPOSE;
let outputs = this.midi.outputs.values();
for (let output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) {
let output = output_it.value;
if ((output as any).enabled) {
let v = vel;
if ((output as any).volume !== undefined)
v *= (output as any).volume;
output.send([0x90, note_number, v], window.performance.now() + delay_ms);
}
}
}
showConnections(sticky: boolean) {
//if(document.getElementById("Notification-MIDI-Connections"))
//sticky = 1; // todo: instead,
let inputs_ul = document.createElement("ul")!;
if (this.midi.inputs.size > 0) {
let inputs = this.midi.inputs.values();
for (let input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) {
let input = input_it.value;
let li = document.createElement("li")!;
(li as any).connectionId = input.id;
li.classList.add("connection");
if ((input as any).enabled) li.classList.add("enabled");
li.textContent = input.name!;
li.addEventListener("click", evt => {
let inputs = this.midi.inputs.values();
for (let input_it = inputs.next(); input_it && !input_it.done; input_it = inputs.next()) {
let input = input_it.value;
if (input.id === (evt.target as any).connectionId) {
(input as any).enabled = !(input as any).enabled;
(evt.target as HTMLElement).classList.toggle("enabled");
console.log("click", input);
this.updateDevices();
return;
}
}
});
if (this.midiHandlerNode.gMidiVolumeTest) {
let knobCanvas = document.createElement("canvas")!;
knobCanvas.width = 16 * window.devicePixelRatio;
knobCanvas.height = 16 * window.devicePixelRatio;
knobCanvas.className = "knob";
li.appendChild(knobCanvas);
let knob = new Knob(knobCanvas, 0, 2, 0.01, (input as any).volume, "volume");
knob.canvas.style.width = "16px";
knob.canvas.style.height = "16px";
knob.canvas.style.float = "right";
knob.on("change", function(k: Knob) {
(input as any).volume = k.value;
});
knob.emit("change", knob);
}
inputs_ul.appendChild(li);
}
} else {
inputs_ul.textContent = "(none)";
}
let outputs_ul = document.createElement("ul");
if (this.midi.outputs.size > 0) {
let outputs = this.midi.outputs.values();
for (let output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) {
let output = output_it.value;
let li = document.createElement("li")!;
(li as any).connectionId = output.id;
li.classList.add("connection");
if ((output as any).enabled) li.classList.add("enabled");
li.textContent = output.name!;
li.addEventListener("click", evt => {
let outputs = this.midi.outputs.values();
for (let output_it = outputs.next(); output_it && !output_it.done; output_it = outputs.next()) {
let output = output_it.value;
if (output.id === (evt.target as any).connectionId) {
(output as any).enabled = !(output as any).enabled;
(evt.target as HTMLElement).classList.toggle("enabled");
console.log("click", output);
this.updateDevices();
return;
}
}
});
if (this.midiHandlerNode.gMidiVolumeTest) {
let knobCanvas = document.createElement("canvas")!;
mixin(knobCanvas, {
width: 16 * window.devicePixelRatio,
height: 16 * window.devicePixelRatio,
className: "knob"
});
li.appendChild(knobCanvas);
let knob = new Knob(knobCanvas, 0, 2, 0.01, (output as any).volume, "volume");
knob.canvas.style.width = "16px";
knob.canvas.style.height = "16px";
knob.canvas.style.float = "right";
knob.on("change", function(k: Knob) {
(output as any).volume = k.value;
});
knob.emit("change", knob);
}
outputs_ul.appendChild(li);
}
} else {
outputs_ul.textContent = "(none)";
}
let div = document.createElement("div");
let h1 = document.createElement("h1");
h1.textContent = "Inputs";
div.appendChild(h1);
div.appendChild(inputs_ul);
h1 = document.createElement("h1");
h1.textContent = "Outputs";
div.appendChild(h1);
div.appendChild(outputs_ul);
this.connectionsNotification = new Notification({
"id": "MIDI-Connections",
"title": "MIDI Connections",
"duration": parseFloat(sticky ? "-1" : "4500"),
"html": div,
"target": "#midi-btn"
});
}
init() {
this.midi.addEventListener("statechange", evt => {
//if(evt instanceof MIDIConnectionEvent) { // TODO this isn't fully supported
this.plug();
//}
});
this.plug();
document.getElementById("midi-btn")!.addEventListener("click", (evt => {
if (!document.getElementById("Notification-MIDI-Connections"))
this.showConnections(true);
else {
this.connectionsNotification.close();
}
}));
}
}