var NANOPAD_TOUCHS = { 37: 0, 39: 1, 41: 2, 43: 3, 45: 4, 47: 5, 49: 6, 51: 7, 36: 8, 38: 9, 40: 10, 42: 11, 44: 12, 46: 13, 48: 14, 50: 15 }; var midi = { onMIDISuccess: function(midiAccess) { console.log('MIDI Access Object', midiAccess); //console.log(this); var self = this; midiAccess.onstatechange = function(e) { self.onMIDIAccessChange(e); } this.midiAccess = midiAccess; this.inputs = {}; this.outputs = {}; this.initPorts(); }, initPorts: function() { var self = this; var inputs = this.midiAccess.inputs; if (inputs.size > 0) { inputs.forEach( function(port, key) { //console.log(port); self.registerPort(port); } ); } else { $("#midiinputs").append("
No connected inputs
"); } var outputs = this.midiAccess.outputs; if (outputs.size > 0) { outputs.forEach( function(port, key) { self.registerPort(port); self.renderPort(port); } ); } else { $("#midioutputs").append("No connected outputs
"); } }, onMIDIAccessChange: function(e) { console.log(e); //console.log(this); var port = e.port; var portContainer = $("#midi" + port.type + "s"); if (portContainer.html().startsWith("No connected")) { portContainer.empty(); } if (port.type == "input") { if (this.inputs[port.name] === undefined) { this.registerPort(port); } } else { if (this.outputs[port.name] === undefined) { this.registerPort(port); } } this.renderPort(port); }, renderPort: function(port) { if (port.state == "connected") { $("#midi" + port.type + "s").append(port.name); } }, registerPort: function(port) { var self = this; if (port.type == "input") { this.inputs[port.name] = port; port.onmidimessage = function(m) { self.onMIDIMessage(m); }; } else { this.outputs[port.name] = port; } port.onstatechange = function(e) { self.onPortStateChange(e); }; }, onMIDIFailure: function(e) { alert("No access to MIDI devices or your browser doesn't support WebMIDI API. Please use WebMIDIAPIShim " + e); }, onPortStateChange: function(event) { console.log(event); }, onMIDIMessage: function(message) { var port = message.target; var data = message.data; if (data[0] == 144) { /* touch on */ var sample_idx = NANOPAD_TOUCHS[data[1]]; this.onTouchOn(port, data, sample_idx); } }, onTouchOn: function(port, data, sample_idx) {} }; // status bytes on channel 1 var messages = { off: 128, on: 144, pp: 160, cc: 176, pc: 192, cp: 208, pb: 224 } var device = function(outputName) { this.current = midi.outputs[outputName]; this.channel = 1; // makes device visible inside of nested function defs var self = this; this._send = function(status, data) { var messageArr = [status + (self.channel - 1)].concat(data); console.log("sending " + messageArr + " to " + self.current.name); self.current.send(messageArr); return self; } this.ch = function(channel) { self.channel = channel; return self; } this.cc = function(b1, b2) { return self._send(messages.cc, [b1, b2]); } this.on = function(b1, b2) { return self._send(messages.on, [b1, b2]); } this.off = function(b1, b2) { return self._send(messages.off, [b1, b2]); } this.pp = function(b1, b2) { return self._send(messages.pp, [b1, b2]); } this.cp = function(b1) { return self._send(messages.cp, [b1]); } this.pb = function(b1) { return self._send( messages.pb, [ b1 & 127, b1 >> 7 ] ); } this.pc = function(b1) { return self._send(messages.pc, [b1]); } this.panic = function() { return self.cc(123, 0) } this.rpn = function(b1, b2) { return self.cc(101, b1 >> 7) .cc(100, b1 & 127) .cc(6, b2 >> 7) .cc(38, b2 & 127) .cc(101, 127) .cc(100, 127); } this.nrpn = function(b1, b2) { return self.cc(99, b1 >> 7) .cc(98, b1 & 127) .cc(6, b2 >> 7) .cc(38, b2 & 127) .cc(101, 127) .cc(100, 127); } this.raw = function(data) { console.log("sending raw data: " + data); self.current.send(data); return self; } this.toString = function() { var s = "no connected devices"; if (typeof this.current != 'undefined') { s = ""; } return s; } return this; }; var nanofun = function() { var self = this; self.initAudio = function() { self.sample_buffers = Array(16); self.samples = Array(16); self.audioCtx = new window.AudioContext(); self.gainNode = self.audioCtx.createGain(); self.gainNode.connect(self.audioCtx.destination); } self.initMIDI = function() { if (navigator.requestMIDIAccess) { navigator.requestMIDIAccess({sysex: true}).then( function(midiAccess) { midi.onMIDISuccess(midiAccess); }, function(e) { midi.onMIDIFailure(e); } ); } } self.initUI = function() { var $nanopad = $('#nanopad'); $('.nanotouch input').on('change', function(ev) { var nanotouch = $(this).parent(); var sample_idx = $nanopad.children().index(nanotouch); var reader = new FileReader(); reader.onload = function(e) { self.audioCtx.decodeAudioData(this.result, function(buffer) { sample_buffers[sample_idx] = buffer; $(nanotouch).find('span.duration').text(parseInt(buffer.duration) + 's'); $(nanotouch).removeClass('error').addClass('loaded'); }, function(e) { $(nanotouch).find('span').text(''); $(nanotouch).removeClass('loaded').addClass('error'); }); } reader.readAsArrayBuffer(this.files[0]); $(nanotouch).find('span.name').text(this.files[0].name); }); midi.onTouchOn = function(port, data, sample_idx) { var sample_buffer = self.sample_buffers[sample_idx]; var nanotouch = $('.nanotouch')[sample_idx]; if (typeof(sample_buffer) != 'undefined') { if (typeof(samples[sample_idx]) != 'undefined' && samples[sample_idx].context.state == 'running') { self.samples[sample_idx].stop(0); self.samples[sample_idx] = undefined; } else { var sample = self.audioCtx.createBufferSource(); self.samples[sample_idx] = sample; sample.loop = false; sample.connect(gainNode); sample.buffer = sample_buffer; sample.onended = function() { console.log('ended'); $(nanotouch).removeClass('playing'); self.samples[sample_idx] = undefined; } $(nanotouch).addClass('playing'); sample.start(0); } } } } self.initAudio(); self.initMIDI(); self.initUI(); } $(function() { nanofun(); });