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