/**
* This file is part of HARMONICARIUM, a web app which allows users to play
* the Harmonic Series dynamically by changing its fundamental tone in real-time.
* It is available in its latest version from:
* https://github.com/IndustrieCreative/Harmonicarium
*
* @license
* Copyright (C) 2017-2020 by Walter Mantovani (http://armonici.it).
* Written by Walter Mantovani.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/* globals HUM */
"use strict";
/**
* The MidiIn class.<br>
* Manage MIDI input messages.
*/
HUM.midi.MidiIn = class {
/**
* @param {HUM.DHC} dhc - The DHC instance to which it belongs
* @param {HUM.midi.MidiHub} midi - The MidiHub instance to which it belongs
*/
constructor(dhc, midi) {
/**
* The DHC instance
*
* @member {HUM.DHC}
*/
this.dhc = dhc;
/**
* The MidiHub instance
*
* @member {HUM.midi.MidiHub}
*/
this.midi = midi;
/**
* Output queue buffer for MIDI messages that must pass through and go out
* (used as logger at the moment)
*
* @todo - Pass through for most of the MIDI messages
*
* @member {Array.<OtherMidiMsg>}
*/
// @old icMidiPassThrough
this.midiPassThrough = [];
/**
* UI HTML elements
*
* @member {Object}
*
* @property {Object.<string, HTMLElement>} fn - Functional UI elements
* @property {Object.<string, HTMLElement>} in - Input UI elements
* @property {Object.<string, HTMLElement>} out - Output UI elements
*/
this.uiElements = {
fn: {
receiveModeTsnapTolerance: document.getElementById("HTMLf_midiReceiveModeTsnapTolerance"+dhc.id),
receiveModeTsnapChan: document.getElementById("HTMLf_midiReceiveModeTsnapChan"+dhc.id),
receiveModeTsnapDivider: document.getElementById("HTMLf_midiReceiveModeTsnapDivider"+dhc.id),
},
in: {
receiveMode: document.getElementById("HTMLi_midiReceiveMode"+dhc.id),
receiveModeTsnapTolerance: document.getElementById("HTMLi_midiReceiveModeTsnapTolerance"+dhc.id),
receiveModeTsnapDivider: document.getElementById("HTMLi_midiReceiveModeTsnapDivider"+dhc.id),
receiveModeTsnapChanFT: document.getElementById("HTMLi_midiReceiveModeTsnapChanFT"+dhc.id),
receiveModeTsnapChanHT: document.getElementById("HTMLi_midiReceiveModeTsnapChanHT"+dhc.id),
receiveModeTsnapChanDivider: document.getElementById("HTMLi_midiReceiveModeTsnapChanDivider"+dhc.id),
},
out: {
monitor0_note: document.getElementById(`HTMLo_midiMonitor0_note${dhc.id}`),
monitor0_velocity: document.getElementById(`HTMLo_midiMonitor0_velocity${dhc.id}`),
monitor0_channel: document.getElementById(`HTMLo_midiMonitor0_channel${dhc.id}`),
monitor0_port: document.getElementById(`HTMLo_midiMonitor0_port${dhc.id}`),
monitor1_note: document.getElementById(`HTMLo_midiMonitor1_note${dhc.id}`),
monitor1_velocity: document.getElementById(`HTMLo_midiMonitor1_velocity${dhc.id}`),
monitor1_channel: document.getElementById(`HTMLo_midiMonitor1_channel${dhc.id}`),
monitor1_port: document.getElementById(`HTMLo_midiMonitor1_port${dhc.id}`),
},
};
/**
* Register of the MIDI Note-On inputs by channel.
* Actually it's common to all input ports.
*
* @member {Object.<number, Object.<midinnum, MidiInNoteOn>>}
*
* @example
* // An example of structure of .notes_on[0]
* 0: { // MIDI Channel
* 39: { // External MIDI note number (on the instrument)
* keymapped: 56, // Internal MIDI note number (on the keymap)
* midievent: {MIDIMessageEvent} // The original `MIDIMessageEvent`
* }
* }
*
* @todo Dynamic, one for each imput port.
* Now two in ports can conflicts if use the same channels.
*/
this.notes_on = {
/**
* MIDI note number from external input controller
* @typedef {Object} MidiInNoteOn
*
* @property {midinnum} keymapped - The MIDI Note Number on the keymap (internal)
* @property {MIDIMessageEvent} midievent - The original MIDI event containing the note-on message
*/
0: {},
1: {},
2: {},
3: {},
4: {},
5: {},
6: {},
7: {},
8: {},
9: {},
10: {},
11: {},
12: {},
13: {},
14: {},
15: {}
};
this._initUI();
// Tell to the DHC that a new app is using it
this.dhc.registerApp(this, 'updatesFromDHC', 0);
// =======================
} // end class Constructor
// ===========================
/**
* Manage and route an incoming message
* @param {HUM.DHCmsg} msg - The incoming message
*/
updatesFromDHC(msg) {
if (msg.cmd === 'panic') {
this.allNotesOff();
}
if (msg.cmd === 'update') {
if (msg.type === 'ft') {
// this.tsnapUpdateFT();
return;
} else if (msg.type === 'ht') {
this.tsnapUpdateHT();
} else if (msg.type === 'ctrlmap') {
return;
}
}
}
/**
* Clear the {@link HUM.midi.MidiIn#notes_on} register
*/
allNotesOff() {
for (var ch = 0; ch < 16; ch++) {
this.notes_on[ch] = {};
}
}
/*==============================================================================*
* MAIN MIDI MESSAGE HANDLER
*==============================================================================*/
/**
* Handle the incoming MIDI messages
*
* @see {@link https://webaudio.github.io/web-midi-api/#MIDIMessageEvent|Web MIDI API specs}
*
* @param {MIDIMessageEvent} midievent - The MIDI message from {@link HUM.midi.MidiPorts#midiAccess}
* @param {Uint8Array} midievent.data - The data array (each entry is a 8bit integer)
* @param {number} midievent.timeStamp - The Time-stamp of the message in milliseconds (floating-point number)
* @param {boolean=} deSnapped=false - If the Note-Off comes from de-snapping action, by the T-Snap receive mode.<br>
* `false`: (default) The note will be turned-off deleted from the {@link HUM.midi.MidiIn#notes_on} register
* `true`: (default) The note will be turned-off but still remains in the {@link HUM.midi.MidiIn#notes_on} register
*/
// @old icMidiMessageReceived
midiMessageReceived(midievent, deSnapped=false) {
// Divide the informations contained in the first byte (Status byte)
// 4 bits bitwise shift to the right to get the remaining 4 bits representing
// the command (the type of MIDI message)
let cmd = midievent.data[0] >> 4;
// Set to zero the first 4 bits from left and get the channel
let channel = midievent.data[0] & 0xf;
// Set the timestamp
let timestamp = midievent.timeStamp;
// Handle Piper feature (fake midievent)
// Default: is not a Piper midievent
let piper = false;
let hancock = false;
let tSnapped = false;
// Special 4th & 5th bytes in the 'midievent' containing 'hancock' and 'piper' tags
if (midievent.data[3] === "hancock") {
// If it's a Hancock midievent
hancock = true;
}
if (midievent.data[4] === "piper") {
// If it's a Piper's fake midievent
piper = true;
}
// this.logMidiEvent(midievent);
// Check 'cmd' (the first 4 bits of the 1st byte of the message)
// If >= 0x80 it's a Status byte
// Filter the Active Sensing messages (254 = 0xFE = 11111110)
if (cmd > 7 && midievent.data[0] !== 254) {
// @todo - implement RUNNING STATUS (status byte not repeated on every message)
// If the message has at least 3 bytes
// if (midievent.data.length > 2) {
// // Read the velocity from the 3rd byte
// velocity = midievent.data[2];
// }
// @todo - implement TRANSMISSION ERRORS HANDLING
if (cmd === 8 || (cmd === 9)) {
let ctrlNum = false;
let monitorNotes = midievent.data[1];
// MIDI NOTE NUMBER PREPARE (in case of Tsnap)
if (this.dhc.settings.controller.receive_mode === 'keymap') {
ctrlNum = midievent.data[1];
} else {
// Find the controller (internal and keymapped) note number from the incoming midi message
// if the midi receiving mode is Tone snap
if (this.dhc.settings.controller.receive_mode === 'tsnap-channel') {
ctrlNum = this.tsnapChannel(midievent.data[1], channel, hancock);
} else if (this.dhc.settings.controller.receive_mode === 'tsnap-divider') {
ctrlNum = this.tsnapDivider(midievent.data[1], channel, hancock);
}
if (hancock === false) {
// Mark that's a Hancock internal generated message
// (it is currently fixed on the keymap receiving mode)
tSnapped = ctrlNum !== false ? true : false;
// MIDI-IN MONITOR translation
let monintorNoteTo = ctrlNum === false ? 'ND' : ctrlNum;
monitorNotes = midievent.data[1] +'>'+ monintorNoteTo;
}
}
// NOTE OFF (MIDI note-on with velocity=0 is the same as note-off)
if (cmd === 8 || ((cmd === 9) && (midievent.data[2] === 0))) {
// if (ctrlNum !== false) {
if (hancock === true) {
ctrlNum = midievent.data[1];
} else {
// If the ctrlNum note is in the 'notes_on' register
if (this.notes_on[channel][midievent.data[1]] !== undefined) {
ctrlNum = this.notes_on[channel][midievent.data[1]].keymapped;
if (!deSnapped) {
delete this.notes_on[channel][midievent.data[1]];
}
}
}
this.muteTone(ctrlNum, midievent.data[2], midievent.data[0], midievent.timeStamp);
// }
// NOTE ON
} else if (cmd === 9) {
// Call note on function
// Pass the 'piper' argument to avoid loop of Piper's fake midievents
// 'statusByte' is useful to the Piper, and with 'timestamp' will be used for MIDI-OUT
this.playTone(ctrlNum, midievent.data[2], midievent.data[0], midievent.timeStamp, piper, tSnapped);
// If it's not a message from Hancock, store the message
// (because the internal virtual midi controller is mapped with the keymap also if Tsnap is active)
if (hancock === false) {
this.notes_on[channel][midievent.data[1]] = {'keymapped': ctrlNum, 'midievent': midievent};
}
// Do not MIDI monitor if it's a Piper's fake midievent (FT0)
if (piper === false) {
this.monitorMidiIN(monitorNotes, midievent.data[2], channel, midievent.srcElement.name);
}
}
// Control Change message or Selects Channel Mode message (0xBn)
} else if (cmd === 11) {
if (midievent.data[1] >= 0 && midievent.data[1] <= 119) {
// console.log("Incoming MIDI > type: CHANNEL VOICE message");
} else {
// All Notes Off message
if (midievent.data[1] === 123) {
// console.log("Incoming MIDI > type: ALL NOTES OFF message");
for (var mnn = 0; mnn <= 127; mnn++) {
this.muteTone(mnn, 80, midievent.data[0], midievent.timeStamp, true);
if (this.notes_on[channel][mnn]) {
delete this.notes_on[channel][mnn];
}
}
} else {
// console.log("Incoming MIDI > type: CHANNEL MODE message");
}
}
// Pitch Bend Change message
} else if (cmd === 14) {
// Handle pitchbend message
let pitchbendValue = ((midievent.data[2] * 128 + midievent.data[1]) - 8192) / 8192;
// Store the pitchbend value into global slot: value normalized to [-1 > 0,99987792968750]
this.dhc.settings.controller.pb.amount = pitchbendValue;
// Update the Synth voices frequencies
this.dhc.synth.updatePitchBend();
// Update the UI Monitors
this.dhc.initUImonitors();
// Other type of MIDI message
} else {
console.log("Incoming MIDI > type: Other message...");
// @todo - Any other type of message pass through and go out
// this.midiPassThrough.push( [midievent.data, timestamp] );
}
// Filter the Active Sensing messages (254 = 0xFE = 11111110)
} else if (midievent.data[0] !== 254) {
// @todo - implement RUNNING STATUS and interpret a message starting with a Data byte
// as part of the last received Status byte - Check if the browser do this for us
// Debug
console.log("Incoming MIDI > NON-STANDARD MIDI Message (maybe RUNNING STATUS). The first 4 bits of the 1st byte of the message (Status byte) has an unexpected value: " + cmd + " = " + cmd.toString(2));
}
}
/*==============================================================================*
* MIDI NOTE ON/OFF HANDLING
*==============================================================================*/
/**
* Send a Note-ON over the app
*
* @param {midinnum} ctrlNum - MIDI note number of the incoming MIDI message
* @param {velocity} velocity - Velocity of the incoming MIDI message
* @param {number} statusByte - Status Byte of the incoming MIDI message
* @param {number} timestamp - Timestamp of the incoming MIDI message (currently not used)
* @param {boolean} piper - If is a note generated by the Piper feature:
* `false`: it's not Piper;
* `true`: it's Piper.
* @param {boolean} tsnap - If is a note translated by the T-Snap receive mode:
* `false`: it's not T-snapped;
* `true`: it's T-snapped.
*/
// @old icNoteON
playTone(ctrlNum, velocity, statusByte, timestamp, piper, tsnap) {
// Get frequency and midi.cents assigned to the incoming MIDI key (ctrlNum)
// If the input MIDI key is in the ctrl_map, proceed
if (this.dhc.tables.ctrl[ctrlNum]) {
// Vars for a better reading
let ftNumber = this.dhc.tables.ctrl[ctrlNum].ft,
htNumber = this.dhc.tables.ctrl[ctrlNum].ht;
// **FT**
// If the key is mapped to a Fundamental Tone
if (ftNumber !== 129) {
// Play the DHC
this.dhc.playFT(HUM.DHCmsg.ftON('midi', ftNumber, velocity, ctrlNum, tsnap));
}
// **HT**
// If the key is mapped to a Harmonic Tone (or subharmonic)
if (htNumber !== 129) {
// Play the DHC
this.dhc.playHT(HUM.DHCmsg.htON('midi', htNumber, velocity, ctrlNum, piper, tsnap));
}
// If the input MIDI key is NOT in the ctrl_map, the message stop here
} // else {
// this.dhc.harmonicarium.components.backendUtils.eventLog("The pressed KEY on the CONTROLLER is not assigned on the current KEYMAP.");
// }
}
/**
* Send a Note-OFF over the app
*
* @param {midinnum} ctrlNum - MIDI note number of the incoming MIDI message
* @param {velocity} velocity - Velocity of the incoming MIDI message
* @param {number} statusByte - Status Byte of the incoming MIDI message
* @param {number} timestamp - Timestamp of the incoming MIDI message (currently not used)
* @param {boolean=} panic - It tells that the message has been generated by a "hard" All-Notes-Off request.
*/
// @old icNoteOFF
muteTone(ctrlNum, velocity, statusByte, timestamp, panic=false) {
// If the input MIDI key is in the ctrl_map, proceed
if (this.dhc.tables.ctrl[ctrlNum]) {
// Vars for a better reading
let ftNumber = this.dhc.tables.ctrl[ctrlNum].ft,
htNumber = this.dhc.tables.ctrl[ctrlNum].ht;
// **FT**
// If the key is mapped to a Fundamental Tone
if (ftNumber !== 129) {
this.dhc.muteFT(HUM.DHCmsg.ftOFF('midi', ftNumber, velocity, ctrlNum, panic));
}
// **HT**
// If the key is mapped to a Harmonic Tone
if (htNumber !== 129) {
this.dhc.muteHT(HUM.DHCmsg.htOFF('midi', htNumber, velocity, ctrlNum, panic));
}
}
}
/*==============================================================================*
* TONE SNAPPING - RECEIVING MODE FEATURE
*==============================================================================*/
/**
* Updates the status of the keys pressed on the controller when the T-Snap is active.
* It should be invoked when the HTs table at {@link HUM.DHC#tables} changes.
* Allows you to play only the keys that match the HTs frequencies.
* If a key on the controller remains pressed, it will be dynamically switched on or off when the HTs table is updated.
*/
// @old icTsnapUpdateHT
tsnapUpdateHT() {
// Handle the change of FT on TSNAP RECEIVING MODE
// Ignore handling if 'keymap' receiving mode
if (this.dhc.settings.controller.receive_mode === 'keymap') {
return;
} else if (this.dhc.settings.controller.receive_mode === 'tsnap-channel') {
let ht_channel = this.dhc.settings.controller.tsnap.channel.ht;
let ht_notes_on = this.notes_on[ht_channel];
// Mute or Re-play the current HT playng notes
// For every HT notes-on
for (let external of Object.keys(ht_notes_on)) {
let newCtrlNoteNumber = this.tsnapChannel(external, ht_channel, false);
let midievent = ht_notes_on[external].midievent;
// If the new note in NOT on the keymap
if (newCtrlNoteNumber === false) {
// Remove the note:
// Turn the note off (fake midievent)
let midievent_noteoff = {
data: [midievent.data[0], midievent.data[1], 0, midievent.data[3], midievent.data[4]],
srcElement: midievent.srcElement
};
this.midiMessageReceived(midievent_noteoff, true);
// If the new note is on the keymap
} else {
// Update the note:
// Turn the note off (fake midievent)
let midievent_noteoff = {
data: [midievent.data[0], midievent.data[1], 0, midievent.data[3], midievent.data[4]],
srcElement: midievent.srcElement
};
// Turn the note on (fake midievent)
let midievent_noteon = {
data: [midievent.data[0], midievent.data[1], midievent.data[2], midievent.data[3], midievent.data[4]],
srcElement: midievent.srcElement
};
this.midiMessageReceived(midievent_noteoff, false);
this.midiMessageReceived(midievent_noteon);
}
}
} else if (this.dhc.settings.controller.receive_mode === 'tsnap-divider') {
// @todo - to fix: ht_channel is from tsnap-channel mode!!! (no more omni!)
let divider_channel = this.dhc.settings.controller.tsnap.channel.divider;
let notes_on = this.notes_on[divider_channel];
// For every notes-on
for (let external of Object.keys(notes_on)) {
// If it's HT
if (this.dhc.settings.controller.tsnap.divider < external) {
let newCtrlNoteNumber = this.tsnapDivider(external, divider_channel, false);
let midievent = notes_on[external].midievent;
// If the new note in NOT on the keymap
if (newCtrlNoteNumber === false) {
// Remove the note:
// Turn the note off (fake midievent)
let midievent_noteoff = {
data: [midievent.data[0], midievent.data[1], 0, midievent.data[3], midievent.data[4]],
srcElement: midievent.srcElement
};
this.midiMessageReceived(midievent_noteoff, true);
// If the new note is on the keymap
} else {
// Update the note:
// Turn the note off (fake midievent)
let midievent_noteoff = {
data: [midievent.data[0], midievent.data[1], 0, midievent.data[3], midievent.data[4]],
srcElement: midievent.srcElement
};
// Turn the note on (fake midievent)
let midievent_noteon = {
data: [midievent.data[0], midievent.data[1], midievent.data[2], midievent.data[3], midievent.data[4]],
srcElement: midievent.srcElement
};
this.midiMessageReceived(midievent_noteoff, false);
this.midiMessageReceived(midievent_noteon);
}
}
}
}
}
/**
* Check if a frequency of a MIDI Note Number corresponds to a Harmonic (or Subharmonic) in the HT table under {@link HUM.DHC#tables}
* @param {midinnum} midi_note_number - The MIDI Note Number to be found in the reverse table
* @param {tonetype} type - The type of tone (FT or HT)
*
* @returns {(false|midinnum)} - Returns the keymapped MIDI Note Number that match the nearest HT to the incoming Note; if nothing is found, returns `false`
*/
// @old icTsnapFindCtrlNoteNumber
tsnapFindCtrlNoteNumber(midi_note_number, type) {
let table_keys_array = Object.keys(this.dhc.tables.reverse[type]);
let closest_mc = table_keys_array.reduce((prev, curr) => {
// @todo: snap to a different tone if two tones are at the same distance
let result = (Math.abs(curr - midi_note_number) < Math.abs(prev - midi_note_number) ? curr : prev);
if (Math.abs(result - midi_note_number) > this.dhc.settings.controller.tsnap.tolerance) {
return false;
} else {
return result;
}
});
if (closest_mc !== false) {
if (type === "ft") {
let relative_tone = this.dhc.tables.reverse.ft[closest_mc];
for (const [key, value] of Object.entries(this.dhc.tables.ctrl)) {
if (value.ft === relative_tone) {
return Number(key);
}
}
} else if (type === "ht") {
let relative_tone = this.dhc.tables.reverse.ht[closest_mc];
for (const [key, value] of Object.entries(this.dhc.tables.ctrl)) {
if (value.ht === relative_tone) {
return Number(key);
}
}
} else {
return false;
}
}
// If nothing found, return false
return false;
}
/**
* Tone Snap Channel receive mode router. Route messages accordingly to the MIDI Channel.
* @param {midinnum} midi_note_number - The MIDI Note Number to be found in the reverse table
* @param {midichan} channel - The MIDI Channel from which the message is coming from
* @param {boolean} hancock - If the message comes from the Hancock virtual MIDI input (if it's `true` ignore and don't apply T-Snap)
*
* @returns {(false|midinnum)} - Returns the keymapped MIDI Note Number that match the nearest HT to the incoming Note; if nothing is found, returns `false`
*/
// @old icTsnapChannel
tsnapChannel(midi_note_number, channel, hancock) {
if (hancock === true) {
return midi_note_number;
} else {
if (this.dhc.settings.controller.tsnap.channel.ft == channel) {
return this.tsnapFindCtrlNoteNumber(midi_note_number, 'ft');
}
else if (this.dhc.settings.controller.tsnap.channel.ht == channel) {
return this.tsnapFindCtrlNoteNumber(midi_note_number, 'ht');
} else {
return false;
}
}
}
/**
* Tone Snap Divider receive mode router. Route messages accordingly to the divider MIDI Note Number.
* @param {midinnum} midi_note_number - The MIDI Note Number to be found in the reverse table
* @param {midichan} channel - The MIDI Channel from which the message is coming from
* @param {boolean} hancock - If the message comes from the Hancock virtual MIDI input (if it's `true` ignore and don't apply T-Snap)
*
* @returns {(false|midinnum)} - Returns the keymapped MIDI Note Number that match the nearest HT to the incoming Note; if nothing is found, returns `false`
*/
// @old icTsnapDivider
tsnapDivider(midi_note_number, channel, hancock) {
if (hancock === true) {
return midi_note_number;
} else {
if (this.dhc.settings.controller.tsnap.channel.divider == channel) {
if (this.dhc.settings.controller.tsnap.divider >= midi_note_number) {
return this.tsnapFindCtrlNoteNumber(midi_note_number, 'ft');
}
else if (this.dhc.settings.controller.tsnap.divider < midi_note_number) {
return this.tsnapFindCtrlNoteNumber(midi_note_number, 'ht');
} else {
return false;
}
}
}
}
/*==============================================================================*
* MIDI UI tools
*==============================================================================*/
/**
* Switch the MIDI INPUT RECEIVING MODE (called when UI is updated)
*
* @param {('keymap'|'tsnap-channel'|'tsnap-divider')} receive_mode - FT/HT controller note receiving mode
*/
// @old icSwitchMidiReceiveMode
switchReceiveModeUI(receive_mode) {
let tsnap_tolerance = this.uiElements.fn.receiveModeTsnapTolerance,
tsnap_chan = this.uiElements.fn.receiveModeTsnapChan,
tsnap_divider = this.uiElements.fn.receiveModeTsnapDivider;
if (receive_mode === "keymap") {
tsnap_tolerance.style.display = "none";
tsnap_chan.style.display = "none";
tsnap_divider.style.display = "none";
} else if (receive_mode === "tsnap-channel") {
tsnap_tolerance.style.display = "table-row";
tsnap_chan.style.display = "table-row";
tsnap_divider.style.display = "none";
} else if (receive_mode === "tsnap-divider") {
tsnap_tolerance.style.display = "table-row";
tsnap_chan.style.display = "none";
tsnap_divider.style.display = "table-row";
} else {
let error = "The 'HTMLi_midiReceiveMode' HTML element has an unexpected value: " + receive_mode;
throw error;
}
}
/**
* MIDI-IN MONITOR
*
* @param {midinnum} noteNumber - MIDI Note number (or conversion string if the Tone snapping receiving mode is active)
* @param {velocity} velocity - MIDI Velocity amount
* @param {midichan} channel - MIDI Channel number
* @param {string} portName - MIDI Port name
*/
// @old icMIDImonitor
monitorMidiIN(noteNumber, velocity, channel, portName) {
let dhcID = this.dhc.id;
// Update the log on MIDI MONITOR on the UI
for (let x of [0,1]) {
this.uiElements.out[`monitor${x}_note`].innerText = noteNumber;
this.uiElements.out[`monitor${x}_velocity`].innerText = velocity;
this.uiElements.out[`monitor${x}_channel`].innerText = channel + 1;
this.uiElements.out[`monitor${x}_port`].innerText = portName;
}
}
/**
* MIDI event log for debug purposes
*
* @param {MIDIMessageEvent} midievent - The MIDI message event
*/
logMidiEvent(midievent) {
// @debug - Parsing log
// Filter the Active Sensing messages (254 = 0xFE = 11111110)
if (midievent.data[0] !== 254) {
var str = "** Incoming MIDI message [" + midievent.data.length + " bytes]: ";
for (var i = 0; i < midievent.data.length; i++) {
str += "0x" + midievent.data[i].toString(16) + " ";
}
str += " | received at timestamp: " + timestamp;
console.log(str);
console.log("cmd: " + cmd + " = " + cmd.toString(2));
console.log("channel: " + channel + " = " + channel.toString(2));
console.log("1st byte: " + midievent.data[0] + " = 0x" + midievent.data[0].toString(16).toUpperCase() + " = " + midievent.data[0].toString(2));
console.log("2nd byte: " + midievent.data[1] + " = 0x" + midievent.data[1].toString(16).toUpperCase() + " = " + midievent.data[1].toString(2));
console.log("3rd byte: " + midievent.data[2] + " = 0x" + midievent.data[2].toString(16).toUpperCase() + " = " + midievent.data[2].toString(2));
}
}
/**
* Initialize the UI of the MidiIn instance
*/
_initUI() {
//------------------------
// UI MIDI settings
//------------------------
// Default MIDI SETTINGS on UI textboxes
this.uiElements.in.receiveMode.value = this.dhc.settings.controller.receive_mode;
this.uiElements.in.receiveModeTsnapTolerance.value = this.dhc.settings.controller.tsnap.tolerance;
this.uiElements.in.receiveModeTsnapDivider.value = this.dhc.settings.controller.tsnap.divider;
this.uiElements.in.receiveModeTsnapChanFT.value = this.dhc.settings.controller.tsnap.channel.ft;
this.uiElements.in.receiveModeTsnapChanHT.options[this.dhc.settings.controller.tsnap.channel.ft].disabled = true;
this.uiElements.in.receiveModeTsnapChanHT.value = this.dhc.settings.controller.tsnap.channel.ht;
this.uiElements.in.receiveModeTsnapChanFT.options[this.dhc.settings.controller.tsnap.channel.ht].disabled = true;
this.uiElements.in.receiveModeTsnapChanDivider.value = this.dhc.settings.controller.tsnap.channel.divider;
// Set the FT/HT NUMBER RECEIVING MODE from UI HTML inputs
this.uiElements.in.receiveMode.addEventListener("change", (event) => {
this.dhc.settings.controller.receive_mode = event.target.value;
this.switchReceiveModeUI(event.target.value);
});
this.uiElements.in.receiveModeTsnapTolerance.addEventListener("input", (event) => {
this.dhc.settings.controller.tsnap.tolerance = event.target.value;
});
this.uiElements.in.receiveModeTsnapDivider.addEventListener("input", (event) => {
this.dhc.settings.controller.tsnap.divider = event.target.value;
});
this.uiElements.in.receiveModeTsnapChanFT.addEventListener("change", (event) => {
if (event.target.value == this.dhc.settings.controller.tsnap.channel.ht) {
throw "FT and HT cannot share the same MIDI channel!";
} else {
let ht_channels = this.uiElements.in.receiveModeTsnapChanHT;
for (let opt of ht_channels) {
opt.disabled = false;
}
ht_channels.options[event.target.value].disabled = true;
this.dhc.settings.controller.tsnap.channel.ft = event.target.value;
}
});
this.uiElements.in.receiveModeTsnapChanHT.addEventListener("change", (event) => {
if (event.target.value == this.dhc.settings.controller.tsnap.channel.ft) {
throw "FT and HT cannot share the same MIDI channel!";
} else {
let ft_channels = this.uiElements.in.receiveModeTsnapChanFT;
for (let opt of ft_channels) {
opt.disabled = false;
}
ft_channels.options[event.target.value].disabled = true;
this.dhc.settings.controller.tsnap.channel.ht = event.target.value;
}
});
this.uiElements.in.receiveModeTsnapChanDivider.addEventListener("change", (event) => {
this.dhc.settings.controller.tsnap.channel.divider = event.target.value;
});
// Set default FT/HT NUMBER RECEIVING MODE after the UI widgets are set-up
this.switchReceiveModeUI(this.dhc.settings.controller.receive_mode);
}
};