Midi and Javascript
This is the beginning how I learn to input midi into a web browser via #javascript
HTML
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Midi Keyboard</title>
<script src="./keyboard.js" async></script>
</head>
<body>
<button id="btn-start">start</button>
</body>
</html>
JS
console.log('keyboard');
window.AudioContext = window.AudioContext || window.webkitAudioContext
let ctx = null
let oscillators = []
const btnStart = document.getElementById('btn-start')
btnStart.addEventListener('click', () => handleInit())
//? Setup
const handleInit = () => {
if (!ctx) {
const newAudioContext = new AudioContext();
newAudioContext.resume().then(() => {
ctx = newAudioContext
setupMidiAccess(newAudioContext);
});
}
};
const setupMidiAccess = (audioContext) => {
navigator.requestMIDIAccess().then(success, failure);
};
function success(midiAccess){
// midiAccess.onstatechange = updateDevices
const inputs = midiAccess.inputs
inputs.forEach((input) => {
input.onmidimessage = handleInput
})
}
function failure(){
console.warn('Can not connect MIDI navigator');
}
// function updateDevices(e){
// // console.log(e);
// console.table({
// Name: e.port.name,
// Brand: e.port.manufacture,
// State: e.port.state,
// Type: e.port.type,
// });
// }
//? input control
function handleInput(input) {
const command = input.data[0]
const note = input.data[1]
const velocity = input.data[2]
// console.table({
// command,
// note,
// velocity,
// })
switch (command) {
case 144: // note on
(velocity > 0)
? noteOn(note, velocity)
: noteOff(note, velocity)
break;
case 128: // note off
noteOff(note, velocity)
break;
default:
break;
}
}
function noteOn(note, velocity) {
if(!ctx) return console.warn('no audio ctx')
const osc = ctx.createOscillator()
const oscGain = ctx.createGain()
oscGain.gain.value = 0.3
const VelGainAmt = velocity / 127
const velGain = ctx.createGain()
velGain.gain.value = VelGainAmt
osc.type = 'sine'
osc.frequency.value = midiToFreq(note)
osc.connect(oscGain)
oscGain.connect(velGain)
velGain.connect(ctx.destination)
osc.gain = oscGain
osc.start()
oscillators = [...oscillators, osc]
}
function noteOff(note, velocity) {
const releaseTime = ctx.currentTime + 0.03
const updatedOscillators = oscillators.filter((oscillator) => {
const freq = oscillator.frequency.value;
if (freqToMidi(freq) === note) {
const oscGain = oscillator.gain
oscGain.gain.setValueAtTime(oscGain.gain.value, ctx.currentTime)
oscGain.gain.exponentialRampToValueAtTime(0.0001, releaseTime) // sustain & release
setTimeout(() => {
oscillator.stop();
oscillator.disconnect();
}, releaseTime + 10);
return false;
}
return true;
});
oscillators = updatedOscillators
console.log(oscillators);
}
//? lib calculations
function midiToFreq(num){
const a = 440
return (a / 32) * (2 ** ((num - 9) / 12))
}
function freqToMidi(frequency) {
const midiNote = 12 * Math.log2(frequency / 440) + 69;
return Math.round(midiNote);
}
left in a few comments during my debug journey. The updateDevices()
function could be useful later when I want to add UI selection for different controllers.
Troubleshooting
Encoders / faders cause lag
looks like it was just because I was logging a 6 cell console.table
very fast