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