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