-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 NANOPAD_TOUCHS = Array(37, 39, 41, 43, 45, 47, 49, 51,
+ 36, 38, 40, 42, 44, 46, 48, 50);
+
+/* on French/Belgian keyboards, emulate pad touches with keypresses */
+var KEYBOARD_CODES = Array('a', 'z', 'e', 'r', 't', 'u', 'i', 'o',
+ 'q', 's', 'd', 'f', 'g', 'h', 'j', 'k');
var midi = {
},
onMIDIAccessChange: function(e) {
- console.log(e);
+ console.log('on midi access change', e);
//console.log(this);
var port = e.port;
var portContainer = $("#midi" + port.type + "s");
portContainer.empty();
}
- if (port.type == "input") {
- if (this.inputs[port.name] === undefined) {
- this.registerPort(port);
- }
+ if (port.state == "disconnected") {
+ if (port.type == "input") {
+ this.inputs[port.name] = undefined;
+ } else {
+ this.outputs[port.name] = undefined;
+ }
} else {
- if (this.outputs[port.name] === undefined) {
- this.registerPort(port);
- }
+ 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);
}
-
- this.renderPort(port);
},
renderPort: function(port) {
port.onmidimessage = function(m) { self.onMIDIMessage(m); };
} else {
this.outputs[port.name] = port;
+
+ if (port.name == 'nanoKONTROL2 MIDI 1') {
+ /* turn "external leds" mode on.
+ *
+ * The sysex dump has been recorded from the official Korg kontrol
+ * editor by the Overtone project, original code at:
+ * https://github.com/overtone/overtone/blob/master/src/overtone/device/midi/nanoKONTROL2.clj */
+ device("nanoKONTROL2 MIDI 1").raw([240, 126, 127, 6, 1, 247]);
+ device("nanoKONTROL2 MIDI 1").raw([240, 66, 64, 0, 1, 19, 0, 31, 18, 0, 247]);
+ device("nanoKONTROL2 MIDI 1").raw([240, 126, 127, 6, 1, 247]);
+
+ device("nanoKONTROL2 MIDI 1").raw([ 240, 66, 64, 0, 1, 19, 0, 127, 127,
+ 2, 3, 5, 64, 0, 0, 0, 1, 16, 1, 0, 0, 0, 0, 127, 0, 1, 0, 16,
+ 0, 0, 127, 0, 1, 0, 32, 0, 127, 0, 0, 1, 0, 48, 0, 127, 0, 0,
+ 1, 0, 64, 0, 127, 0, 16, 0, 1, 0, 1, 0, 127, 0, 1, 0, 0, 17, 0,
+ 127, 0, 1, 0, 0, 33, 0, 127, 0, 1, 0, 49, 0, 0, 127, 0, 1, 0,
+ 65, 0, 0, 127, 0, 16, 1, 0, 2, 0, 0, 127, 0, 1, 0, 18, 0, 127,
+ 0, 0, 1, 0, 34, 0, 127, 0, 0, 1, 0, 50, 0, 127, 0, 1, 0, 0, 66,
+ 0, 127, 0, 16, 1, 0, 0, 3, 0, 127, 0, 1, 0, 0, 19, 0, 127, 0,
+ 1, 0, 35, 0, 0, 127, 0, 1, 0, 51, 0, 0, 127, 0, 1, 0, 67, 0,
+ 127, 0, 0, 16, 1, 0, 4, 0, 127, 0, 0, 1, 0, 20, 0, 127, 0, 0,
+ 1, 0, 36, 0, 127, 0, 1, 0, 0, 52, 0, 127, 0, 1, 0, 0, 68, 0,
+ 127, 0, 16, 1, 0, 0, 5, 0, 127, 0, 1, 0, 21, 0, 0, 127, 0, 1,
+ 0, 37, 0, 0, 127, 0, 1, 0, 53, 0, 127, 0, 0, 1, 0, 69, 0, 127,
+ 0, 0, 16, 1, 0, 6, 0, 127, 0, 0, 1, 0, 22, 0, 127, 0, 1, 0, 0,
+ 38, 0, 127, 0, 1, 0, 0, 54, 0, 127, 0, 1, 0, 70, 0, 0, 127, 0,
+ 16, 1, 0, 7, 0, 0, 127, 0, 1, 0, 23, 0, 0, 127, 0, 1, 0, 39, 0,
+ 127, 0, 0, 1, 0, 55, 0, 127, 0, 0, 1, 0, 71, 0, 127, 0, 16, 0,
+ 1, 0, 58, 0, 127, 0, 1, 0, 0, 59, 0, 127, 0, 1, 0, 0, 46, 0,
+ 127, 0, 1, 0, 60, 0, 0, 127, 0, 1, 0, 61, 0, 0, 127, 0, 1, 0,
+ 62, 0, 127, 0, 0, 1, 0, 43, 0, 127, 0, 0, 1, 0, 44, 0, 127, 0,
+ 1, 0, 0, 42, 0, 127, 0, 1, 0, 0, 41, 0, 127, 0, 1, 0, 45, 0, 0,
+ 127, 0, 127, 127, 127, 127, 0, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 247]);
+
+ device("nanoKONTROL2 MIDI 1").raw([240, 126, 127, 6, 1, 247]);
+ device("nanoKONTROL2 MIDI 1").raw([240, 66, 64, 0, 1, 19, 0, 31, 17, 0, 247]);
+
+ function on(note) { device("nanoKONTROL2 MIDI 1").cc(note, 127); }
+ function off(note) { device("nanoKONTROL2 MIDI 1").cc(note, 0); }
+ var leds = Array(43, 44, 42, 41, 45);
+ for (var i=0; i<8; i++) {
+ leds.push(64+i);
+ leds.push(48+i);
+ leds.push(32+i);
+ i += 1;
+ leds.push(32+i);
+ leds.push(48+i);
+ leds.push(64+i);
+ }
+ console.log(leds);
+ on(leds[0]);
+ on(leds[1]);
+ on(leds[2]);
+ var led_idx = 2;
+ var interval_id = setInterval(function() {
+ if (led_idx < leds.length) {
+ on(leds[led_idx+1]);
+ }
+ off(leds[led_idx-2]);
+ if (led_idx-1 == leds.length) {
+ clearInterval(interval_id);
+ }
+ led_idx += 1;
+ }, 50);
+ }
}
port.onstatechange = function(e) { self.onPortStateChange(e); };
onMIDIMessage: function(message) {
var port = message.target;
var data = message.data;
+ console.log(message);
if (data[0] == 144) { /* touch on */
- var sample_idx = NANOPAD_TOUCHS[data[1]];
- this.onTouchOn(port, data, sample_idx);
+ var sample_idx = NANOPAD_TOUCHS.indexOf(data[1]);
+ if (sample_idx != -1) {
+ this.onTouchOn(port, data, sample_idx);
+ }
+ }
+ if (data[0] == 176) { /* control change */
+ this.onControlChange(port, data, data[1], data[2]);
}
},
-onTouchOn: function(port, data, sample_idx) {}
+onTouchOn: function(port, data, sample_idx) {},
+onControlChange: function(port, data, number, value) {}
};
return this;
};
-$(function() {
- var $nanopad = $('#nanopad');
- var sample_buffers = Array(16);
- var samples = Array(16);
- var audioCtx = new window.AudioContext();
- var gainNode = audioCtx.createGain();
- gainNode.connect(audioCtx.destination);
-
- $('.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) {
- 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');
- });
+var nanofun = function() {
+ var self = this;
+
+ self.initAudio = function() {
+ self.sample_buffers = Array(16);
+ self.samples = Array(16);
+ self.audioCtx = new window.AudioContext();
+ self.touchGainNodes = Array(16);
+ self.masterGainNode = self.audioCtx.createGain();
+ for (var i=0; i<16; i++) {
+ self.touchGainNodes[i] = self.audioCtx.createGain();
+ self.touchGainNodes[i].connect(self.masterGainNode);
}
- reader.readAsArrayBuffer(this.files[0]);
- $(nanotouch).find('span.name').text(this.files[0].name);
- });
+ self.masterGainNode.connect(self.audioCtx.destination);
+ }
- midi.onTouchOn = function(port, data, sample_idx) {
- var sample_buffer = sample_buffers[sample_idx];
+ 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');
+ var $nanotouch = $('.nanotouch');
+ for (var i=1; i<16; i++) {
+ var $new_touch = $nanotouch.clone();
+ $new_touch.attr('data-touch', i);
+ $new_touch.appendTo($nanopad);
+ }
+
+ $('.nanotouch input[type=file]').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) {
+ self.startSample(sample_idx);
+ }
+
+ midi.onControlChange = function(port, data, control, value) {
+ if (control > 7 && control < 16) return; /* range between sliders and pots */
+ if (control > 23) return; /* after pots */
+ if (control >= 16) { control -= 8; }
+ $('[data-touch=' + control + '] .touch-gain').val(value).trigger('change');
+ }
+
+ $(document).keypress(function(ev) {
+ var sample_idx = KEYBOARD_CODES.indexOf(ev.key);
+ if (sample_idx != -1) {
+ self.startSample(sample_idx);
+ }
+ });
+
+ $('#master-gain').on('change', function() {
+ var fraction = parseInt(this.value) / parseInt(127);
+ self.masterGainNode.gain.value = fraction * fraction;
+ });
+
+ $('.touch-gain').on('change', function() {
+ var fraction = parseInt(this.value) / parseInt(127);
+ var touchIdx = parseInt($(this).parent().data('touch'));
+ self.touchGainNodes[touchIdx].gain.value = fraction * fraction;
+ });
+ }
+
+ self.startSample = function(sample_idx) {
+ var sample_buffer = self.sample_buffers[sample_idx];
var nanotouch = $('.nanotouch')[sample_idx];
if (typeof(sample_buffer) != 'undefined') {
- console.log(samples[sample_idx]);
if (typeof(samples[sample_idx]) != 'undefined' && samples[sample_idx].context.state == 'running') {
- samples[sample_idx].stop(0);
- samples[sample_idx] = undefined;
+ self.samples[sample_idx].stop(0);
+ self.samples[sample_idx] = undefined;
} else {
- var sample = audioCtx.createBufferSource();
- samples[sample_idx] = sample;
+ var sample = self.audioCtx.createBufferSource();
+ var gainNode = self.touchGainNodes[sample_idx];
+ self.samples[sample_idx] = sample;
sample.loop = false;
sample.connect(gainNode);
sample.buffer = sample_buffer;
sample.onended = function() {
console.log('ended');
$(nanotouch).removeClass('playing');
- samples[sample_idx] = undefined;
+ self.samples[sample_idx] = undefined;
}
$(nanotouch).addClass('playing');
sample.start(0);
}
}
- if (navigator.requestMIDIAccess) {
- navigator.requestMIDIAccess({
- sysex: true
- }).then(
- function(midiAccess) { midi.onMIDISuccess(midiAccess); },
- function(e) { midi.onMIDIFailure(e); }
- );
- }
+ self.initAudio();
+ self.initMIDI();
+ self.initUI();
+
+}
-});
+$(function() { nanofun(); });