1 var NANOPAD_TOUCHS = Array(37, 39, 41, 43, 45, 47, 49, 51,
2 36, 38, 40, 42, 44, 46, 48, 50);
4 /* on French/Belgian keyboards, emulate pad touches with keypresses */
5 var KEYBOARD_CODES = Array('a', 'z', 'e', 'r', 't', 'u', 'i', 'o',
6 'q', 's', 'd', 'f', 'g', 'h', 'j', 'k');
10 onMIDISuccess: function(midiAccess) {
11 console.log('MIDI Access Object', midiAccess);
15 midiAccess.onstatechange = function(e) {
16 self.onMIDIAccessChange(e);
18 this.midiAccess = midiAccess;
25 initPorts: function() {
28 var inputs = this.midiAccess.inputs;
29 if (inputs.size > 0) {
33 self.registerPort(port);
37 $("#midiinputs").append("<p>No connected inputs</p>");
40 var outputs = this.midiAccess.outputs;
41 if (outputs.size > 0) {
44 self.registerPort(port);
45 self.renderPort(port);
49 $("#midioutputs").append("<p>No connected outputs</p>");
53 onMIDIAccessChange: function(e) {
54 console.log('on midi access change', e);
57 var portContainer = $("#midi" + port.type + "s");
58 if (portContainer.html().startsWith("<p>No connected")) {
59 portContainer.empty();
62 if (port.state == "disconnected") {
63 if (port.type == "input") {
64 this.inputs[port.name] = undefined;
66 this.outputs[port.name] = undefined;
69 if (port.type == "input") {
70 if (this.inputs[port.name] === undefined) { this.registerPort(port); }
72 if (this.outputs[port.name] === undefined) { this.registerPort(port); }
74 this.renderPort(port);
78 renderPort: function(port) {
79 if (port.state == "connected") {
80 $("#midi" + port.type + "s").append(port.name);
84 registerPort: function(port) {
86 if (port.type == "input") {
87 this.inputs[port.name] = port;
88 port.onmidimessage = function(m) { self.onMIDIMessage(m); };
90 this.outputs[port.name] = port;
92 if (port.name == 'nanoKONTROL2 MIDI 1') {
93 /* turn "external leds" mode on.
95 * The sysex dump has been recorded from the official Korg kontrol
96 * editor by the Overtone project, original code at:
97 * https://github.com/overtone/overtone/blob/master/src/overtone/device/midi/nanoKONTROL2.clj */
98 device("nanoKONTROL2 MIDI 1").raw([240, 126, 127, 6, 1, 247]);
99 device("nanoKONTROL2 MIDI 1").raw([240, 66, 64, 0, 1, 19, 0, 31, 18, 0, 247]);
100 device("nanoKONTROL2 MIDI 1").raw([240, 126, 127, 6, 1, 247]);
102 device("nanoKONTROL2 MIDI 1").raw([ 240, 66, 64, 0, 1, 19, 0, 127, 127,
103 2, 3, 5, 64, 0, 0, 0, 1, 16, 1, 0, 0, 0, 0, 127, 0, 1, 0, 16,
104 0, 0, 127, 0, 1, 0, 32, 0, 127, 0, 0, 1, 0, 48, 0, 127, 0, 0,
105 1, 0, 64, 0, 127, 0, 16, 0, 1, 0, 1, 0, 127, 0, 1, 0, 0, 17, 0,
106 127, 0, 1, 0, 0, 33, 0, 127, 0, 1, 0, 49, 0, 0, 127, 0, 1, 0,
107 65, 0, 0, 127, 0, 16, 1, 0, 2, 0, 0, 127, 0, 1, 0, 18, 0, 127,
108 0, 0, 1, 0, 34, 0, 127, 0, 0, 1, 0, 50, 0, 127, 0, 1, 0, 0, 66,
109 0, 127, 0, 16, 1, 0, 0, 3, 0, 127, 0, 1, 0, 0, 19, 0, 127, 0,
110 1, 0, 35, 0, 0, 127, 0, 1, 0, 51, 0, 0, 127, 0, 1, 0, 67, 0,
111 127, 0, 0, 16, 1, 0, 4, 0, 127, 0, 0, 1, 0, 20, 0, 127, 0, 0,
112 1, 0, 36, 0, 127, 0, 1, 0, 0, 52, 0, 127, 0, 1, 0, 0, 68, 0,
113 127, 0, 16, 1, 0, 0, 5, 0, 127, 0, 1, 0, 21, 0, 0, 127, 0, 1,
114 0, 37, 0, 0, 127, 0, 1, 0, 53, 0, 127, 0, 0, 1, 0, 69, 0, 127,
115 0, 0, 16, 1, 0, 6, 0, 127, 0, 0, 1, 0, 22, 0, 127, 0, 1, 0, 0,
116 38, 0, 127, 0, 1, 0, 0, 54, 0, 127, 0, 1, 0, 70, 0, 0, 127, 0,
117 16, 1, 0, 7, 0, 0, 127, 0, 1, 0, 23, 0, 0, 127, 0, 1, 0, 39, 0,
118 127, 0, 0, 1, 0, 55, 0, 127, 0, 0, 1, 0, 71, 0, 127, 0, 16, 0,
119 1, 0, 58, 0, 127, 0, 1, 0, 0, 59, 0, 127, 0, 1, 0, 0, 46, 0,
120 127, 0, 1, 0, 60, 0, 0, 127, 0, 1, 0, 61, 0, 0, 127, 0, 1, 0,
121 62, 0, 127, 0, 0, 1, 0, 43, 0, 127, 0, 0, 1, 0, 44, 0, 127, 0,
122 1, 0, 0, 42, 0, 127, 0, 1, 0, 0, 41, 0, 127, 0, 1, 0, 45, 0, 0,
123 127, 0, 127, 127, 127, 127, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0,
124 0, 0, 0, 0, 0, 0, 0, 0, 0, 247]);
126 device("nanoKONTROL2 MIDI 1").raw([240, 126, 127, 6, 1, 247]);
127 device("nanoKONTROL2 MIDI 1").raw([240, 66, 64, 0, 1, 19, 0, 31, 17, 0, 247]);
129 function on(note) { device("nanoKONTROL2 MIDI 1").cc(note, 127); }
130 function off(note) { device("nanoKONTROL2 MIDI 1").cc(note, 0); }
131 var leds = Array(43, 44, 42, 41, 45);
132 for (var i=0; i<8; i++) {
146 var interval_id = setInterval(function() {
147 if (led_idx < leds.length) {
150 off(leds[led_idx-2]);
151 if (led_idx-1 == leds.length) {
152 clearInterval(interval_id);
159 port.onstatechange = function(e) { self.onPortStateChange(e); };
162 onMIDIFailure: function(e) {
163 alert("No access to MIDI devices or your browser doesn't support WebMIDI API. Please use WebMIDIAPIShim " + e);
166 onPortStateChange: function(event) {
170 onMIDIMessage: function(message) {
171 var port = message.target;
172 var data = message.data;
173 if (data[0] == 144) { /* touch on */
174 var sample_idx = NANOPAD_TOUCHS.indexOf(data[1]);
175 if (sample_idx != -1) {
176 this.onTouchOn(port, data, sample_idx);
181 onTouchOn: function(port, data, sample_idx) {}
185 // status bytes on channel 1
196 var device = function(outputName) {
197 this.current = midi.outputs[outputName];
200 // makes device visible inside of nested function defs
203 this._send = function(status, data) {
204 var messageArr = [status + (self.channel - 1)].concat(data);
205 console.log("sending " + messageArr + " to " + self.current.name);
206 self.current.send(messageArr);
210 this.ch = function(channel) {
211 self.channel = channel;
215 this.cc = function(b1, b2) {
216 return self._send(messages.cc, [b1, b2]);
219 this.on = function(b1, b2) {
220 return self._send(messages.on, [b1, b2]);
223 this.off = function(b1, b2) {
224 return self._send(messages.off, [b1, b2]);
227 this.pp = function(b1, b2) {
228 return self._send(messages.pp, [b1, b2]);
231 this.cp = function(b1) {
232 return self._send(messages.cp, [b1]);
235 this.pb = function(b1) {
244 this.pc = function(b1) {
245 return self._send(messages.pc, [b1]);
248 this.panic = function() {
249 return self.cc(123, 0)
252 this.rpn = function(b1, b2) {
253 return self.cc(101, b1 >> 7)
261 this.nrpn = function(b1, b2) {
262 return self.cc(99, b1 >> 7)
270 this.raw = function(data) {
271 console.log("sending raw data: " + data);
272 self.current.send(data);
276 this.toString = function() {
277 var s = "no connected devices";
278 if (typeof this.current != 'undefined') {
287 var nanofun = function() {
290 self.initAudio = function() {
291 self.sample_buffers = Array(16);
292 self.samples = Array(16);
293 self.audioCtx = new window.AudioContext();
294 self.gainNode = self.audioCtx.createGain();
295 self.gainNode.connect(self.audioCtx.destination);
298 self.initMIDI = function() {
299 if (navigator.requestMIDIAccess) {
300 navigator.requestMIDIAccess({sysex: true}).then(
301 function(midiAccess) { midi.onMIDISuccess(midiAccess); },
302 function(e) { midi.onMIDIFailure(e); }
307 self.initUI = function() {
308 var $nanopad = $('#nanopad');
310 $('.nanotouch input').on('change', function(ev) {
311 var nanotouch = $(this).parent();
312 var sample_idx = $nanopad.children().index(nanotouch);
313 var reader = new FileReader();
314 reader.onload = function(e) {
315 self.audioCtx.decodeAudioData(this.result, function(buffer) {
316 sample_buffers[sample_idx] = buffer;
317 $(nanotouch).find('span.duration').text(parseInt(buffer.duration) + 's');
318 $(nanotouch).removeClass('error').addClass('loaded');
320 $(nanotouch).find('span').text('');
321 $(nanotouch).removeClass('loaded').addClass('error');
324 reader.readAsArrayBuffer(this.files[0]);
325 $(nanotouch).find('span.name').text(this.files[0].name);
328 midi.onTouchOn = function(port, data, sample_idx) {
329 self.startSample(sample_idx);
332 $(document).keypress(function(ev) {
333 var sample_idx = KEYBOARD_CODES.indexOf(ev.key);
334 if (sample_idx != -1) {
335 self.startSample(sample_idx);
340 self.startSample = function(sample_idx) {
341 var sample_buffer = self.sample_buffers[sample_idx];
342 var nanotouch = $('.nanotouch')[sample_idx];
343 if (typeof(sample_buffer) != 'undefined') {
344 if (typeof(samples[sample_idx]) != 'undefined' && samples[sample_idx].context.state == 'running') {
345 self.samples[sample_idx].stop(0);
346 self.samples[sample_idx] = undefined;
348 var sample = self.audioCtx.createBufferSource();
349 self.samples[sample_idx] = sample;
351 sample.connect(gainNode);
352 sample.buffer = sample_buffer;
353 sample.onended = function() {
354 console.log('ended');
355 $(nanotouch).removeClass('playing');
356 self.samples[sample_idx] = undefined;
358 $(nanotouch).addClass('playing');
370 $(function() { nanofun(); });