Skip to content

Instantly share code, notes, and snippets.

@softpunch
Last active December 4, 2019 08:10
Show Gist options
  • Save softpunch/eef9c7836bd9eab8ee08f20b993e99e6 to your computer and use it in GitHub Desktop.
Save softpunch/eef9c7836bd9eab8ee08f20b993e99e6 to your computer and use it in GitHub Desktop.
Send WebMIDI Pitchbend LFO
// use webMIDI to send a pitch-bend message
// that oscillates like an LFO
// HTML code:
//
// <label for="outputSelector">select midi output</label><br>
// <select id="outputSelector" onchange="updatePort()">
// <option value="null" selected>[none]</option>
// </select>
// <input type="button" value="play" onclick="playIt()" />
// <input type="button" value="stop" onclick="stopIt()" />
// begin js:
var midi = null;
var outputSelector = document.querySelector('#outputSelector');
function update() {
// schedule the next update
requestAnimationFrame(update);
// get current LFO value
var val = myLfo.value(); // from -1 to 1
var unsignedVal = val + 1; // from 0 to 2
// scaling method (destination & source)
// dmax - dmin = drange
// 16384 - 0 = 16384
// smax - smin = srange
// 2 - 0 = 2
// d = dmin + (drange)(s - smin) / (srange)
// scaledVal = 0 + (16384)(unsignedVal - 0) / 2
var preScaledVal = (16384 * unsignedVal) / 2;
var scaledVal = parseInt(preScaledVal);
// max total bend [upward] is 0x3FFF or 16383
// min total bend [downward] is 0x0000 or 0
// data transmits in two separate bytes
// each begin with zero followed by 7 bits
// each individually range from 0x00 to 0x7F
// use bitwise operators
// to get appropriate digits
var bend1 = scaledVal&127;
var bend2 = scaledVal>>7;
var bendMessage = [0xE0, bend1, bend2];
var now = window.performance.now();
var output = midi.outputs.get(port);
output.send(bendMessage, now);
};
var port;
function updatePort() {
var i = outputSelector.selectedIndex;
port = outputSelector.options[i].value;
}
function playIt(){
sendMiddleC(midi, port);
myLfo = new LFO();
update();
}
function stopIt() {
update = undefined;
var v = myLfo.value();
console.log(v)
}
function onMIDISuccess( midiAccess ) {
midi = midiAccess;
midi.outputs.forEach(
function(port, key) {
var portName = port.name;
var portID = port.id;
var option = document.createElement("option");
option.setAttribute("value", portID);
var textNode = document.createTextNode(portName);
option.appendChild(textNode);
outputSelector.appendChild(option);
});
}
navigator.requestMIDIAccess().then(onMIDISuccess);
function sendMiddleC( midiAccess, portID ) {
var noteOnMessage = [0x90, 60, 0x7f];
var output = midiAccess.outputs.get(portID);
output.send( noteOnMessage );
output.send( [0x80, 60, 0x40], window.performance.now() + 6000.0 );
}
// based on @TheTeapot418's LFO.js
// http://github.com/TheTeapot418/LFO.js
// [with modifications!]
function LFO (param) {
this.startTime = window.performance.now();
var param = param || {};
this.freq = 1;
this.amplitude = 2;
this.set = function (param) {
var param = param || {};
this.freq = param.freq || this.freq;
this.amplitude = param.amplitude || this.amplitude;
switch (param.waveform) {
case "sine":
this.waveform = function(x) {
return Math.sin(x);
}
break;
case "triangle":
this.waveform = function(x) {
if (x <= Math.PI) {
return x / (Math.PI / 2) - 1;
} else {
return (x - (Math.PI)) / (-Math.PI / 2) + 1;
}
}
break;
case "square":
this.waveform = function(x) {
if (x <= Math.PI) {
return -1;
} else {
return 1;
}
}
break;
case "sawtooth":
this.waveform = function(x) {
return x / Math.PI - 1;
}
break;
case "noise":
this.waveform = function(x) {
return Math.random() * 2 - 1;
}
break;
default:
this.waveform = function(x) {
return Math.sin(x);
};
}
}
this.set(param);
this.value = function() {
var T = 1 / this.freq;
var d = window.performance.now();
var a = (d - this.startTime) / 1000;
var x = this.freq * a;
x = x - Math.floor(x);
return (this.amplitude / 2) * this.waveform(x * 2 * Math.PI);
}
this.reset = function() {
this.startTime = window.performance.now();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment