MidiController.prototype = new Controller();
MidiController.constructor = MidiController;
/**
* @summary
* Connects a midicontroller with a range of listeners. Can also send commands Back
* Midi Example on codepen:
* <a href="https://codepen.io/xangadix/pen/BbVogR" target="_blank">codepen</a>
*
* @description
* The Midi class searches and Connects to a midicontroller with a range of listeners.
* You can also send commands _back_. This is especially handy when you can control
* lights or automatic faders on your MIDI Controller.
*
* Here is a demo on [Codepen](https://codepen.io/xangadix/pen/BbVogR), which was tested with 2 AKAI midicontrollers
*
* The original implementation is on GitHub in a [Gist](https://gist.github.com/xangadix/936ae1925ff690f8eb430014ba5bc65e).
*
* @example
* var midi1 = new MidiController();
* midi1.addEventListener( 0, function(_arr) { console.log( " Midi received", _arr ) } ) ;
* // button 0, returns [ 144, 0, 1 ]
*
* // use debug for more information
* midi.debug = true;
*
* @implements Controller
* @constructor Controller#MidiController
* @param options:Object
* @author Sense Studios
*/
function MidiController( _options ) {
// base
// returns a floating point between 1 and 0, in sync with a bpm
var _self = this
// exposed variables.
_self.uuid = "MidiController_" + (((1+Math.random())*0x100000000)|0).toString(16).substring(1);
_self.type = "MidiController"
/** @member Controller#KeyboardController#debug {boolean} */
_self.bypass = true
/** @member Controller#KeyboardController#debug {boolean} */
_self.debug = false
/** @member Controller#KeyboardController~debug {boolean} */
_self.ready = false
/** @member Controller#KeyboardController~debug {object} */
_self.controllers = {}
var binds = []
var nodes = []
var c = 0 // counter
var midi, input, output
/** @function Controller#KeyboardController~success {object} */
var success = function(_midi) {
midi = _midi
var inputs = midi.inputs.values();
var outputs = midi.outputs.values();
for (i = inputs.next(); i && !i.done; i = inputs.next()) {
input = i.value;
input.onmidimessage = _self.onMIDIMessage;
}
for (o = outputs.next(); o && !o.done; o = outputs.next()) {
output = o.value;
//if ( _self.debug ) console.log(" MIDI INITIALIZED", "ready")
}
console.log("Midi READY? ", output, midi)
if ( output != undefined ) _self.ready = true
if ( output != undefined ) _self.bypass = false
}
// everything went wrong.
/** @function Controller#KeyboardController~failure {object} */
var failure = function (_fail) {
console.error('No access to your midi devices.', _fail);
}
// request MIDI access
console.log("Midi check... ")
if (navigator.requestMIDIAccess) {
navigator.requestMIDIAccess()
.then( success, failure );
}
// some examples, this is the 'onpress' (and on slider) function
var doubleclickbuffer = [ 0, 0, 0, 0 ]
var doubleclickPattern = [ 128, 144, 128, 144 ]
var doubleclick = false
/** @function Controller#KeyboardController~onMIDIMessage {event} */
_self.onMIDIMessage = function(e) {
if (_self.debug) console.log(" MIDIMESSAGE >>", e.data)
dispatchMidiEvent(e)
// hello from midi
// console.log(e.data)
// Uint8Array(3) [176, 48, 117]
// [ state, key, velocity ]
// state
// 144 = down
// 112 = up
// 176 = sliding ( fader )
//
// This is mainly experimental code for doubleclicking
// we could return this as 256, 257 or higher state values (?)
/*
var opaque = false
if (doubleclick) return
doubleclickbuffer.unshift([ e.data[0], e.data[1] ])
doubleclickbuffer.pop()
if ( doubleclickbuffer.map(function(m) { return m[0] } ).join(",") == doubleclickPattern.join(",") ) {
console.log("blink1")
// update event listeners
listeners.forEach( function( val, i ) {
// doubleclick
if ( val.btn == e.data[1] ) {
val.cb( e.data, true )
}
})
if ( doubleclickbuffer.map( function(m) { return m[1] } ).every( (val, i, arr) => val === arr[0] ) ) {
doubleclickbuffer = [ 0, 0, 0, 0 ]
// DO STUFF ON DOUBLECLICK
_self.output.send( [ 0x90, e.data[1], GREEN_BLINK ] )
doubleclick = true
// chain1.setChainLink(e.data[1], faders[e.data[1]]/126)
faders_opaque[e.data[1]] = 1
// var source = chain1.getChainLink( e.data[1] )
// if (source.video) source.video.currentTime = Math.random() * source.video.duration
setTimeout(function() { doubleclickbuffer = [ 0, 0, 0, 0 ]; doubleclick = false}, 350)
return
}
}
// update event listeners
listeners.forEach( function( val, i ) {
// doubleclick
if ( val.btn == e.data[1] ) {
val.cb( e.data, false )
}
})
setTimeout(function() { doubleclickbuffer = [ 0, 0, 0, 0 ]; doubleclick = false }, 350)
//console.log( doubleclickbuffer )
if (e.data[1] == 48) {
//console.log( e.data[2] / 126 )
//testSource2.video.playbackRate = e.data[2] / 56
//console.log(e.data[2])
//if ( faders_opaque[0] ) chain1.setChainLink (0, e.data[2]/126 )
faders[0] = e.data[2]
}
if (e.data[1] == 49) {
//testSource3.video.playbackRate = e.data[2] / 56
//if ( faders_opaque[1] ) chain1.setChainLink (1, e.data[2]/126 )
faders[1] = e.data[2]
}
if (e.data[1] == 50) {
//testSource4.video.playbackRate = e.data[2] / 56.0
//if ( faders_opaque[2] ) chain1.setChainLink (2, e.data[2]/126 )
faders[2] = e.data[2]
}
if (e.data[1] == 51) {
//testSource4.video.playbackRate = e.data[2] / 56.0
//if ( faders_opaque[3] ) chain1.setChainLink (3, e.data[2]/126 )
faders[3] = e.data[2]
}
if (e.data[1] == 64) {
// switch everything off
var commands = []
midimap.forEach( function( row, i ) {
row.forEach( function( value, j ) {
commands.push(0x90, value, OFF)
})
})
rest.forEach( function( r, i ) {
commands.push( 0x90, r, OFF )
})
_self.output.send(commands)
}else if (e.data[1] == 65) {
// switch the main pads yellow
var commands = []
midimap.forEach( function( row, i ) {
row.forEach( function( value, j ) {
commands.push( 0x90, value, YELLOW )
})
})
_self.output.send( commands )
}else{
// press a button, make it green
if (e.data[0] == 128 ) {
_self.output.send( [ 0x90, e.data[1], OFF ] );
//chain1.setChainLink(e.data[1], 0)
//console.log("toggle chain")
doubleclick = false
}
if (e.data[0] == 144 ) {
_self.output.send( [ 0x90, e.data[1], GREEN ] );
//chain1.setChainLink(e.data[1], faders[e.data[1]]/126)
//console.log("toggle chain", faders[e.data[1]], e.data[1] )
faders_opaque[e.data[1]] = 0
doubleclick = false
}
}
*/
}
// ---------------------------------------------------------------------------
/** @function Controller#KeyboardController~init */
_self.init = function() {}
/** @function Controller#KeyboardController~update */
_self.update = function() {}
/**
* @description
* send midi data back to the controller. To switch a light on, or to make it
* change color. Theorettically you should be able to control motorized faders
* too, but I haven't tested that. Lights is nice though.
*
* try to send evertything in one blob and try not to do all kinds of little
* updates, it'll crash your midi controller (it crashed mine)
*
*
* @example
* // make button 0 yellow, if a video reports 'seeking'
* testSource1.video.addEventListener('seeking', function() { midi1.send([ 0x90, 0, 6] );} )
*
* // don't forget to switch it off again
* testSource1.video.addEventListener('seeked', function() { midi1.send([ 0x90, 0, 5] );} )
*
* // make button 8-11 and button 16-19 green (tested with an Akai APC Mini)
*
* midi1.send([ 0x90, 8, 1, 0x90, 9, 1, 0x90, 10, 1, 0x90, 11, 1, 0x90, 16, 1, 0x90, 17, 1, 0x90, 18, 1, 0x90, 19, 1])
*
* @function Controller#MidiController#send
* @param {array} commands - the sequence that needs execution
*
*/
_self.send = function( commands ) {
if (_self.ready) {
console.log("Midi send ", commands, "to", output)
output.send( commands )
}else{
console.log("Midi is not ready yet")
}
}
/**
* @description
* clears all the buttons (sets them to 0)
* @example
* midi.clear()
* @function Controller#MidiController#clear
*
*/
_self.clear = function() {
var commands = []
for( var i = 0; i++; i < 100 ) commands.push( 0x90, i, 0 );
output.send(commands)
}
/**
* @description
* removeEventListener
* @example
* midi.removeEventListener(1)
* @function Controller#MidiController#removeEventListener
* @param {integer} _target - the number of controller being pressed
*
*/
self.removeEventListener = function( _target ) {
nodes.forEach( function(node, i ) {
if ( node.target == _target ) {
var removeNode = i
}
})
nodes.splice(i, 1)
}
/**
* @description
* addEventListener, expect an array of three values, representing the state and value of your controller
*
* @example
* function doSomething(_arr ) {
* console.log('pressed1', arr) // [ 144, 0, 1 ]
* }
* midicontroller.addEventListener(1, function() )
*
* @function Controller#MidiController#addEventListener
* @param {integer} _target - the number of controller being pressed
* @param {function} _callback - the callback to be executed
*
*/
_self.addEventListener = function( _target, _callback, ) {
nodes.push( { target: _target, callback: _callback } )
console.log("MIDI listeners: ", nodes)
}
/** @function Controller#KeyboardController~dispatchMidiEvent {event} */
var dispatchMidiEvent = function(e) {
nodes.forEach(function( _obj ){
if ( _obj.target == e.data[1] ) {
_obj.callback(e.data)
}
});
}
}