Add ry to strip model
[SugarCubes.git] / _MIDI.pde
CommitLineData
d6ac1ee8
MS
1/**
2 * DOUBLE BLACK DIAMOND DOUBLE BLACK DIAMOND
3 *
4 * //\\ //\\ //\\ //\\
5 * ///\\\ ///\\\ ///\\\ ///\\\
6 * \\\/// \\\/// \\\/// \\\///
7 * \\// \\// \\// \\//
8 *
9 * EXPERTS ONLY!! EXPERTS ONLY!!
10 *
11 * This file defines the MIDI mapping interfaces. This shouldn't
12 * need editing unless you're adding top level support for a
13 * specific MIDI device of some sort. Generally, all MIDI devices
14 * will just work with the default configuration, and you can
15 * set your SCPattern class to respond to the controllers that you
16 * care about.
17 */
18
19interface MidiEngineListener {
20 public void onFocusedDeck(int deckIndex);
21}
22
23class MidiEngine {
24
25 private final List<MidiEngineListener> listeners = new ArrayList<MidiEngineListener>();
26 private final List<SCMidiInput> midiControllers = new ArrayList<SCMidiInput>();
27
28 public MidiEngine addListener(MidiEngineListener l) {
29 listeners.add(l);
30 return this;
31 }
32
33 public MidiEngine removeListener(MidiEngineListener l) {
34 listeners.remove(l);
35 return this;
36 }
37
38 private SCMidiInput midiQwertyKeys;
39 private SCMidiInput midiQwertyAPC;
40
41 private int activeDeckIndex = 0;
42
43 public MidiEngine() {
44
45 midiControllers.add(midiQwertyKeys = new SCMidiInput(SCMidiInput.KEYS));
46 midiControllers.add(midiQwertyAPC = new SCMidiInput(SCMidiInput.APC));
47 for (MidiInputDevice device : RWMidi.getInputDevices()) {
48 if (device.getName().contains("APC")) {
49 midiControllers.add(new APC40MidiInput(device).setEnabled(true));
50 } else if (device.getName().contains("SLIDER/KNOB KORG")) {
51 midiControllers.add(new KorgNanoKontrolMidiInput(device).setEnabled(true));
52 } else {
4214e9a2 53 boolean enabled = device.getName().contains("KEYBOARD KORG") || device.getName().contains("Bus 1 Apple");
cc8b3f82 54 midiControllers.add(new SCMidiInput(device).setEnabled(enabled));
d6ac1ee8
MS
55 }
56 }
57 }
58
59 public List<SCMidiInput> getControllers() {
60 return this.midiControllers;
61 }
62
63 public MidiEngine setFocusedDeck(int deckIndex) {
64 if (this.activeDeckIndex != deckIndex) {
65 this.activeDeckIndex = deckIndex;
66 for (MidiEngineListener listener : listeners) {
67 listener.onFocusedDeck(deckIndex);
68 }
69 }
70 return this;
71 }
72
73 public Engine.Deck getFocusedDeck() {
74 return lx.engine.getDeck(activeDeckIndex);
75 }
76
77 public boolean isQwertyEnabled() {
78 return midiQwertyKeys.isEnabled() || midiQwertyAPC.isEnabled();
79 }
80}
81
82public interface SCMidiInputListener {
83 public void onEnabled(SCMidiInput controller, boolean enabled);
84}
85
86public class SCMidiInput extends AbstractScrollItem {
87
88 public static final int MIDI = 0;
89 public static final int KEYS = 1;
90 public static final int APC = 2;
91
92 private boolean enabled = false;
93 private final String name;
94 private final int mode;
95 private int octaveShift = 0;
96
97 class NoteMeta {
98 int channel;
99 int number;
100 NoteMeta(int channel, int number) {
101 this.channel = channel;
102 this.number = number;
103 }
104 }
105
106 final Map<Character, NoteMeta> keyToNote = new HashMap<Character, NoteMeta>();
107
108 final List<SCMidiInputListener> listeners = new ArrayList<SCMidiInputListener>();
109
110 public SCMidiInput addListener(SCMidiInputListener l) {
111 listeners.add(l);
112 return this;
113 }
114
115 public SCMidiInput removeListener(SCMidiInputListener l) {
116 listeners.remove(l);
117 return this;
118 }
119
120 SCMidiInput(MidiInputDevice d) {
121 mode = MIDI;
122 d.createInput(this);
123 name = d.getName().replace("Unknown vendor","");
124 }
125
126 SCMidiInput(int mode) {
127 this.mode = mode;
128 switch (mode) {
129 case APC:
130 name = "QWERTY (APC Mode)";
131 mapAPC();
132 break;
133 default:
134 case KEYS:
135 name = "QWERTY (Key Mode)";
136 mapKeys();
137 break;
138 }
139 }
140
141 private void mapAPC() {
142 mapNote('1', 0, 53);
143 mapNote('2', 1, 53);
144 mapNote('3', 2, 53);
145 mapNote('4', 3, 53);
146 mapNote('5', 4, 53);
147 mapNote('6', 5, 53);
148 mapNote('q', 0, 54);
149 mapNote('w', 1, 54);
150 mapNote('e', 2, 54);
151 mapNote('r', 3, 54);
152 mapNote('t', 4, 54);
153 mapNote('y', 5, 54);
154 mapNote('a', 0, 55);
155 mapNote('s', 1, 55);
156 mapNote('d', 2, 55);
157 mapNote('f', 3, 55);
158 mapNote('g', 4, 55);
159 mapNote('h', 5, 55);
160 mapNote('z', 0, 56);
161 mapNote('x', 1, 56);
162 mapNote('c', 2, 56);
163 mapNote('v', 3, 56);
164 mapNote('b', 4, 56);
165 mapNote('n', 5, 56);
166 registerKeyEvent(this);
167 }
168
169 private void mapKeys() {
170 int note = 48;
171 mapNote('a', 1, note++);
172 mapNote('w', 1, note++);
173 mapNote('s', 1, note++);
174 mapNote('e', 1, note++);
175 mapNote('d', 1, note++);
176 mapNote('f', 1, note++);
177 mapNote('t', 1, note++);
178 mapNote('g', 1, note++);
179 mapNote('y', 1, note++);
180 mapNote('h', 1, note++);
181 mapNote('u', 1, note++);
182 mapNote('j', 1, note++);
183 mapNote('k', 1, note++);
184 mapNote('o', 1, note++);
185 mapNote('l', 1, note++);
186 registerKeyEvent(this);
187 }
188
189 void mapNote(char ch, int channel, int number) {
190 keyToNote.put(ch, new NoteMeta(channel, number));
191 }
192
193 public String getLabel() {
194 return name;
195 }
196
197 public void keyEvent(KeyEvent e) {
198 if (!enabled) {
199 return;
200 }
201 char c = Character.toLowerCase(e.getKeyChar());
202 NoteMeta nm = keyToNote.get(c);
203 if (nm != null) {
204 switch (e.getID()) {
205 case KeyEvent.KEY_PRESSED:
206 noteOnReceived(new Note(Note.NOTE_ON, nm.channel, nm.number + octaveShift*12, 127));
207 break;
208 case KeyEvent.KEY_RELEASED:
209 noteOffReceived(new Note(Note.NOTE_OFF, nm.channel, nm.number + octaveShift*12, 0));
210 break;
211 }
212 }
213 if ((mode == KEYS) && (e.getID() == KeyEvent.KEY_PRESSED)) {
214 switch (c) {
215 case 'z':
216 octaveShift = constrain(octaveShift-1, -4, 4);
217 break;
218 case 'x':
219 octaveShift = constrain(octaveShift+1, -4, 4);
220 break;
221 }
222 }
223 }
224
225 public boolean isEnabled() {
226 return enabled;
227 }
228
229 public boolean isSelected() {
230 return enabled;
231 }
232
233 public void onMousePressed() {
234 setEnabled(!enabled);
235 }
236
237 public SCMidiInput setEnabled(boolean enabled) {
238 if (enabled != this.enabled) {
239 this.enabled = enabled;
240 for (SCMidiInputListener l : listeners) {
241 l.onEnabled(this, enabled);
242 }
243 }
244 return this;
245 }
246
247 protected SCPattern getFocusedPattern() {
248 return (SCPattern) midiEngine.getFocusedDeck().getActivePattern();
249 }
250
251 private boolean logMidi() {
252 return (uiMidi != null) && uiMidi.logMidi();
253 }
254
255 final void programChangeReceived(ProgramChange pc) {
256 if (!enabled) {
257 return;
258 }
259 if (logMidi()) {
260 println(getLabel() + " :: Program Change :: " + pc.getNumber());
261 }
262 handleProgramChange(pc);
263 }
264
265 final void controllerChangeReceived(rwmidi.Controller cc) {
266 if (!enabled) {
267 return;
268 }
269 if (logMidi()) {
270 println(getLabel() + " :: Controller :: " + cc.getCC() + ":" + cc.getValue());
271 }
272 if (!getFocusedPattern().controllerChangeReceived(cc)) {
273 handleControllerChange(cc);
274 }
275 }
276
277 final void noteOnReceived(Note note) {
278 if (!enabled) {
279 return;
280 }
281 if (logMidi()) {
282 println(getLabel() + " :: Note On :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
283 }
284 if (!getFocusedPattern().noteOnReceived(note)) {
285 handleNoteOn(note);
286 }
287 }
288
289 final void noteOffReceived(Note note) {
290 if (!enabled) {
291 return;
292 }
293 if (logMidi()) {
294 println(getLabel() + " :: Note Off :: " + note.getChannel() + ":" + note.getPitch() + ":" + note.getVelocity());
295 }
296 if (!getFocusedPattern().noteOffReceived(note)) {
297 handleNoteOff(note);
298 }
299 }
300
301 // Subclasses may implement these to map top-level functionality
302 protected void handleProgramChange(ProgramChange pc) {
303 }
304 protected void handleControllerChange(rwmidi.Controller cc) {
305 }
306 protected void handleNoteOn(Note note) {
307 }
308 protected void handleNoteOff(Note note) {
309 }
310}
311
312public class APC40MidiInput extends SCMidiInput {
313
314 private boolean shiftOn = false;
315 private LXEffect releaseEffect = null;
316
317 APC40MidiInput(MidiInputDevice d) {
318 super(d);
319 }
320
321 protected void handleControllerChange(rwmidi.Controller cc) {
322 int number = cc.getCC();
323 switch (number) {
324 // Crossfader
325 case 15:
326 lx.engine.getDeck(1).getCrossfader().setValue(cc.getValue() / 127.);
327 break;
328 }
329
330 int parameterIndex = -1;
331 if (number >= 48 && number <= 55) {
332 parameterIndex = number - 48;
333 } else if (number >= 16 && number <= 19) {
334 parameterIndex = 8 + (number-16);
335 }
336 if (parameterIndex >= 0) {
337 List<LXParameter> parameters = getFocusedPattern().getParameters();
338 if (parameterIndex < parameters.size()) {
339 parameters.get(parameterIndex).setValue(cc.getValue() / 127.);
340 }
341 }
342
343 if (number >= 20 && number <= 23) {
344 int effectIndex = number - 20;
345 List<LXParameter> parameters = glucose.getSelectedEffect().getParameters();
346 if (effectIndex < parameters.size()) {
347 parameters.get(effectIndex).setValue(cc.getValue() / 127.);
348 }
349 }
350 }
351
3661fcee 352
353
354 private double Tap1 = 0;
355 private double getNow() { return millis() + 1000*second() + 60*1000*minute() + 3600*1000*hour(); }
356 private boolean dbtwn (double a,double b,double c) { return a >= b && a <= c; }
357
d6ac1ee8 358 protected void handleNoteOn(Note note) {
3661fcee 359 int nPitch = note.getPitch(), nChan = note.getChannel();
360 switch (nPitch) {
361
362 case 82: EFF_boom .trigger(); break; // BOOM!
363 case 83: EFF_flash .trigger(); break; // Flash
364
365 case 90: lx.tempo.trigger(); Tap1 = getNow(); break; // dan's dirty tapping mechanism
af83f61c 366
367 case 91: // play
368 case 95: // bank
369 midiEngine.setFocusedDeck(0);
370 break;
371
372 case 93: // rec
d6ac1ee8
MS
373 case 94: // right bank
374 midiEngine.setFocusedDeck(1);
375 break;
af83f61c 376
d6ac1ee8
MS
377 case 96: // up bank
378 if (shiftOn) {
379 glucose.incrementSelectedEffectBy(1);
380 } else {
381 midiEngine.getFocusedDeck().goNext();
382 }
383 break;
384 case 97: // down bank
385 if (shiftOn) {
386 glucose.incrementSelectedEffectBy(-1);
387 } else {
388 midiEngine.getFocusedDeck().goPrev();
389 }
390 break;
391
392 case 98: // shift
393 shiftOn = true;
394 break;
395
396 case 99: // tap tempo
397 lx.tempo.tap();
398 break;
399 case 100: // nudge+
400 lx.tempo.setBpm(lx.tempo.bpm() + (shiftOn ? 1 : .1));
401 break;
402 case 101: // nudge-
403 lx.tempo.setBpm(lx.tempo.bpm() - (shiftOn ? 1 : .1));
404 break;
405
af83f61c 406 case 62: // Detail View
d6ac1ee8
MS
407 releaseEffect = glucose.getSelectedEffect();
408 if (releaseEffect.isMomentary()) {
409 releaseEffect.enable();
410 } else {
411 releaseEffect.toggle();
412 }
413 break;
414
af83f61c 415 case 63: // rec quantize
d6ac1ee8
MS
416 glucose.getSelectedEffect().disable();
417 break;
418 }
419 }
420
421 protected void handleNoteOff(Note note) {
3661fcee 422 int nPitch = note.getPitch(), nChan = note.getChannel();
423 switch (nPitch) {
424 case 90:
425 if (dbtwn(getNow() - Tap1,5000,300*1000)) { // hackish tapping mechanism
426 double bpm = 32.*60000./(getNow()-Tap1);
427 while (bpm < 20) bpm*=2;
428 while (bpm > 40) bpm/=2;
429 lx.tempo.setBpm(bpm); lx.tempo.trigger(); Tap1=0; println("Tap Set - " + bpm + " bpm");
430 }
431 break;
432
af83f61c 433
434 case 63: // rec quantize
d6ac1ee8
MS
435 if (releaseEffect != null) {
436 if (releaseEffect.isMomentary()) {
437 releaseEffect.disable();
438 }
439 }
440 break;
af83f61c 441
d6ac1ee8
MS
442 case 98: // shift
443 shiftOn = false;
444 break;
445 }
446 }
447}
448
449class KorgNanoKontrolMidiInput extends SCMidiInput {
450
451 KorgNanoKontrolMidiInput(MidiInputDevice d) {
452 super(d);
453 }
454
455 protected void handleControllerChange(rwmidi.Controller cc) {
456 int number = cc.getCC();
457 if (number >= 16 && number <= 23) {
458 int parameterIndex = number - 16;
459 List<LXParameter> parameters = getFocusedPattern().getParameters();
460 if (parameterIndex < parameters.size()) {
461 parameters.get(parameterIndex).setValue(cc.getValue() / 127.);
462 }
463 }
464
465 if (cc.getValue() == 127) {
466 switch (number) {
467 // Left track
468 case 58:
469 midiEngine.setFocusedDeck(0);
470 break;
471 // Right track
472 case 59:
473 midiEngine.setFocusedDeck(1);
474 break;
475 // Left chevron
476 case 43:
477 midiEngine.getFocusedDeck().goPrev();
478 break;
479 // Right chevron
480 case 44:
481 midiEngine.getFocusedDeck().goNext();
482 break;
483 }
484 }
485 }
486}
487