1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2
3 /*
4 Rosegarden
5 A MIDI and audio sequencer and musical notation editor.
6 Copyright 2000-2021 the Rosegarden development team.
7
8 This file is Copyright 2009
9 Immanuel Litzroth <immanuel203@gmail.com>
10
11 Other copyrights also apply to some parts of this work. Please
12 see the AUTHORS file and individual file headers for details.
13
14 This program is free software; you can redistribute it and/or
15 modify it under the terms of the GNU General Public License as
16 published by the Free Software Foundation; either version 2 of the
17 License, or (at your option) any later version. See the file
18 COPYING included with this distribution for more information.
19 */
20
21 #define RG_MODULE_STRING "[TranzportClient]"
22
23 #include "base/Exception.h"
24 #include "commands/edit/AddMarkerCommand.h"
25 #include "TranzportClient.h"
26 #include "document/RosegardenDocument.h"
27 #include "document/CommandHistory.h"
28 #include "gui/editors/segment/TrackButtons.h"
29 #include "RosegardenMainWindow.h"
30 #include "misc/Debug.h"
31 #include "misc/Strings.h"
32
33 #include <QSocketNotifier>
34
35 #include <errno.h>
36 #include <sstream>
37 #include <stdlib.h>
38 #include <stdint.h>
39 #include <fcntl.h>
40 #include <unistd.h>
41 #include <string.h>
42 #include <strings.h>
43 #include <limits>
44
45 namespace Rosegarden
46 {
47
TranzportClient(RosegardenMainWindow * rgGUIApp)48 TranzportClient::TranzportClient(RosegardenMainWindow* rgGUIApp) :
49 QObject(),
50 device_online(true),
51 previous_buttons(*reinterpret_cast<uint32_t*>(previousbuf+2)),
52 current_buttons(*reinterpret_cast<uint32_t*>(currentbuf+2)),
53 datawheel(currentbuf[6]),
54 status(currentbuf[1]),
55 m_rgGUIApp(rgGUIApp),
56 m_rgDocument(rgGUIApp->getDocument()),
57 m_composition(&m_rgDocument->getComposition())
58 {
59 m_descriptor = open("/dev/tranzport0",O_RDWR);
60
61 if (m_descriptor < 0) {
62 throw Exception(qstrtostr(QObject::tr("Failed to open tranzport device /dev/tranzport0")));
63 }
64
65 bzero(currentbuf,8);
66 bzero(previousbuf,8);
67
68 fcntl(m_descriptor,F_SETOWN, getpid());
69 int socketFlags = fcntl(m_descriptor, F_GETFL, 0);
70 if (socketFlags != -1) {
71 fcntl(m_descriptor, F_SETFL, socketFlags | O_NONBLOCK);
72 }
73
74 m_socketReadNotifier = new QSocketNotifier(m_descriptor, QSocketNotifier::Read, nullptr);
75 m_socketWriteNotifier = new QSocketNotifier(m_descriptor, QSocketNotifier::Write,nullptr);
76
77 connect(m_socketReadNotifier, &QSocketNotifier::activated, this, &TranzportClient::readData);
78 connect(m_socketWriteNotifier, &QSocketNotifier::activated, this, &TranzportClient::writeCommandQueue);
79
80 connect(this, &TranzportClient::play,
81 m_rgGUIApp, &RosegardenMainWindow::slotPlay );
82 connect(this, &TranzportClient::stop,
83 m_rgGUIApp, &RosegardenMainWindow::slotStop );
84 connect(this, &TranzportClient::record,
85 m_rgGUIApp, &RosegardenMainWindow::slotRecord );
86 connect(this, &TranzportClient::rewind,
87 m_rgGUIApp, &RosegardenMainWindow::slotRewind );
88 connect(this, &TranzportClient::rewindToBeginning,
89 m_rgGUIApp, &RosegardenMainWindow::slotRewindToBeginning );
90 connect(this, &TranzportClient::fastForward,
91 m_rgGUIApp, &RosegardenMainWindow::slotFastforward );
92 connect(this, &TranzportClient::fastForwardToEnd,
93 m_rgGUIApp, &RosegardenMainWindow::slotFastForwardToEnd );
94 connect(this, &TranzportClient::toggleRecord,
95 m_rgGUIApp, &RosegardenMainWindow::slotToggleRecord );
96 connect(this, &TranzportClient::trackDown,
97 m_rgGUIApp, &RosegardenMainWindow::slotSelectNextTrack);
98 connect(this, &TranzportClient::trackUp,
99 m_rgGUIApp, &RosegardenMainWindow::slotSelectPreviousTrack);
100 connect(this, SIGNAL(trackMute()),
101 m_rgGUIApp, SLOT(slotToggleMute()) );
102 connect(this, &TranzportClient::trackRecord,
103 m_rgGUIApp, &RosegardenMainWindow::slotToggleRecordCurrentTrack );
104 connect(this, &TranzportClient::solo,
105 m_rgGUIApp, &RosegardenMainWindow::slotToggleSolo);
106
107 connect(m_rgGUIApp, &RosegardenMainWindow::documentLoaded,
108 this, &TranzportClient::slotDocumentLoaded);
109
110 connect(m_rgDocument, &RosegardenDocument::pointerPositionChanged,
111 this, &TranzportClient::pointerPositionChanged);
112
113 connect(m_rgDocument, &RosegardenDocument::loopChanged,
114 this, &TranzportClient::loopChanged);
115
116 connect(this, &TranzportClient::undo,
117 CommandHistory::getInstance(),&CommandHistory::undo);
118
119 connect(this, &TranzportClient::redo,
120 CommandHistory::getInstance(), &CommandHistory::redo);
121
122 connect(this, &TranzportClient::setPosition,
123 m_rgDocument, &RosegardenDocument::slotSetPointerPosition);
124
125 m_composition->addObserver(this);
126 m_socketWriteNotifier->setEnabled(false);
127 stateUpdate();
128
129 RG_DEBUG << "TranzportClient::TranzportClient: connected to tranzport device: " << m_descriptor;
130 }
131
132 void
pointerPositionChanged(timeT time)133 TranzportClient::pointerPositionChanged(timeT time)
134 {
135 RG_DEBUG << "TranzportClient, pointerPositionChanged";
136
137 if (device_online) {
138 static int prevbeat = 0;
139 int bar, beat, fraction, remainder;
140
141 m_composition->getMusicalTimeForAbsoluteTime(time,bar,beat,fraction,remainder);
142 if (prevbeat != beat) {
143 std::stringstream ss;
144 ss << bar+1 << ":" << beat;
145 LCDWrite(ss.str(), Bottom, 10);
146 prevbeat = beat;
147 }
148 }
149 }
150
151 void
slotDocumentLoaded(RosegardenDocument * doc)152 TranzportClient::slotDocumentLoaded(RosegardenDocument *doc)
153 {
154 RG_DEBUG << "TranzportClient::DocumentChanged ";
155
156 m_rgDocument = doc;
157 m_composition = &m_rgDocument->getComposition();
158 m_composition->addObserver(this);
159 connect(m_rgDocument, &RosegardenDocument::pointerPositionChanged,
160 this, &TranzportClient::pointerPositionChanged);
161 connect(m_rgDocument, &RosegardenDocument::loopChanged,
162 this, &TranzportClient::loopChanged);
163 connect(this, &TranzportClient::setPosition,
164 m_rgDocument, &RosegardenDocument::slotSetPointerPosition);
165
166 while (not commands.empty()) {
167 commands.pop();
168 }
169 stateUpdate();
170 }
171
172 #if 0
173 // soloChanged() has been removed. Solo has moved from Composition to
174 // Track. The new CompositionObserver::selectedTrackChanged() might be
175 // of interest in bringing this back to life.
176
177 /**
178 * Called when solo status changes (solo on/off, and selected track)
179 */
180 void
181 TranzportClient::soloChanged(const Composition * c,
182 bool solo,
183 TrackId selectedTrack )
184 {
185 RG_DEBUG << "TranzportClient, CompostionObserver::soloChanged";
186
187 if (device_online) {
188 if (solo) {
189 LightOn(LightAnysolo);
190 } else {
191 LightOff(LightAnysolo);
192 }
193
194 Track* track = c->getTrackById(selectedTrack);
195
196 if (track->isArmed()) {
197 LightOn(LightTrackrec);
198 } else {
199 LightOff(LightTrackrec);
200 }
201
202 if (track->isMuted()) {
203 LightOn(LightTrackmute);
204 } else {
205 LightOff(LightTrackmute);
206 }
207
208 LCDWrite(track->getLabel(), Bottom);
209 }
210 }
211 #endif
212
213 /**
214 * Called when a track is changed (instrument id, muted status...)
215 */
216 void
trackChanged(const Composition * c,Track * track)217 TranzportClient::trackChanged(const Composition *c,
218 Track* track)
219 {
220 RG_DEBUG << "TranzportClient, CompostionObserver::trackChanged";
221
222 if (device_online) {
223 const Track* track2 = c->getTrackById(c->getSelectedTrack());
224
225 // If the changed track is the selected track
226 if (track == track2) {
227 RG_DEBUG << "TranzportClient, CompostionObserver::trackChanged updateing";
228
229 if (track->isArmed()) {
230 LightOn(LightTrackrec);
231 } else {
232 LightOff(LightTrackrec);
233 }
234
235 if (track->isMuted()) {
236 LightOn(LightTrackmute);
237 } else {
238 LightOff(LightTrackmute);
239 }
240
241 LCDWrite(track->getLabel(), Bottom);
242 }
243 }
244 }
245
246 void
loopChanged(timeT t1,timeT t2)247 TranzportClient::loopChanged(timeT t1,
248 timeT t2)
249 {
250 RG_DEBUG << "TranzportClient: loopChanged" << t1 << ", " << t2;
251
252 if (device_online) {
253 if (t1 == 0 and t2 == 0) {
254 LightOff(LightLoop);
255 } else {
256 LightOn(LightLoop);
257 }
258 }
259 }
260
261 void
stateUpdate()262 TranzportClient::stateUpdate()
263 {
264 if (device_online) {
265 LCDWrite("Rosegarden");
266
267 // No longer supported. Solo has moved to Track.
268 //if (m_composition->isSolo()) {
269 // LightOn(LightAnysolo);
270 //} else {
271 LightOff(LightAnysolo);
272 //}
273
274 if (m_composition->isLooping()) {
275 LightOn(LightLoop);
276 } else {
277 LightOff(LightLoop);
278 }
279
280 TrackId trackID = m_composition->getSelectedTrack();
281 Track* track = m_composition->getTrackById(trackID);
282
283 if (track->isArmed()) {
284 LightOn(LightTrackrec);
285 } else {
286 LightOff(LightTrackrec);
287 }
288
289 if (track->isMuted()) {
290 LightOn(LightTrackmute);
291 } else {
292 LightOff(LightTrackmute);
293 }
294
295 LCDWrite(track->getLabel().substr(0,9), Bottom);
296
297 int bar, beat, fraction, remainder;
298 m_composition->getMusicalTimeForAbsoluteTime(
299 m_composition->getPosition(), bar, beat, fraction, remainder);
300 std::stringstream ss;
301 ss << bar+1 << ":" << beat;
302 LCDWrite(ss.str(), Bottom, 10);
303 }
304 }
305
~TranzportClient()306 TranzportClient::~TranzportClient()
307 {
308 delete m_socketReadNotifier;
309 delete m_socketWriteNotifier;
310
311 close(m_descriptor);
312
313 RG_DEBUG << "TranzportClient::~TranzportClient: cleaned up ";
314 }
315
316
writeCommandQueue()317 void TranzportClient::writeCommandQueue()
318 {
319 RG_DEBUG << "TranzportClient: writeCommandQueue";
320
321 if (commands.empty()) {
322 m_socketWriteNotifier->setEnabled(false);
323 return;
324 }
325
326 uint64_t cmd = commands.front();
327 int res = ::write(m_descriptor, (void*)&cmd, 8);
328 m_socketWriteNotifier->setEnabled(false);
329
330 if (res < 0) {
331 RG_DEBUG << "TranzportClient::writeCommandQueue: could not write to device, error" << strerror(errno);
332
333 m_socketWriteNotifier->setEnabled(true);
334 return;
335 } else if (res != 8) {
336 RG_DEBUG << "TranzportClient::writeCommandQueue: could not write full data to device";
337
338 commands.pop();
339 m_socketWriteNotifier->setEnabled(true);
340 }
341
342 commands.pop();
343
344 if (not commands.empty()) {
345 m_socketWriteNotifier->setEnabled(true);
346 }
347 }
348
349 void
write(uint64_t buf)350 TranzportClient::write(uint64_t buf)
351 {
352 commands.push(buf);
353 if (not m_socketWriteNotifier->isEnabled()) {
354 RG_DEBUG << "TranzportClient::write Setting the socket write notifier to enabled";
355 m_socketWriteNotifier->setEnabled(true);
356 }
357 }
358
359 void
LightOn(Light light)360 TranzportClient::LightOn(Light light)
361 {
362 uint8_t cmd[8];
363
364 cmd[0] = 0x00;
365 cmd[1] = 0x00;
366 cmd[2] = light;
367 cmd[3] = 0x01;
368 cmd[4] = 0x00;
369 cmd[5] = 0x00;
370 cmd[6] = 0x00;
371 cmd[7] = 0x00;
372
373 write(*(uint64_t*) cmd);
374 }
375
376 void
LightOff(Light light)377 TranzportClient::LightOff(Light light)
378 {
379 uint8_t cmd[8];
380
381 cmd[0] = 0x00;
382 cmd[1] = 0x00;
383 cmd[2] = light;
384 cmd[3] = 0x00;
385 cmd[4] = 0x00;
386 cmd[5] = 0x00;
387 cmd[6] = 0x00;
388 cmd[7] = 0x00;
389 write(*(uint64_t*)cmd);
390 }
391
392 void
LCDWrite(const std::string & text,Row row,uint8_t offset)393 TranzportClient::LCDWrite(const std::string& text,
394 Row row,
395 uint8_t offset)
396 {
397 if (offset >= LCDLength) {
398 return;
399 }
400
401 std::string str(LCDLength, ' ' );
402 str.insert(offset, text.c_str(),
403 std::min(text.size(), static_cast<size_t>(LCDLength - offset)));
404
405 uint8_t cmd[8];
406 uint8_t cell = row == Top ? 0 : 5;
407
408 for (int i = 0; i < LCDLength;) {
409 cmd[0] = 0x00;
410 cmd[1] = 0x01;
411 cmd[2] = cell++;
412 cmd[3] = str[i++];
413 cmd[4] = str[i++];
414 cmd[5] = str[i++];
415 cmd[6] = str[i++];
416 cmd[7] = 0x00;
417 write(*(uint64_t*)cmd);
418 }
419 }
420
421 void
readData()422 TranzportClient::readData()
423 {
424 memcpy(previousbuf, currentbuf, 8);
425 ssize_t val;
426 static timeT loop_start_time=0;
427 static timeT loop_end_time=0;
428
429 while ((val=read(m_descriptor,currentbuf,8)) == 8) {
430 uint32_t new_buttons = current_buttons ^ previous_buttons;
431 if (status == 0x1) {
432 RG_DEBUG << "TranzportClient: device just came online";
433
434 while (not commands.empty()) {
435 commands.pop();
436 }
437 device_online = true;
438
439 m_rgDocument = m_rgGUIApp->getDocument();
440 m_composition = &m_rgGUIApp->getDocument()->getComposition();
441 stateUpdate();
442 }
443
444 if (status == 0xff) {
445 RG_DEBUG << "TranzportClient: device just went offline";
446
447 device_online = false;
448 return;
449 }
450
451 // Solo has moved to Track.
452 #if 0
453 if (new_buttons & TrackSolo and
454 current_buttons & TrackSolo) {
455 if (current_buttons & Shift) {
456 bool soloflag = m_composition->isSolo();
457 emit solo(not soloflag);
458 }
459 }
460 #endif
461
462 if (new_buttons & Add and
463 current_buttons & Add) {
464 if (current_buttons & Shift) {
465 } else {
466 AddMarkerCommand* cmd = new AddMarkerCommand(m_composition,
467 m_composition->getPosition(),
468 "tranzport",
469 "");
470 CommandHistory::getInstance()->addCommand(cmd);
471 }
472 }
473
474 if (new_buttons & Prev and
475 current_buttons & Prev) {
476 RG_DEBUG << "TranzportClient:: received marker previous";
477
478 if (current_buttons & Shift) {
479 } else {
480 timeT currentTime = m_composition->getPosition();
481 Composition::markercontainer& mc = m_composition->getMarkers();
482 timeT closestPrevious = -1;
483
484 for (Composition::markerconstiterator it = mc.begin();
485 it != mc.end();
486 ++it) {
487 timeT markerTime = (*it)->getTime();
488 if (markerTime < currentTime and
489 markerTime > closestPrevious) {
490 closestPrevious = markerTime;
491 }
492 }
493
494 if (closestPrevious >= 0) {
495 RG_DEBUG << "Tranzport:: setting position: " << closestPrevious;
496
497 emit setPosition(closestPrevious);
498 }
499 }
500 }
501
502 if (new_buttons & Next and
503 current_buttons & Next)
504 {
505 RG_DEBUG << "TranzportClient:: received marker next";
506
507 if (current_buttons & Shift) {
508 } else {
509 timeT currentTime = m_composition->getPosition();
510 Composition::markercontainer& mc = m_composition->getMarkers();
511 timeT closestNext = std::numeric_limits<long>::max();
512
513 for (Composition::markerconstiterator it = mc.begin();
514 it != mc.end();
515 ++it) {
516 timeT markerTime = (*it)->getTime();
517 if (markerTime > currentTime and
518 markerTime < closestNext) {
519 closestNext = markerTime;
520 }
521 }
522
523 if (closestNext < std::numeric_limits<long>::max()) {
524 RG_DEBUG << "Tranzport:: setting position: " << closestNext;
525
526 emit setPosition(closestNext);
527 }
528 }
529 }
530
531 if (new_buttons & Undo and
532 current_buttons & Undo) {
533 if (current_buttons & Shift) {
534 emit redo();
535 } else {
536 emit undo();
537 }
538 }
539
540 if (new_buttons & Play and
541 current_buttons & Play) {
542 if (current_buttons & Shift) {
543 } else {
544 emit play();
545 }
546 }
547
548 if (new_buttons & Stop and
549 current_buttons & Stop) {
550 if (current_buttons & Shift) {
551 } else {
552 emit stop();
553 }
554 }
555
556 if (new_buttons & Record and
557 current_buttons & Record) {
558 if (current_buttons & Shift) {
559 } else {
560 emit record();
561 }
562 }
563
564 if (new_buttons & Loop and
565 current_buttons & Loop) {
566 if (current_buttons & Shift) {
567 } else {
568 loop_start_time = m_composition->getPosition();
569 loop_end_time = loop_start_time;
570 }
571 }
572
573 if (new_buttons & Loop and
574 (not (current_buttons & Loop))) {
575 if (current_buttons & Shift) {
576 } else {
577 if (loop_start_time == loop_end_time) {
578 m_rgDocument->setLoop(0,0);
579 }
580
581 loop_start_time = 0;
582 loop_end_time = 0;
583 }
584 }
585
586 if (new_buttons& Rewind and
587 current_buttons & Rewind) {
588 if (current_buttons&Shift) {
589 emit rewindToBeginning();
590 } else {
591 emit rewind();
592 }
593 }
594
595 if (new_buttons & FastForward and
596 current_buttons & FastForward) {
597 if (current_buttons & Shift) {
598 emit fastForwardToEnd();
599 } else {
600 emit fastForward();
601 }
602 }
603
604 if (new_buttons & TrackRec and
605 current_buttons & TrackRec) {
606 if (current_buttons & Shift) {
607 } else {
608 emit trackRecord();
609 }
610 }
611
612 if (new_buttons & TrackRight and
613 current_buttons & TrackRight) {
614 if (current_buttons & Shift) {
615 } else {
616 emit trackDown();
617 }
618 }
619
620 if (new_buttons & TrackLeft and
621 current_buttons & TrackLeft) {
622 if (current_buttons& Shift) {
623 } else {
624 emit trackUp();
625 }
626 }
627
628 if (new_buttons & TrackMute and
629 current_buttons & TrackMute) {
630 if (current_buttons & Shift) {
631 } else {
632 emit trackMute();
633 }
634 }
635
636 if (datawheel) {
637 if (datawheel < 0x7F) {
638 if (current_buttons & Loop) {
639 loop_end_time += datawheel *
640 m_composition->getDurationForMusicalTime(loop_end_time, 0,1,0,0);
641 m_rgDocument->setLoop(loop_start_time, loop_end_time);
642 } else if(current_buttons & Shift) {
643 timeT here = m_composition->getPosition();
644 here += datawheel * m_composition->getDurationForMusicalTime(here,0,0,1,0);
645 if (here <= m_composition->getEndMarker()) {
646 emit setPosition(here);
647 }
648 } else {
649 timeT here = m_composition->getPosition();
650 here += datawheel * m_composition->getDurationForMusicalTime(here,0,1,0,0);
651 if (here <= m_composition->getEndMarker()) {
652 emit setPosition(here);
653 }
654 }
655 } else {
656 #define DATAWHEEL_VALUE (1 + (0xFF - (datawheel)))
657 if (current_buttons & Loop) {
658 loop_end_time -= (1 + (0xFF - datawheel)) *
659 m_rgGUIApp->getDocument()->getComposition().getDurationForMusicalTime(loop_end_time, 0,1,0,0);
660 m_rgDocument->setLoop(loop_start_time, loop_end_time);
661 }
662
663 if (current_buttons & Shift) {
664 timeT here = m_composition->getPosition();
665 here -= DATAWHEEL_VALUE * m_composition->getDurationForMusicalTime(here,0,0,1,0);
666 if (here >= m_composition->getStartMarker()) {
667 emit setPosition(here);
668 }
669 } else {
670 timeT here = m_composition->getPosition();
671 here -= DATAWHEEL_VALUE * m_composition->getDurationForMusicalTime(here,0,1,0,0);
672 if (here >= m_composition->getStartMarker()) {
673 emit setPosition(here);
674 }
675 }
676 #undef DATAWHEEL_VALUE
677 }
678 }
679
680 memcpy(previousbuf, currentbuf, 8);
681 }
682
683 if (val == -1) {
684 if (errno == EAGAIN) {
685 return;
686 } else {
687 RG_DEBUG << "TranzportClient::readData: error " << strerror(errno);
688 }
689 } else {
690 RG_DEBUG << "TranzportClient::readData: partial read of length " << val;
691 RG_DEBUG << "TranzportClient::readData: this should not happen " << val;
692 }
693 }
694
695 }
696
697