]> git.0d.be Git - nanofun.git/blob - nanofun.js
8e6204d6b27f470e20d69b6d59c73ebb39dd42b7
[nanofun.git] / nanofun.js
1 var NANOPAD_TOUCHS = {
2   37:  0, 39:  1, 41:  2, 43:  3, 45:  4, 47:  5, 49:  6, 51:  7,
3   36:  8, 38:  9, 40: 10, 42: 11, 44: 12, 46: 13, 48: 14, 50: 15
4 };
5
6 var midi = {
7
8 onMIDISuccess: function(midiAccess) {
9     console.log('MIDI Access Object', midiAccess);
10     //console.log(this);
11
12     var self = this;
13     midiAccess.onstatechange = function(e) {
14         self.onMIDIAccessChange(e);
15     }
16     this.midiAccess = midiAccess;
17     this.inputs = {};
18     this.outputs = {};
19
20     this.initPorts();
21 },
22
23 initPorts: function() {
24     var self = this;
25
26     var inputs = this.midiAccess.inputs;
27     if (inputs.size > 0) {
28         inputs.forEach(
29             function(port, key) {
30                 //console.log(port);
31                 self.registerPort(port);
32             }
33         );
34     } else {
35         $("#midiinputs").append("<p>No connected inputs</p>");
36     }
37
38     var outputs = this.midiAccess.outputs;
39     if (outputs.size > 0) {
40         outputs.forEach(
41             function(port, key) {
42                 self.registerPort(port);
43                 self.renderPort(port);
44             }
45         );
46     } else {
47         $("#midioutputs").append("<p>No connected outputs</p>"); 
48     }
49 },
50
51 onMIDIAccessChange: function(e) {
52     console.log(e);
53     //console.log(this);
54     var port = e.port;
55     var portContainer = $("#midi" + port.type + "s");
56     if (portContainer.html().startsWith("<p>No connected")) {
57         portContainer.empty();
58     }
59
60     if (port.type == "input") {
61         if (this.inputs[port.name] === undefined) {
62             this.registerPort(port);
63         }
64     } else {
65         if (this.outputs[port.name] === undefined) {
66             this.registerPort(port);
67         }
68     }
69
70     this.renderPort(port);
71 },
72
73 renderPort: function(port) {
74     if (port.state == "connected") {
75       $("#midi" + port.type + "s").append(port.name);
76     }
77 },
78
79 registerPort: function(port) {
80     var self = this;
81     if (port.type == "input") {
82         this.inputs[port.name] = port;
83         port.onmidimessage = function(m) { self.onMIDIMessage(m); };
84     } else {
85         this.outputs[port.name] = port;
86     }
87
88     port.onstatechange = function(e) { self.onPortStateChange(e); };
89 },
90
91 onMIDIFailure: function(e) {
92     alert("No access to MIDI devices or your browser doesn't support WebMIDI API. Please use WebMIDIAPIShim " + e);
93 },
94
95 onPortStateChange: function(event) {
96   console.log(event);
97 },
98
99 onMIDIMessage: function(message) {
100     var port = message.target;
101     var data = message.data;
102     if (data[0] == 144) { /* touch on */
103       var sample_idx = NANOPAD_TOUCHS[data[1]];
104       this.onTouchOn(port, data, sample_idx);
105     }
106 },
107
108 onTouchOn: function(port, data, sample_idx) {}
109
110 };
111
112 // status bytes on channel 1
113 var messages = {
114     off: 128,
115     on: 144,
116     pp: 160,
117     cc: 176,
118     pc: 192,
119     cp: 208,
120     pb: 224
121 }
122
123 var device = function(outputName) {
124    this.current = midi.outputs[outputName];
125    this.channel = 1;
126
127    // makes device visible inside of nested function defs
128    var self = this;
129
130    this._send = function(status, data) {
131     var messageArr = [status + (self.channel - 1)].concat(data);
132     console.log("sending " + messageArr + " to " + self.current.name);
133     self.current.send(messageArr);
134     return self;
135    }
136
137    this.ch = function(channel) {
138       self.channel = channel;
139       return self;
140    }
141
142    this.cc = function(b1, b2) {
143     return self._send(messages.cc, [b1, b2]);
144    }
145
146    this.on = function(b1, b2) {
147     return self._send(messages.on, [b1, b2]);
148    }
149
150    this.off = function(b1, b2) {
151     return self._send(messages.off, [b1, b2]);
152    }
153
154    this.pp = function(b1, b2) {
155     return self._send(messages.pp, [b1, b2]);
156    }
157
158    this.cp = function(b1) {
159     return self._send(messages.cp, [b1]);
160    }
161
162    this.pb = function(b1) {
163     return self._send(
164         messages.pb, [
165             b1 & 127,
166             b1 >> 7
167         ]
168     );
169    }
170
171    this.pc = function(b1) {
172     return self._send(messages.pc, [b1]);
173    }
174
175    this.panic = function() {
176     return self.cc(123, 0)
177    }
178
179    this.rpn = function(b1, b2) {
180     return self.cc(101, b1 >> 7)
181         .cc(100, b1 & 127)
182         .cc(6, b2 >> 7)
183         .cc(38, b2 & 127)
184         .cc(101, 127)
185         .cc(100, 127);
186    }
187
188    this.nrpn = function(b1, b2) {
189     return self.cc(99, b1 >> 7)
190         .cc(98, b1 & 127)
191         .cc(6, b2 >> 7)
192         .cc(38, b2 & 127)
193         .cc(101, 127)
194         .cc(100, 127);
195    }
196
197    this.raw = function(data) {
198     console.log("sending raw data: " + data);
199     self.current.send(data);
200     return self;
201    }
202
203    this.toString = function() {
204     var s = "no connected devices";
205     if (typeof this.current != 'undefined') {
206         s = "";
207     }
208     return s;
209    }
210
211    return this;
212 };
213
214 var nanofun = function() {
215   var self = this;
216
217   self.initAudio = function() {
218     self.sample_buffers = Array(16);
219     self.samples = Array(16);
220     self.audioCtx = new window.AudioContext();
221     self.gainNode = self.audioCtx.createGain();
222     self.gainNode.connect(self.audioCtx.destination);
223   }
224
225   self.initMIDI = function() {
226     if (navigator.requestMIDIAccess) {
227       navigator.requestMIDIAccess({sysex: true}).then(
228         function(midiAccess) { midi.onMIDISuccess(midiAccess); },
229         function(e) { midi.onMIDIFailure(e); }
230       );
231     }
232   }
233
234   self.initUI = function() {
235     var $nanopad = $('#nanopad');
236
237     $('.nanotouch input').on('change', function(ev) {
238       var nanotouch = $(this).parent();
239       var sample_idx = $nanopad.children().index(nanotouch);
240       var reader = new FileReader();
241       reader.onload = function(e) {
242         self.audioCtx.decodeAudioData(this.result, function(buffer) {
243           sample_buffers[sample_idx] = buffer;
244           $(nanotouch).find('span.duration').text(parseInt(buffer.duration) + 's');
245           $(nanotouch).removeClass('error').addClass('loaded');
246         }, function(e) {
247           $(nanotouch).find('span').text('');
248           $(nanotouch).removeClass('loaded').addClass('error');
249         });
250       }
251       reader.readAsArrayBuffer(this.files[0]);
252       $(nanotouch).find('span.name').text(this.files[0].name);
253     });
254
255     midi.onTouchOn = function(port, data, sample_idx) {
256       var sample_buffer = self.sample_buffers[sample_idx];
257       var nanotouch = $('.nanotouch')[sample_idx];
258       if (typeof(sample_buffer) != 'undefined') {
259         if (typeof(samples[sample_idx]) != 'undefined' && samples[sample_idx].context.state == 'running') {
260           self.samples[sample_idx].stop(0);
261           self.samples[sample_idx] = undefined;
262         } else {
263           var sample = self.audioCtx.createBufferSource();
264           self.samples[sample_idx] = sample;
265           sample.loop = false;
266           sample.connect(gainNode);
267           sample.buffer = sample_buffer;
268           sample.onended = function() {
269             console.log('ended');
270             $(nanotouch).removeClass('playing');
271             self.samples[sample_idx] = undefined;
272           }
273           $(nanotouch).addClass('playing');
274           sample.start(0);
275         }
276       }
277     }
278   }
279
280   self.initAudio();
281   self.initMIDI();
282   self.initUI();
283
284 }
285
286 $(function() { nanofun(); });