1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 /*
3 Rosegarden
4 A sequencer and musical notation editor.
5 Copyright 2020 the Rosegarden development team.
6
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the
10 License, or (at your option) any later version. See the file
11 COPYING included with this distribution for more information.
12 */
13
14 #define RG_MODULE_STRING "[KorgNanoKontrol2]"
15
16 #include "KorgNanoKontrol2.h"
17
18 #include "base/AudioLevel.h"
19 #include "base/Composition.h"
20 #include "misc/Debug.h"
21 #include "base/Instrument.h"
22 #include "MappedEvent.h"
23 #include "base/QEvents.h"
24 #include "document/RosegardenDocument.h"
25 #include "gui/application/RosegardenMainWindow.h"
26 #include "base/Studio.h"
27 #include "base/Track.h"
28
29 #include <QCoreApplication>
30 #include <QEvent>
31
32 #include <vector>
33
34 namespace Rosegarden
35 {
36
37
KorgNanoKontrol2()38 KorgNanoKontrol2::KorgNanoKontrol2() :
39 m_page(0),
40 m_firstRefresh(true),
41 m_play(false),
42 m_record(false),
43 m_stop(false),
44 m_rewind(false),
45 m_fastForward(false),
46 m_cycle(false)
47 {
48 }
49
init()50 void KorgNanoKontrol2::init()
51 {
52 // Configure the device.
53
54 #if 0
55
56 // Confirm expected device.
57
58 // Send Inquiry Message Request.
59 ExternalController::sendSysExHex("7E7F0601");
60
61 std::string buffer;
62
63 // Get Device Inquiry Reply.
64 // We need a synchronous getSysEx() with 500msec(?) timeout.
65 // It also needs to stitch together multiple packets into one until EOX.
66 ExternalController::self()->getSysEx(buffer);
67
68 QByteArray byteArray(buffer.c_str());
69 RG_DEBUG << "Response: " << byteArray.toHex();
70
71 // Special handling for this since the hardware version can vary.
72 bool success = checkDeviceInquiryReply(buffer);
73 if (!success) {
74 RG_WARNING << "init(): Did not receive expected Device Inquiry Reply";
75 return;
76 }
77
78 // Get a dump of the scene.
79 sendSysExRaw(currentSceneDataDumpRequest);
80 getSysEx(buffer);
81
82 // Compare with Rosegarden scene, bail if same.
83 if (compareSysEx(buffer, rosegardenScene))
84 return;
85
86 // If the user appears to have customized the scene, ask if it is
87 // ok to clobber it.
88 if (!compareSysEx(buffer, defaultScene))
89 {
90 // Ask user if it is ok to reconfigure the device.
91 QMessageBox::StandardButton reply = QMessageBox::warning(
92 0,
93 tr("Rosegarden"),
94 tr("The connected Korg nanoKONTROL2 is not configured optimally for Rosegarden. Reconfiguring it will lose any custom settings you've made with the nanoKONTROL2 editor. Reconfigure?"),
95 QMessageBox::Yes | QMessageBox::No,
96 QMessageBox::Yes);
97
98 // If not, return.
99 if (reply == QMessageBox::No)
100 return;
101 }
102
103 // Send "Current Scene Data Dump" with Rosegarden scene.
104 sendSysExRaw(rosegardenScene);
105
106 // Confirm ACK
107 getSysEx(buffer);
108 success = compareSysEx(buffer, dataLoadCompleted);
109 if (!success) {
110 RG_WARNING << "init(): Did not receive expected Data Load Completed ACK";
111 return;
112 }
113
114 // Send Scene Write Request.
115 sendSysExRaw(sceneWriteRequest);
116
117 // Confirm Write Completed.
118 getSysEx(buffer);
119 success = compareSysEx(buffer, writeCompleted);
120 if (!success) {
121 RG_WARNING << "init(): Did not receive expected Write Completed";
122 return;
123 }
124
125 // ??? Do an LED test? testLEDs() and ask the user to confirm.
126
127 initLEDs();
128
129 #endif
130
131 }
132
documentModified()133 void KorgNanoKontrol2::documentModified()
134 {
135 refreshLEDs();
136 }
137
stopped()138 void KorgNanoKontrol2::stopped()
139 {
140 setPlayRecordStopLEDs(false, false, true);
141 }
142
playing()143 void KorgNanoKontrol2::playing()
144 {
145 setPlayRecordStopLEDs(true, false, false);
146 }
147
recording()148 void KorgNanoKontrol2::recording()
149 {
150 // ??? This will appear to not work. See setPlayRecordStopLEDs()
151 // for an explanation.
152 setPlayRecordStopLEDs(false, true, false);
153 }
154
processEvent(const MappedEvent * event)155 void KorgNanoKontrol2::processEvent(const MappedEvent *event)
156 {
157 // Not a CC? Bail.
158 if (event->getType() != MappedEvent::MidiController)
159 return;
160
161 const MidiByte controlNumber = event->getData1();
162 const MidiByte value = event->getData2();
163
164 // Record
165 // Handle this first for "speed".
166 if (controlNumber == 45 && value == 127) {
167 RosegardenMainWindow::self()->slotRecord();
168 return;
169 }
170
171 // Play
172 // Handle this second for "speed".
173 if (controlNumber == 41 && value == 127) {
174 RosegardenMainWindow::self()->slotPlay();
175 return;
176 }
177
178 // Volume Faders
179 if (controlNumber <= 7) {
180 processFader(controlNumber, value);
181 return;
182 }
183
184 // Pan Knobs
185 if (16 <= controlNumber && controlNumber <= 23) {
186 processKnob(controlNumber, value);
187 return;
188 }
189
190 // Track Left
191 if (controlNumber == 58 && value == 127) {
192 if (m_page == 0) {
193 // refresh LEDs regardless in case the user needs to get
194 // them back in sync.
195 refreshLEDs();
196
197 return;
198 }
199
200 --m_page;
201
202 refreshLEDs();
203
204 // ??? Would be nice to have some feedback in the UI. E.g. a
205 // range indicator on the tracks.
206 //TrackButtons *trackButtons = RosegardenMainWindow::self()->
207 // getView()->getTrackEditor()->getTrackButtons();
208 //trackButtons->setSurfaceRange(m_page * 8, 8);
209 //trackButtons->slotUpdateTracks();
210
211 return;
212 }
213
214 // Track Right
215 if (controlNumber == 59 && value == 127) {
216 RosegardenDocument *doc = RosegardenMainWindow::self()->getDocument();
217 Composition &comp = doc->getComposition();
218
219 if ((m_page + 1) * 8 >= comp.getTracks().size()) {
220 // refresh LEDs regardless in case the user needs to get
221 // them back in sync.
222 refreshLEDs();
223
224 return;
225 }
226
227 ++m_page;
228
229 refreshLEDs();
230
231 return;
232 }
233
234 // Stop
235 if (controlNumber == 42 && value == 127) {
236 // We cannot call this in the middle of processing incoming MIDI.
237 // The system is not ready for recording to stop.
238 //RosegardenMainWindow::self()->slotStop();
239
240 // Instead, we queue up a request for
241 // RosegardenMainWindow::customEvent().
242 QEvent *event = new QEvent(Stop);
243 QCoreApplication::postEvent(
244 RosegardenMainWindow::self(), event);
245
246 return;
247 }
248
249 // Rewind
250 if (controlNumber == 43) {
251 // Note: We tried doing this locally, but it crosses threads.
252 // Using the event queue is thread-safe.
253
254 QEvent *event = new ButtonEvent(Rewind, (value == 127));
255 QCoreApplication::postEvent(
256 RosegardenMainWindow::self(), event);
257 return;
258 }
259
260 // Fast-forward
261 if (controlNumber == 44) {
262 // Note: We tried doing this locally, but it crosses threads.
263 // Using the event queue is thread-safe.
264
265 QEvent *event = new ButtonEvent(FastForward, (value == 127));
266 QCoreApplication::postEvent(
267 RosegardenMainWindow::self(), event);
268 return;
269 }
270
271 // Cycle (Loop)
272 if (controlNumber == 46 && value == 127) {
273 RosegardenMainWindow::self()->toggleLoop();
274 return;
275 }
276
277 // "S" solo buttons
278 if (32 <= controlNumber && controlNumber <= 39 && value == 127) {
279 processSolo(controlNumber);
280 return;
281 }
282
283 // "M" mute buttons
284 if (48 <= controlNumber && controlNumber <= 55 && value == 127) {
285 processMute(controlNumber);
286 return;
287 }
288
289 // "R" record buttons
290 if (64 <= controlNumber && controlNumber <= 71 && value == 127) {
291 processRecord(controlNumber);
292 return;
293 }
294
295 // Marker Set
296 if (controlNumber == 60 && value == 127) {
297 QEvent *event = new QEvent(AddMarker);
298 QCoreApplication::postEvent(
299 RosegardenMainWindow::self(), event);
300 return;
301 }
302
303 // Marker Left
304 if (controlNumber == 61 && value == 127) {
305 QEvent *event = new QEvent(PreviousMarker);
306 QCoreApplication::postEvent(
307 RosegardenMainWindow::self(), event);
308 return;
309 }
310
311 // Marker Right
312 if (controlNumber == 62 && value == 127) {
313 QEvent *event = new QEvent(NextMarker);
314 QCoreApplication::postEvent(
315 RosegardenMainWindow::self(), event);
316 return;
317 }
318
319 }
320
processFader(MidiByte controlNumber,MidiByte value)321 void KorgNanoKontrol2::processFader(MidiByte controlNumber, MidiByte value)
322 {
323 const int trackNumber = controlNumber + m_page*8;
324
325 RosegardenDocument *doc = RosegardenMainWindow::self()->getDocument();
326 Composition &comp = doc->getComposition();
327
328 const Track *track = comp.getTrackByPosition(trackNumber);
329 if (!track)
330 return;
331
332 Studio &studio = doc->getStudio();
333 Instrument *instrument = studio.getInstrumentById(track->getInstrument());
334 if (!instrument)
335 return;
336
337 if (instrument->getType() == Instrument::Midi) {
338 // If the Instrument has volume...
339 if (instrument->hasController(MIDI_CONTROLLER_VOLUME)) {
340 // Adjust the Instrument and update everyone.
341 instrument->setControllerValue(MIDI_CONTROLLER_VOLUME, value);
342 Instrument::emitControlChange(instrument, MIDI_CONTROLLER_VOLUME);
343 doc->setModified();
344 }
345
346 return;
347 }
348
349 // We have an audio instrument or a softsynth...
350
351 const float dB = AudioLevel::fader_to_dB(
352 value, 127, AudioLevel::ShortFader);
353
354 instrument->setLevel(dB);
355 Instrument::emitControlChange(instrument, MIDI_CONTROLLER_VOLUME);
356 doc->setModified();
357 }
358
processKnob(MidiByte controlNumber,MidiByte value)359 void KorgNanoKontrol2::processKnob(MidiByte controlNumber, MidiByte value)
360 {
361 const int trackNumber = controlNumber - 16 + m_page*8;
362
363 RosegardenDocument *doc = RosegardenMainWindow::self()->getDocument();
364 Composition &comp = doc->getComposition();
365
366 const Track *track = comp.getTrackByPosition(trackNumber);
367 if (!track)
368 return;
369
370 Studio &studio = doc->getStudio();
371 Instrument *instrument = studio.getInstrumentById(track->getInstrument());
372 if (!instrument)
373 return;
374
375 if (instrument->getType() == Instrument::Midi) {
376 // If the Instrument has volume...
377 if (instrument->hasController(MIDI_CONTROLLER_PAN)) {
378 // Adjust the Instrument and update everyone.
379 instrument->setControllerValue(MIDI_CONTROLLER_PAN, value);
380 Instrument::emitControlChange(instrument, MIDI_CONTROLLER_PAN);
381 doc->setModified();
382 }
383
384 return;
385 }
386
387 // We have an audio instrument or a softsynth...
388
389 instrument->setControllerValue(
390 MIDI_CONTROLLER_PAN,
391 AudioLevel::AudioPanI(value));
392 Instrument::emitControlChange(instrument, MIDI_CONTROLLER_PAN);
393 doc->setModified();
394 }
395
processSolo(MidiByte controlNumber)396 void KorgNanoKontrol2::processSolo(MidiByte controlNumber)
397 {
398 const int trackNumber = controlNumber - 32 + m_page*8;
399
400 RosegardenDocument *doc = RosegardenMainWindow::self()->getDocument();
401 Composition &comp = doc->getComposition();
402
403 Track *track = comp.getTrackByPosition(trackNumber);
404 if (!track)
405 return;
406
407 // Toggle solo
408 track->setSolo(!track->isSolo());
409 comp.notifyTrackChanged(track);
410
411 doc->slotDocumentModified();
412 }
413
processMute(MidiByte controlNumber)414 void KorgNanoKontrol2::processMute(MidiByte controlNumber)
415 {
416 const int trackNumber = controlNumber - 48 + m_page*8;
417
418 RosegardenDocument *doc = RosegardenMainWindow::self()->getDocument();
419 Composition &comp = doc->getComposition();
420
421 Track *track = comp.getTrackByPosition(trackNumber);
422 if (!track)
423 return;
424
425 // Toggle mute
426 track->setMuted(!track->isMuted());
427 comp.notifyTrackChanged(track);
428
429 doc->slotDocumentModified();
430 }
431
processRecord(MidiByte controlNumber)432 void KorgNanoKontrol2::processRecord(MidiByte controlNumber)
433 {
434 const int trackNumber = controlNumber - 64 + m_page*8;
435
436 RosegardenDocument *doc = RosegardenMainWindow::self()->getDocument();
437 Composition &comp = doc->getComposition();
438
439 Track *track = comp.getTrackByPosition(trackNumber);
440 if (!track)
441 return;
442
443 // Toggle
444 bool state = !comp.isTrackRecording(track->getId());
445
446 // Update the Track
447 comp.setTrackRecording(track->getId(), state);
448 comp.notifyTrackChanged(track);
449
450 doc->checkAudioPath(track);
451
452 doc->slotDocumentModified();
453 }
454
testLEDs(bool on)455 void KorgNanoKontrol2::testLEDs(bool on)
456 {
457 const MidiByte value = on ? 127 : 0;
458
459 for (int i = 0; i < 8; ++i) {
460 // Record
461 ExternalController::send(
462 0, // channel
463 64+i, // controlNumber
464 value); // value
465 // Mute
466 ExternalController::send(
467 0, // channel
468 48+i, // controlNumber
469 value); // value
470 // Solo
471 ExternalController::send(
472 0, // channel
473 32+i, // controlNumber
474 value); // value
475 }
476
477 // Play
478 ExternalController::send(
479 0, // channel
480 41, // controlNumber
481 value); // value
482 // Stop
483 ExternalController::send(
484 0, // channel
485 42, // controlNumber
486 value); // value
487 // Rewind
488 ExternalController::send(
489 0, // channel
490 43, // controlNumber
491 value); // value
492 // Fast forward
493 ExternalController::send(
494 0, // channel
495 44, // controlNumber
496 value); // value
497 // Record
498 ExternalController::send(
499 0, // channel
500 45, // controlNumber
501 value); // value
502 // Cycle
503 ExternalController::send(
504 0, // channel
505 46, // controlNumber
506 value); // value
507
508 // The following buttons have no LED:
509 // - Track Left (58)
510 // - Track Right (59)
511 // - Marker Set (60)
512 // - Marker Left (61)
513 // - Marker Left (62)
514 }
515
initLEDs()516 void KorgNanoKontrol2::initLEDs()
517 {
518 // Turn off all the LEDs and update the cache to match.
519
520 testLEDs(false);
521
522 for (int i = 0; i < 8; ++i) {
523 m_solo[i] = false;
524 m_mute[i] = true;
525 m_recordArmed[i] = false;
526 }
527
528 m_play = false;
529
530 m_stop = true;
531 // Stop
532 ExternalController::send(
533 0, // channel
534 42, // controlNumber
535 127); // value
536
537 m_rewind = false;
538 m_fastForward = false;
539 m_record = false;
540 m_cycle = false;
541 }
542
refreshLEDs()543 void KorgNanoKontrol2::refreshLEDs()
544 {
545 #if 0
546 // Do another init for testing purposes. We should be able to
547 // connect to aseqdump and watch the messages go back and forth.
548 init();
549 #endif
550 #if 0
551 static bool on = false;
552 on = !on;
553 testLEDs(on);
554 #endif
555
556 if (m_firstRefresh) {
557 initLEDs();
558 m_firstRefresh = false;
559 }
560
561 // Note: The LEDs will not work unless they are set to "External" mode.
562 // This can be adjusted with the nanoKONTROL2 editor for Windows/Mac
563 // or you can send two sysex messages to the device. Eventually,
564 // this class will be able to send the sysex messages. See
565 // init().
566
567 RosegardenDocument *doc = RosegardenMainWindow::self()->getDocument();
568 Composition &comp = doc->getComposition();
569
570 // For each Track
571 for (int i = 0; i < 8; ++i) {
572 const int trackNumber = i + m_page*8;
573
574 Track *track = comp.getTrackByPosition(trackNumber);
575 if (!track)
576 return;
577
578 // Solo
579 const bool solo = track->isSolo();
580 // If there was a change...
581 if (solo != m_solo[i]) {
582 ExternalController::send(
583 0, // channel
584 32+i, // controlNumber
585 solo ? 127 : 0); // value
586 // Update the cache.
587 m_solo[i] = solo;
588 }
589
590 // Mute
591 const bool mute = track->isMuted();
592 // If there was a change...
593 if (mute != m_mute[i]) {
594 ExternalController::send(
595 0, // channel
596 48+i, // controlNumber
597 mute ? 0 : 127); // value
598 // Update the cache.
599 m_mute[i] = mute;
600 }
601
602 // Record
603 const bool recordArmed = comp.isTrackRecording(track->getId());
604 // If there was a change...
605 if (recordArmed != m_recordArmed[i]) {
606 ExternalController::send(
607 0, // channel
608 64+i, // controlNumber
609 recordArmed ? 127 : 0); // value
610 // Update the cache.
611 m_recordArmed[i] = recordArmed;
612 }
613 }
614
615 // Cycle
616 const bool cycle = doc->getComposition().isLooping();
617 // If there was a change...
618 if (cycle != m_cycle) {
619 ExternalController::send(
620 0, // channel
621 46, // controlNumber
622 cycle ? 127 : 0); // value
623 // Update the cache.
624 m_cycle = cycle;
625 }
626
627 }
628
setPlayRecordStopLEDs(bool play,bool record,bool stop)629 void KorgNanoKontrol2::setPlayRecordStopLEDs(bool play, bool record, bool stop)
630 {
631 // Note: Tried SequenceManager::getTransportStatus(), but there is no
632 // way to subscribe for changes to that.
633
634 if (stop != m_stop) {
635 ExternalController::send(
636 0, // channel
637 42, // controlNumber
638 stop ? 127 : 0); // value
639 m_stop = stop;
640 }
641
642 if (play != m_play) {
643 ExternalController::send(
644 0, // channel
645 41, // controlNumber
646 play ? 127 : 0); // value
647 m_play = play;
648 }
649
650 if (record != m_record) {
651 // ??? This isn't working. From watching this with aseqdump, you can
652 // see that when record begins, the CC's that are sent out the
653 // external controller port get stuck and are not released until
654 // we stop recording. So, when we are in record, sending CCs to
655 // the external controller port does not work. Yet another
656 // argument for separating the external controller port from the
657 // rest of AlsaDriver.
658 ExternalController::send(
659 0, // channel
660 45, // controlNumber
661 record ? 127 : 0); // value
662 m_record = record;
663 }
664
665 }
666
667
668 }
669