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) {
57 var portContainer = $("#midi" + port.type + "s");
58 if (portContainer.html().startsWith("<p>No connected")) {
59 portContainer.empty();
62 if (port.type == "input") {
63 if (this.inputs[port.name] === undefined) {
64 this.registerPort(port);
67 if (this.outputs[port.name] === undefined) {
68 this.registerPort(port);
72 this.renderPort(port);
75 renderPort: function(port) {
76 if (port.state == "connected") {
77 $("#midi" + port.type + "s").append(port.name);
81 registerPort: function(port) {
83 if (port.type == "input") {
84 this.inputs[port.name] = port;
85 port.onmidimessage = function(m) { self.onMIDIMessage(m); };
87 this.outputs[port.name] = port;
90 port.onstatechange = function(e) { self.onPortStateChange(e); };
93 onMIDIFailure: function(e) {
94 alert("No access to MIDI devices or your browser doesn't support WebMIDI API. Please use WebMIDIAPIShim " + e);
97 onPortStateChange: function(event) {
101 onMIDIMessage: function(message) {
102 var port = message.target;
103 var data = message.data;
104 if (data[0] == 144) { /* touch on */
105 var sample_idx = NANOPAD_TOUCHS.indexOf(data[1]);
106 if (sample_idx != -1) {
107 this.onTouchOn(port, data, sample_idx);
112 onTouchOn: function(port, data, sample_idx) {}
116 // status bytes on channel 1
127 var device = function(outputName) {
128 this.current = midi.outputs[outputName];
131 // makes device visible inside of nested function defs
134 this._send = function(status, data) {
135 var messageArr = [status + (self.channel - 1)].concat(data);
136 console.log("sending " + messageArr + " to " + self.current.name);
137 self.current.send(messageArr);
141 this.ch = function(channel) {
142 self.channel = channel;
146 this.cc = function(b1, b2) {
147 return self._send(messages.cc, [b1, b2]);
150 this.on = function(b1, b2) {
151 return self._send(messages.on, [b1, b2]);
154 this.off = function(b1, b2) {
155 return self._send(messages.off, [b1, b2]);
158 this.pp = function(b1, b2) {
159 return self._send(messages.pp, [b1, b2]);
162 this.cp = function(b1) {
163 return self._send(messages.cp, [b1]);
166 this.pb = function(b1) {
175 this.pc = function(b1) {
176 return self._send(messages.pc, [b1]);
179 this.panic = function() {
180 return self.cc(123, 0)
183 this.rpn = function(b1, b2) {
184 return self.cc(101, b1 >> 7)
192 this.nrpn = function(b1, b2) {
193 return self.cc(99, b1 >> 7)
201 this.raw = function(data) {
202 console.log("sending raw data: " + data);
203 self.current.send(data);
207 this.toString = function() {
208 var s = "no connected devices";
209 if (typeof this.current != 'undefined') {
218 var nanofun = function() {
221 self.initAudio = function() {
222 self.sample_buffers = Array(16);
223 self.samples = Array(16);
224 self.audioCtx = new window.AudioContext();
225 self.gainNode = self.audioCtx.createGain();
226 self.gainNode.connect(self.audioCtx.destination);
229 self.initMIDI = function() {
230 if (navigator.requestMIDIAccess) {
231 navigator.requestMIDIAccess({sysex: true}).then(
232 function(midiAccess) { midi.onMIDISuccess(midiAccess); },
233 function(e) { midi.onMIDIFailure(e); }
238 self.initUI = function() {
239 var $nanopad = $('#nanopad');
241 $('.nanotouch input').on('change', function(ev) {
242 var nanotouch = $(this).parent();
243 var sample_idx = $nanopad.children().index(nanotouch);
244 var reader = new FileReader();
245 reader.onload = function(e) {
246 self.audioCtx.decodeAudioData(this.result, function(buffer) {
247 sample_buffers[sample_idx] = buffer;
248 $(nanotouch).find('span.duration').text(parseInt(buffer.duration) + 's');
249 $(nanotouch).removeClass('error').addClass('loaded');
251 $(nanotouch).find('span').text('');
252 $(nanotouch).removeClass('loaded').addClass('error');
255 reader.readAsArrayBuffer(this.files[0]);
256 $(nanotouch).find('span.name').text(this.files[0].name);
259 midi.onTouchOn = function(port, data, sample_idx) {
260 self.startSample(sample_idx);
263 $(document).keypress(function(ev) {
264 var sample_idx = KEYBOARD_CODES.indexOf(ev.key);
265 if (sample_idx != -1) {
266 self.startSample(sample_idx);
271 self.startSample = function(sample_idx) {
272 var sample_buffer = self.sample_buffers[sample_idx];
273 var nanotouch = $('.nanotouch')[sample_idx];
274 if (typeof(sample_buffer) != 'undefined') {
275 if (typeof(samples[sample_idx]) != 'undefined' && samples[sample_idx].context.state == 'running') {
276 self.samples[sample_idx].stop(0);
277 self.samples[sample_idx] = undefined;
279 var sample = self.audioCtx.createBufferSource();
280 self.samples[sample_idx] = sample;
282 sample.connect(gainNode);
283 sample.buffer = sample_buffer;
284 sample.onended = function() {
285 console.log('ended');
286 $(nanotouch).removeClass('playing');
287 self.samples[sample_idx] = undefined;
289 $(nanotouch).addClass('playing');
301 $(function() { nanofun(); });