X-Git-Url: https://git.0d.be/?p=nanofun.git;a=blobdiff_plain;f=nanofun.js;h=b8dcaf96721bd3ad094f32bd6cd8a26b54cce0f5;hp=ffa07dd588dcfb3d8af7ba9cebc17138e365b4c5;hb=dd3d52c1f6e690673ed0f3668647cdc9163f5d6c;hpb=8dad243f729fe7294280db19b66ec059d652df61 diff --git a/nanofun.js b/nanofun.js index ffa07dd..b8dcaf9 100644 --- a/nanofun.js +++ b/nanofun.js @@ -2,7 +2,7 @@ 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', +var KEYBOARD_CODES = Array('a', 'z', 'e', 'r', 't', 'y', 'u', 'i', 'q', 's', 'd', 'f', 'g', 'h', 'j', 'k'); var midi = { @@ -51,25 +51,29 @@ initPorts: function() { }, 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"); - if (portContainer.html().startsWith("

No connected")) { - 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; + } + if (port.name == 'nanoPAD2 MIDI 1' || port.name == 'nanoPAD2 2 PAD') { $('#devices .nanopad').removeClass('on'); } + if (port.name == 'nanoKONTROL2 MIDI 1' || port.name == 'nanoKONTROL2 2 SLIDER/KNOB') { $('#devices .nanokontrol').removeClass('on'); } } 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); } + } + console.log('hello:', port.name); + if (port.name == 'nanoPAD2 MIDI 1' || port.name == 'nanoPAD2 2 PAD') { $('#devices .nanopad').addClass('on'); } + if (port.name == 'nanoKONTROL2 MIDI 1' || port.name == 'nanoKONTROL2 2 SLIDER/KNOB') { $('#devices .nanokontrol').addClass('on'); } + this.renderPort(port); } - - this.renderPort(port); }, renderPort: function(port) { @@ -85,6 +89,73 @@ registerPort: function(port) { port.onmidimessage = function(m) { self.onMIDIMessage(m); }; } else { this.outputs[port.name] = port; + + if (port.name == 'nanoKONTROL2 MIDI 1' || port.name == 'nanoKONTROL2 2 SLIDER/KNOB') { + var port_name = port.name; + /* 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(port_name).raw([240, 126, 127, 6, 1, 247]); + device(port_name).raw([240, 66, 64, 0, 1, 19, 0, 31, 18, 0, 247]); + device(port_name).raw([240, 126, 127, 6, 1, 247]); + + device(port_name).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(port_name).raw([240, 126, 127, 6, 1, 247]); + device(port_name).raw([240, 66, 64, 0, 1, 19, 0, 31, 17, 0, 247]); + + function on(note) { device(port_name).cc(note, 127); } + function off(note) { device(port_name).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); }; @@ -101,15 +172,20 @@ onPortStateChange: function(event) { 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.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) {} }; @@ -219,11 +295,34 @@ var nanofun = function() { var self = this; self.initAudio = function() { - self.sample_buffers = Array(16); - self.samples = Array(16); + self.sample_buffers = Array(NANOPAD_TOUCHS.length); + self.samples = Array(NANOPAD_TOUCHS.length); + self.sample_start_times = Array(NANOPAD_TOUCHS.length); self.audioCtx = new window.AudioContext(); - self.gainNode = self.audioCtx.createGain(); - self.gainNode.connect(self.audioCtx.destination); + self.touchGainNodes = Array(NANOPAD_TOUCHS.length); + self.masterGainNode = self.audioCtx.createGain(); + self.effectsGainNode = self.audioCtx.createGain(); + for (var i=0; i 7 && control < 16) return; /* range between sliders and pots */ + if (control >= 32 && control < 40) { /* "S" buttons */ + var nanotouch = $('.nanotouch')[control-32]; + if (value == 127) { + var checked = $(nanotouch).find('.loop input').prop('checked'); + if (checked) { + $(nanotouch).find('.loop input').prop('checked', false); + device("nanoKONTROL2 MIDI 1").cc(control, 0); + } else { + $(nanotouch).find('.loop input').prop('checked', true); + device("nanoKONTROL2 MIDI 1").cc(control, 127); + } + } + } + if (control > 23) return; /* after pots */ + if (control < 8) { + control += 8; /* sliders, control bottom pads (8-15) */ + } else { + control -= 16; /* pots, control top pads (0-7) */ + } + $('[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); } }); + + $('.effects input').on('change', function() { + var effects = $(this).prop('checked'); + var i = parseInt($(this).parents('.nanotouch').data('touch')); + if (effects) { + self.touchGainNodes[i].connect(self.effectsGainNode); + } else { + self.touchGainNodes[i].disconnect(self.effectsGainNode); + } + }); + + $('#master-gain').on('change', function() { + var fraction = parseInt(this.value) / parseInt(127); + var now = self.audioCtx.currentTime; + self.masterGainNode.gain.exponentialRampToValueAtTime(fraction * fraction, now + 0.015); + }); + + $('#delay').on('change', function() { + var value = this.value; + if (value == 0) { + self.delay.disconnect(); + } else { + var now = self.audioCtx.currentTime; + self.delay.delayTime.exponentialRampToValueAtTime(value, now + 0.015); + self.delay.connect(self.audioCtx.destination); + } + }); + + $('#feedback').on('change', function() { + var now = self.audioCtx.currentTime; + self.feedback.gain.exponentialRampToValueAtTime(this.value, now + 0.015); + }); + + $('#filter').on('change', function() { + self.filter.frequency.value = this.value; + }); + + $('.touch-gain').on('change', function() { + var fraction = parseInt(this.value) / parseInt(127); + var touchIdx = parseInt($(this).parent().data('touch')); + var now = self.audioCtx.currentTime; + self.touchGainNodes[touchIdx].gain.exponentialRampToValueAtTime(fraction * fraction, now + 0.015); + }); + + self.time_interval_id = setInterval(function() { + for (var i=0; i