1 /*
2 * Copyright (C) 2014-2018 Robin Gareus <robin@gareus.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include <unistd.h>
20
21 #include <glibmm.h>
22
23 #include "alsa_midi.h"
24
25 #include "pbd/error.h"
26 #include "pbd/pthread_utils.h"
27 #include "pbd/i18n.h"
28
29 using namespace ARDOUR;
30
31 #ifndef NDEBUG
32 #define _DEBUGPRINT(STR) fprintf(stderr, STR);
33 #else
34 #define _DEBUGPRINT(STR) ;
35 #endif
36
AlsaMidiIO()37 AlsaMidiIO::AlsaMidiIO ()
38 : _state (-1)
39 , _running (false)
40 , _pfds (0)
41 , _sample_length_us (1e6 / 48000.0)
42 , _period_length_us (1.024e6 / 48000.0)
43 , _samples_per_period (1024)
44 , _rb (0)
45 {
46 pthread_mutex_init (&_notify_mutex, 0);
47 pthread_cond_init (&_notify_ready, 0);
48
49 // MIDI (hw port) 31.25 kbaud
50 // worst case here is 8192 SPP and 8KSPS for which we'd need
51 // 4000 bytes sans MidiEventHeader.
52 // since we're not always in sync, let's use 4096.
53 _rb = new PBD::RingBuffer<uint8_t>(4096 + 4096 * sizeof(MidiEventHeader));
54 }
55
~AlsaMidiIO()56 AlsaMidiIO::~AlsaMidiIO ()
57 {
58 delete _rb;
59 pthread_mutex_destroy (&_notify_mutex);
60 pthread_cond_destroy (&_notify_ready);
61 free (_pfds);
62 }
63
pthread_process(void * arg)64 static void * pthread_process (void *arg)
65 {
66 AlsaMidiIO *d = static_cast<AlsaMidiIO *>(arg);
67 pthread_set_name ("AlsaMidiIO");
68 d->main_process_thread ();
69 pthread_exit (0);
70 return 0;
71 }
72
73 int
start()74 AlsaMidiIO::start ()
75 {
76 if (pbd_realtime_pthread_create (PBD_SCHED_FIFO, PBD_RT_PRI_MIDI, PBD_RT_STACKSIZE_HELP,
77 &_main_thread, pthread_process, this))
78 {
79 if (pbd_pthread_create (PBD_RT_STACKSIZE_HELP, &_main_thread, pthread_process, this)) {
80 PBD::error << _("AlsaMidiIO: Failed to create process thread.") << endmsg;
81 return -1;
82 } else {
83 PBD::warning << _("AlsaMidiIO: Cannot acquire realtime permissions.") << endmsg;
84 }
85 }
86 int timeout = 5000;
87 while (!_running && --timeout > 0) { Glib::usleep (1000); }
88 if (timeout == 0 || !_running) {
89 return -1;
90 }
91 return 0;
92 }
93
94 int
stop()95 AlsaMidiIO::stop ()
96 {
97 void *status;
98 if (!_running) {
99 return 0;
100 }
101
102 _running = false;
103
104 pthread_mutex_lock (&_notify_mutex);
105 pthread_cond_signal (&_notify_ready);
106 pthread_mutex_unlock (&_notify_mutex);
107
108 if (pthread_join (_main_thread, &status)) {
109 PBD::error << _("AlsaMidiIO: Failed to terminate.") << endmsg;
110 return -1;
111 }
112 return 0;
113 }
114
115 void
setup_timing(const size_t samples_per_period,const float samplerate)116 AlsaMidiIO::setup_timing (const size_t samples_per_period, const float samplerate)
117 {
118 _period_length_us = (double) samples_per_period * 1e6 / samplerate;
119 _sample_length_us = 1e6 / samplerate;
120 _samples_per_period = samples_per_period;
121 }
122
123 void
sync_time(const uint64_t tme)124 AlsaMidiIO::sync_time (const uint64_t tme)
125 {
126 // TODO consider a PLL, if this turns out to be the bottleneck for jitter
127 // also think about using
128 // snd_pcm_status_get_tstamp() and snd_rawmidi_status_get_tstamp()
129 // instead of monotonic clock.
130 #ifdef DEBUG_TIMING
131 double tdiff = (_clock_monotonic + _period_length_us - tme) / 1000.0;
132 if (abs(tdiff) >= .05) {
133 printf("AlsaMidiIO MJ: %.1f ms\n", tdiff);
134 }
135 #endif
136 _clock_monotonic = tme;
137 }
138
139 ///////////////////////////////////////////////////////////////////////////////
140
AlsaMidiOut()141 AlsaMidiOut::AlsaMidiOut ()
142 : AlsaMidiIO ()
143 {
144 }
145
146 int
send_event(const pframes_t time,const uint8_t * data,const size_t size)147 AlsaMidiOut::send_event (const pframes_t time, const uint8_t *data, const size_t size)
148 {
149 const uint32_t buf_size = sizeof (MidiEventHeader) + size;
150 if (_rb->write_space() < buf_size) {
151 _DEBUGPRINT("AlsaMidiOut: ring buffer overflow\n");
152 return -1;
153 }
154 struct MidiEventHeader h (_clock_monotonic + time * _sample_length_us, size);
155 _rb->write ((uint8_t*) &h, sizeof(MidiEventHeader));
156 _rb->write (data, size);
157
158 if (pthread_mutex_trylock (&_notify_mutex) == 0) {
159 pthread_cond_signal (&_notify_ready);
160 pthread_mutex_unlock (&_notify_mutex);
161 }
162 return 0;
163 }
164
165 ///////////////////////////////////////////////////////////////////////////////
166
AlsaMidiIn()167 AlsaMidiIn::AlsaMidiIn ()
168 : AlsaMidiIO ()
169 {
170 }
171
172 size_t
recv_event(pframes_t & time,uint8_t * data,size_t & size)173 AlsaMidiIn::recv_event (pframes_t &time, uint8_t *data, size_t &size)
174 {
175 const uint32_t read_space = _rb->read_space();
176 struct MidiEventHeader h(0,0);
177
178 if (read_space <= sizeof(MidiEventHeader)) {
179 return 0;
180 }
181
182 PBD::RingBuffer<uint8_t>::rw_vector vector;
183 _rb->get_read_vector(&vector);
184 if (vector.len[0] >= sizeof(MidiEventHeader)) {
185 memcpy((uint8_t*)&h, vector.buf[0], sizeof(MidiEventHeader));
186 } else {
187 if (vector.len[0] > 0) {
188 memcpy ((uint8_t*)&h, vector.buf[0], vector.len[0]);
189 }
190 assert(vector.buf[1]);
191 memcpy (((uint8_t*)&h) + vector.len[0], vector.buf[1], sizeof(MidiEventHeader) - vector.len[0]);
192 }
193
194 if (h.time >= _clock_monotonic + _period_length_us ) {
195 #ifdef DEBUG_TIMING
196 printf("AlsaMidiIn DEBUG: POSTPONE EVENT TO NEXT CYCLE: %.1f spl\n", ((h.time - _clock_monotonic) / _sample_length_us));
197 #endif
198 return 0;
199 }
200 _rb->increment_read_idx (sizeof(MidiEventHeader));
201
202 assert (h.size > 0);
203 if (h.size > size) {
204 _DEBUGPRINT("AlsaMidiIn::recv_event MIDI event too large!\n");
205 _rb->increment_read_idx (h.size);
206 return 0;
207 }
208 if (_rb->read (&data[0], h.size) != h.size) {
209 _DEBUGPRINT("AlsaMidiIn::recv_event Garbled MIDI EVENT DATA!!\n");
210 return 0;
211 }
212 if (h.time < _clock_monotonic) {
213 #ifdef DEBUG_TIMING
214 printf("AlsaMidiIn DEBUG: MIDI TIME < 0 %.1f spl\n", ((_clock_monotonic - h.time) / -_sample_length_us));
215 #endif
216 time = 0;
217 } else if (h.time >= _clock_monotonic + _period_length_us ) {
218 #ifdef DEBUG_TIMING
219 printf("AlsaMidiIn DEBUG: MIDI TIME > PERIOD %.1f spl\n", ((h.time - _clock_monotonic) / _sample_length_us));
220 #endif
221 time = _samples_per_period - 1;
222 } else {
223 time = floor ((h.time - _clock_monotonic) / _sample_length_us);
224 }
225 assert(time < _samples_per_period);
226 size = h.size;
227 return h.size;
228 }
229
230 int
queue_event(const uint64_t time,const uint8_t * data,const size_t size)231 AlsaMidiIn::queue_event (const uint64_t time, const uint8_t *data, const size_t size) {
232 const uint32_t buf_size = sizeof(MidiEventHeader) + size;
233
234 if (size == 0) {
235 return -1;
236 }
237 if (_rb->write_space() < buf_size) {
238 _DEBUGPRINT("AlsaMidiIn: ring buffer overflow\n");
239 return -1;
240 }
241 struct MidiEventHeader h (time, size);
242 _rb->write ((uint8_t*) &h, sizeof(MidiEventHeader));
243 _rb->write (data, size);
244 return 0;
245 }
246