1 /*
2     MIDI Sequencer C++ library
3     Copyright (C) 2006-2021, Pedro Lopez-Cabanillas <plcl@users.sf.net>
4 
5     This library is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 3 of the License, or
8     (at your option) any later version.
9 
10     This library is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License
16     along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18 
19 extern "C" {
20     #include <alsa/asoundlib.h>
21 }
22 
23 #include <QReadLocker>
24 #include <QWriteLocker>
25 #include <drumstick/alsaclient.h>
26 #include <drumstick/alsaqueue.h>
27 #include <drumstick/playthread.h>
28 
29 /**
30  * @file playthread.cpp
31  * Implementation of a sequencer output thread
32  */
33 
34 /**
35  * @addtogroup PlayThread
36  * @{
37  * ALSA Sequencer easy playback functionality.
38  * SequencerOutputThread provides MIDI sequence playback.
39  *
40  * This is an abstract class that must be extended providing
41  * an implementation for the pure virtual methods:
42  * SequencerOutputThread::hasNext() and SequencerOutputThread::nextEvent()
43  * before using it for MIDI sequence playback. You can use any structure or
44  * class you prefer to store the SequencerEvent objects, but they must be
45  * provided ordered by time. A simplistic Song definition may be:
46  *
47  * @code
48  * typedef QList<SequencerEvent> Song;
49  * @endcode
50  *
51  * Using this class is optional. You may prefer another mechanism to
52  * manage playback actions. This class uses a thread to manage the
53  * sequence playback as a background task.
54  * @}
55  */
56 
57 namespace drumstick {
58 namespace ALSA {
59 
60 const int TIMEOUT = 100;
61 
62 /**
63  * Constructor
64  * @param seq Existing MidiClient object pointer
65  * @param portId Numeric input/output port identifier
66  */
SequencerOutputThread(MidiClient * seq,int portId)67 SequencerOutputThread::SequencerOutputThread(MidiClient *seq, int portId)
68     : QThread(),
69     m_MidiClient(seq),
70     m_Queue(nullptr),
71     m_PortId(portId),
72     m_Stopped(false),
73     m_QueueId(0),
74     m_npfds(0),
75     m_pfds(nullptr)
76 {
77     if (m_MidiClient != nullptr) {
78         m_Queue = m_MidiClient->getQueue();
79         m_QueueId = m_Queue->getId();
80     }
81 }
82 
83 /**
84  * Checks if stop has been requested
85  * @return True if stop has been requested
86  * @since 0.2.0
87  */
88 bool
stopRequested()89 SequencerOutputThread::stopRequested()
90 {
91 	QReadLocker locker(&m_mutex);
92     return m_Stopped;
93 }
94 
95 /**
96  * Stops the playback task
97  */
98 void
stop()99 SequencerOutputThread::stop()
100 {
101 	QWriteLocker locker(&m_mutex);
102     m_Stopped = true;
103     locker.unlock();
104     while (isRunning()) {
105         wait(TIMEOUT);
106     }
107 }
108 
109 /**
110  * Sends an echo event, with the same PortId as sender and destination.
111  * @param tick Event schedule time in ticks.
112  */
113 void
sendEchoEvent(int tick)114 SequencerOutputThread::sendEchoEvent(int tick)
115 {
116     if (!stopRequested() && m_MidiClient != nullptr) {
117         SystemEvent ev(SND_SEQ_EVENT_ECHO);
118         ev.setSource(m_PortId);
119         ev.setDestination(m_MidiClient->getClientId(), m_PortId);
120         ev.scheduleTick(m_QueueId, tick, false);
121         sendSongEvent(&ev);
122     }
123 }
124 
125 /**
126  * Sends a SequencerEvent
127  * @param ev SequencerEvent object pointer
128  */
129 void
sendSongEvent(SequencerEvent * ev)130 SequencerOutputThread::sendSongEvent(SequencerEvent* ev)
131 {
132     if (m_MidiClient != nullptr) {
133         while (!stopRequested() &&
134                (snd_seq_event_output_direct(m_MidiClient->getHandle(), ev->getHandle()) < 0)) {
135             poll(m_pfds, m_npfds, TIMEOUT);
136         }
137     }
138 }
139 
140 /**
141  * Flush the ALSA output buffer.
142  */
143 void
drainOutput()144 SequencerOutputThread::drainOutput()
145 {
146     if (!stopRequested() && m_MidiClient != nullptr) {
147         while (!stopRequested() && (snd_seq_drain_output(m_MidiClient->getHandle()) < 0)) {
148             poll(m_pfds, m_npfds, TIMEOUT);
149         }
150     }
151 }
152 
153 /**
154  * Waits until the ALSA output queue is empty (all the events have been played.)
155  */
156 void
syncOutput()157 SequencerOutputThread::syncOutput()
158 {
159     if (!stopRequested() && m_MidiClient != nullptr) {
160         m_MidiClient->synchronizeOutput();
161     }
162 }
163 
164 /**
165  * Thread process loop
166  */
run()167 void SequencerOutputThread::run()
168 {
169     if (m_MidiClient != nullptr) {
170         try  {
171             unsigned int last_tick;
172             m_npfds = snd_seq_poll_descriptors_count(m_MidiClient->getHandle(), POLLOUT);
173             m_pfds = (pollfd*) calloc(m_npfds, sizeof(pollfd));
174             snd_seq_poll_descriptors(m_MidiClient->getHandle(), m_pfds, m_npfds, POLLOUT);
175             last_tick = getInitialPosition();
176             if (last_tick == 0) {
177                 m_Queue->start();
178             } else {
179                 m_Queue->setTickPosition(last_tick);
180                 m_Queue->continueRunning();
181             }
182             while (!stopRequested() && hasNext()) {
183                 SequencerEvent* ev = nextEvent();
184                 if (!stopRequested() && !SequencerEvent::isConnectionChange(ev)) {
185                     sendSongEvent(ev);
186                 }
187                 if (getEchoResolution() > 0) {
188                     while (!stopRequested() && (last_tick < ev->getTick())) {
189                         last_tick += getEchoResolution();
190                         sendEchoEvent(last_tick);
191                     }
192                 }
193             }
194             if (stopRequested()) {
195                 m_Queue->clear();
196                 emit playbackStopped();
197             } else {
198                 drainOutput();
199                 syncOutput();
200                 if (stopRequested())
201                     emit playbackStopped();
202                 else
203                     emit playbackFinished();
204             }
205             m_Queue->stop();
206         } catch (...) {
207             qWarning("exception in output thread");
208         }
209         m_npfds = 0;
210         free(m_pfds);
211         m_pfds = nullptr;
212     }
213 }
214 
215 /**
216  * Starts the playback thread
217  * @param priority Thread priority, default is InheritPriority
218  */
start(Priority priority)219 void SequencerOutputThread::start( Priority priority )
220 {
221 	QWriteLocker locker(&m_mutex);
222     m_Stopped = false;
223     QThread::start( priority );
224 }
225 
226 } // namespace ALSA
227 } // namespace drumstick
228 
229 
230