]> git.0d.be Git - nanofun.git/blob - nanofun.js
rewrite nanopad touch mapping as a simple array
[nanofun.git] / nanofun.js
1 var NANOPAD_TOUCHS = Array(37, 39, 41, 43, 45, 47, 49, 51,
2                            36, 38, 40, 42, 44, 46, 48, 50);
3
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');
7
8 var midi = {
9
10 onMIDISuccess: function(midiAccess) {
11     console.log('MIDI Access Object', midiAccess);
12     //console.log(this);
13
14     var self = this;
15     midiAccess.onstatechange = function(e) {
16         self.onMIDIAccessChange(e);
17     }
18     this.midiAccess = midiAccess;
19     this.inputs = {};
20     this.outputs = {};
21
22     this.initPorts();
23 },
24
25 initPorts: function() {
26     var self = this;
27
28     var inputs = this.midiAccess.inputs;
29     if (inputs.size > 0) {
30         inputs.forEach(
31             function(port, key) {
32                 //console.log(port);
33                 self.registerPort(port);
34             }
35         );
36     } else {
37         $("#midiinputs").append("<p>No connected inputs</p>");
38     }
39
40     var outputs = this.midiAccess.outputs;
41     if (outputs.size > 0) {
42         outputs.forEach(
43             function(port, key) {
44                 self.registerPort(port);
45                 self.renderPort(port);
46             }
47         );
48     } else {
49         $("#midioutputs").append("<p>No connected outputs</p>"); 
50     }
51 },
52
53 onMIDIAccessChange: function(e) {
54     console.log(e);
55     //console.log(this);
56     var port = e.port;
57     var portContainer = $("#midi" + port.type + "s");
58     if (portContainer.html().startsWith("<p>No connected")) {
59         portContainer.empty();
60     }
61
62     if (port.type == "input") {
63         if (this.inputs[port.name] === undefined) {
64             this.registerPort(port);
65         }
66     } else {
67         if (this.outputs[port.name] === undefined) {
68             this.registerPort(port);
69         }
70     }
71
72     this.renderPort(port);
73 },
74
75 renderPort: function(port) {
76     if (port.state == "connected") {
77       $("#midi" + port.type + "s").append(port.name);
78     }
79 },
80
81 registerPort: function(port) {
82     var self = this;
83     if (port.type == "input") {
84         this.inputs[port.name] = port;
85         port.onmidimessage = function(m) { self.onMIDIMessage(m); };
86     } else {
87         this.outputs[port.name] = port;
88     }
89
90     port.onstatechange = function(e) { self.onPortStateChange(e); };
91 },
92
93 onMIDIFailure: function(e) {
94     alert("No access to MIDI devices or your browser doesn't support WebMIDI API. Please use WebMIDIAPIShim " + e);
95 },
96
97 onPortStateChange: function(event) {
98   console.log(event);
99 },
100
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);
108       }
109     }
110 },
111
112 onTouchOn: function(port, data, sample_idx) {}
113
114 };
115
116 // status bytes on channel 1
117 var messages = {
118     off: 128,
119     on: 144,
120     pp: 160,
121     cc: 176,
122     pc: 192,
123     cp: 208,
124     pb: 224
125 }
126
127 var device = function(outputName) {
128    this.current = midi.outputs[outputName];
129    this.channel = 1;
130
131    // makes device visible inside of nested function defs
132    var self = this;
133
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);
138     return self;
139    }
140
141    this.ch = function(channel) {
142       self.channel = channel;
143       return self;
144    }
145
146    this.cc = function(b1, b2) {
147     return self._send(messages.cc, [b1, b2]);
148    }
149
150    this.on = function(b1, b2) {
151     return self._send(messages.on, [b1, b2]);
152    }
153
154    this.off = function(b1, b2) {
155     return self._send(messages.off, [b1, b2]);
156    }
157
158    this.pp = function(b1, b2) {
159     return self._send(messages.pp, [b1, b2]);
160    }
161
162    this.cp = function(b1) {
163     return self._send(messages.cp, [b1]);
164    }
165
166    this.pb = function(b1) {
167     return self._send(
168         messages.pb, [
169             b1 & 127,
170             b1 >> 7
171         ]
172     );
173    }
174
175    this.pc = function(b1) {
176     return self._send(messages.pc, [b1]);
177    }
178
179    this.panic = function() {
180     return self.cc(123, 0)
181    }
182
183    this.rpn = function(b1, b2) {
184     return self.cc(101, b1 >> 7)
185         .cc(100, b1 & 127)
186         .cc(6, b2 >> 7)
187         .cc(38, b2 & 127)
188         .cc(101, 127)
189         .cc(100, 127);
190    }
191
192    this.nrpn = function(b1, b2) {
193     return self.cc(99, b1 >> 7)
194         .cc(98, b1 & 127)
195         .cc(6, b2 >> 7)
196         .cc(38, b2 & 127)
197         .cc(101, 127)
198         .cc(100, 127);
199    }
200
201    this.raw = function(data) {
202     console.log("sending raw data: " + data);
203     self.current.send(data);
204     return self;
205    }
206
207    this.toString = function() {
208     var s = "no connected devices";
209     if (typeof this.current != 'undefined') {
210         s = "";
211     }
212     return s;
213    }
214
215    return this;
216 };
217
218 var nanofun = function() {
219   var self = this;
220
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);
227   }
228
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); }
234       );
235     }
236   }
237
238   self.initUI = function() {
239     var $nanopad = $('#nanopad');
240
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');
250         }, function(e) {
251           $(nanotouch).find('span').text('');
252           $(nanotouch).removeClass('loaded').addClass('error');
253         });
254       }
255       reader.readAsArrayBuffer(this.files[0]);
256       $(nanotouch).find('span.name').text(this.files[0].name);
257     });
258
259     midi.onTouchOn = function(port, data, sample_idx) {
260       self.startSample(sample_idx);
261     }
262
263     $(document).keypress(function(ev) {
264       var sample_idx = KEYBOARD_CODES.indexOf(ev.key);
265       if (sample_idx != -1) {
266         self.startSample(sample_idx);
267       }
268     });
269   }
270
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;
278       } else {
279         var sample = self.audioCtx.createBufferSource();
280         self.samples[sample_idx] = sample;
281         sample.loop = false;
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;
288         }
289         $(nanotouch).addClass('playing');
290         sample.start(0);
291       }
292     }
293   }
294
295   self.initAudio();
296   self.initMIDI();
297   self.initUI();
298
299 }
300
301 $(function() { nanofun(); });