I am trying to build a web page where I can play the tones using a Bluetooth MIDI controller. I intend to use up to 4 different controllers at once but select only one at a time using the dropdown that lists all the available input MIDI controllers. In the code I am using, even though I can set the MIDI input device, sending the command from any other connected device (but not selected in the dropdown) also activates the tones.
I am not able to figure it out. https://codepen.io/wittywit/pen/NWMeQjr?
import * as Tone from 'https://cdn.skypack.dev/tone@v14.7.58';
let reverb = new Tone.JCReverb(0).toDestination();
let delay = new Tone.FeedbackDelay(0);
const synth = new Tone.Synth().chain(delay, reverb).toDestination();
const now = Tone.now();
const notes = ['A3','A#3','B3','C4','C#4','D4','D#4','E4','F4','F#4'];
const keyboardNotesMap = {
'q': 'A3',
'w': 'A#3',
'e': 'B3',
'r': 'C4',
't': 'C#4',
'y': 'D4',
'u': 'D#4',
'i': 'E4',
'o': 'F4',
'p': 'F#4'
};
const midiNotesMap = {
'57': 'A3',
'58': 'A#3',
'59': 'B3',
'60': 'C4',
'61': 'C#4',
'62': 'D4',
'63': 'D#4',
'64': 'E4',
'65': 'F4',
'66': 'F#4'
};
let pianoElement;
let keyElements;
function getUpDOM() {
const pianoElement = document.getElementById('piano');
notes.forEach((note) => {
const noteDiv = document.createElement("div");
noteDiv.textContent = note;
noteDiv.className = 'note';
noteDiv.setAttribute('data', note);
piano.appendChild(noteDiv);
});
keyElements = document.getElementsByClassName('note');
}
function keyAction() {
for (let i = 0; i < keyElements.length; i++) {
keyElements[i].onmousedown = async function playNote(key) {
const note = keyElements[i].getAttribute('data');
synth.triggerAttackRelease(note, "8n");
};
};
}
function startUp() {
document.querySelector('button.start')
.addEventListener('click', async () => {
await Tone.start();
synth.triggerAttackRelease("C4", "8n", now)
synth.triggerAttackRelease("E4", "8n", now + 0.5)
synth.triggerAttackRelease("G4", "8n", now + 1)
});
}
function keyUpHandler(event) {
for (let i = 0; i < keyElements.length; i++) {
keyElements[i].classList.remove('active');
}
}
function keyDownHandler(event) {
console.log(event);
if (event.key && !keyboardNotesMap[event.key]) {
return;
}
if (event.midiKey && !midiNotesMap[event.midiKey]) {
return;
}
const key = keyboardNotesMap[event.key] || midiNotesMap[event.midiKey];
console.log('key', key);
synth.triggerAttackRelease(key, "8n");
let element;
for (let i = 0; i < keyElements.length; i++) {
if( keyElements[i].getAttribute('data') === keyboardNotesMap[event.key] ) {
element = keyElements[i];
element.classList.add('active');
}
}
}
function keyboardPress() {
document.addEventListener('keydown', keyDownHandler, false);
document.addEventListener('keyup', keyUpHandler, false);
}
function delayListner() {
const delayElement = document.querySelector('.controls .delay');
delayElement.addEventListener('change', (event) => {
delay = new Tone.FeedbackDelay(event.target.value);
});
}
startUp();
getUpDOM();
keyAction();
keyboardPress();
delayListner();
const addMidiListSelect = (midiInput) => {
const select = document.querySelector('.midi select')
var opt = document.createElement('option');
opt.value = midiInput.value;
opt.innerHTML = midiInput.name;
select.appendChild(opt);
}
function parseMidiMessage(message) {
return {
command: message.data[0] >> 4,
channel: message.data[0] & 0xf,
note: message.data[1],
velocity: message.data[2] / 127
}
}
navigator.requestMIDIAccess()
.then(function(access) {
console.log('midi access');
// Get lists of available MIDI controllers
const inputs = access.inputs;
const outputs = access.outputs;
inputs.forEach((input) => {
addMidiListSelect({ name: input.name, value: input.value });
// console.log(input.name); /* inherited property from MIDIPort */
console.log(input);
input.onmidimessage = function(message) {
const { command, note } = parseMidiMessage(message);
if(command === 9) {
console.log(note);
keyDownHandler({ midiKey: note })
}
if(command === 8) {
console.log(note);
keyUpHandler({ midiKey: note })
}
}
});
access.onstatechange = function(e) {
// Print information about the (dis)connected MIDI controller
console.log(e.port.name, e.port.manufacturer, e.port.state);
};
});
Codepen contains the code that I am using.