waf: configure option for enforcing autostart method
[jackdbus.git] / linux / alsarawmidi / JackALSARawMidiDriver.cpp
blobb9288bd56c2028fa01b20d3f4bd3cbd2e3ee6114
1 /*
2 Copyright (C) 2011 Devin Anderson
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.
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.
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 #include <memory>
21 #include <new>
22 #include <stdexcept>
24 #include <alsa/asoundlib.h>
26 #include "JackALSARawMidiDriver.h"
27 #include "JackALSARawMidiUtil.h"
28 #include "JackEngineControl.h"
29 #include "JackError.h"
30 #include "JackMidiUtil.h"
31 #include "driver_interface.h"
33 using Jack::JackALSARawMidiDriver;
35 JackALSARawMidiDriver::JackALSARawMidiDriver(const char *name,
36 const char *alias,
37 JackLockedEngine *engine,
38 JackSynchro *table):
39 JackMidiDriver(name, alias, engine, table)
41 thread = new JackThread(this);
42 fds[0] = -1;
43 fds[1] = -1;
44 input_ports = 0;
45 output_ports = 0;
46 output_port_timeouts = 0;
47 poll_fds = 0;
50 JackALSARawMidiDriver::~JackALSARawMidiDriver()
52 delete thread;
55 int
56 JackALSARawMidiDriver::Attach()
58 const char *alias;
59 jack_nframes_t buffer_size = fEngineControl->fBufferSize;
60 jack_port_id_t index;
61 jack_nframes_t latency = buffer_size;
62 jack_latency_range_t latency_range;
63 const char *name;
64 JackPort *port;
65 latency_range.max = latency;
66 latency_range.min = latency;
67 for (int i = 0; i < fCaptureChannels; i++) {
68 JackALSARawMidiInputPort *input_port = input_ports[i];
69 name = input_port->GetName();
70 fEngine->PortRegister(fClientControl.fRefNum, name,
71 JACK_DEFAULT_MIDI_TYPE,
72 CaptureDriverFlags, buffer_size, &index);
73 if (index == NO_PORT) {
74 jack_error("JackALSARawMidiDriver::Attach - cannot register input "
75 "port with name '%s'.", name);
76 // XX: Do we need to deallocate ports?
77 return -1;
79 alias = input_port->GetAlias();
80 port = fGraphManager->GetPort(index);
81 port->SetAlias(alias);
82 port->SetLatencyRange(JackCaptureLatency, &latency_range);
83 fCapturePortList[i] = index;
85 jack_info("JackALSARawMidiDriver::Attach - input port registered "
86 "(name='%s', alias='%s').", name, alias);
88 if (! fEngineControl->fSyncMode) {
89 latency += buffer_size;
90 latency_range.max = latency;
91 latency_range.min = latency;
93 for (int i = 0; i < fPlaybackChannels; i++) {
94 JackALSARawMidiOutputPort *output_port = output_ports[i];
95 name = output_port->GetName();
96 fEngine->PortRegister(fClientControl.fRefNum, name,
97 JACK_DEFAULT_MIDI_TYPE,
98 PlaybackDriverFlags, buffer_size, &index);
99 if (index == NO_PORT) {
100 jack_error("JackALSARawMidiDriver::Attach - cannot register "
101 "output port with name '%s'.", name);
102 // XX: Do we need to deallocate ports?
103 return -1;
105 alias = output_port->GetAlias();
106 port = fGraphManager->GetPort(index);
107 port->SetAlias(alias);
108 port->SetLatencyRange(JackPlaybackLatency, &latency_range);
109 fPlaybackPortList[i] = index;
111 jack_info("JackALSARawMidiDriver::Attach - output port registered "
112 "(name='%s', alias='%s').", name, alias);
114 return 0;
118 JackALSARawMidiDriver::Close()
120 // Generic MIDI driver close
121 int result = JackMidiDriver::Close();
123 if (input_ports) {
124 for (int i = 0; i < fCaptureChannels; i++) {
125 delete input_ports[i];
127 delete[] input_ports;
128 input_ports = 0;
130 if (output_ports) {
131 for (int i = 0; i < fPlaybackChannels; i++) {
132 delete output_ports[i];
134 delete[] output_ports;
135 output_ports = 0;
137 return result;
140 bool
141 JackALSARawMidiDriver::Execute()
143 jack_nframes_t timeout_frame = 0;
144 for (;;) {
145 struct timespec timeout;
146 struct timespec *timeout_ptr;
147 if (! timeout_frame) {
148 timeout_ptr = 0;
149 } else {
151 // The timeout value is relative to the time that
152 // 'GetMicroSeconds()' is called, not the time that 'poll()' is
153 // called. This means that the amount of time that passes between
154 // 'GetMicroSeconds()' and 'ppoll()' is time that will be lost
155 // while waiting for 'poll() to timeout.
157 // I tried to replace the timeout with a 'timerfd' with absolute
158 // times, but, strangely, it actually slowed things down, and made
159 // the code a lot more complicated.
161 // I wonder about using the 'epoll' interface instead of 'ppoll()'.
162 // The problem with the 'epoll' interface is that the timeout
163 // resolution of 'epoll_wait()' is set in milliseconds. We need
164 // microsecond resolution. Without microsecond resolution, we
165 // impose the same jitter as USB MIDI.
167 // Another problem is that 'ppoll()' returns later than the wait
168 // time. The problem can be minimized with high precision timers.
170 timeout_ptr = &timeout;
171 jack_time_t next_time = GetTimeFromFrames(timeout_frame);
172 jack_time_t now = GetMicroSeconds();
173 if (next_time <= now) {
174 timeout.tv_sec = 0;
175 timeout.tv_nsec = 0;
176 } else {
177 jack_time_t wait_time = next_time - now;
178 timeout.tv_sec = wait_time / 1000000;
179 timeout.tv_nsec = (wait_time % 1000000) * 1000;
182 int poll_result = ppoll(poll_fds, poll_fd_count, timeout_ptr, 0);
184 // Getting the current frame value here allows us to use it for
185 // incoming MIDI bytes. This makes sense, as the data has already
186 // arrived at this point.
187 jack_nframes_t current_frame = GetCurrentFrame();
189 if (poll_result == -1) {
190 if (errno == EINTR) {
191 continue;
193 jack_error("JackALSARawMidiDriver::Execute - poll error: %s",
194 strerror(errno));
195 break;
197 jack_nframes_t port_timeout;
198 timeout_frame = 0;
199 if (! poll_result) {
201 // No I/O events occurred. So, only handle timeout events on
202 // output ports.
204 for (int i = 0; i < fPlaybackChannels; i++) {
205 port_timeout = output_port_timeouts[i];
206 if (port_timeout && (port_timeout <= current_frame)) {
207 if (! output_ports[i]->ProcessPollEvents(false, true,
208 &port_timeout)) {
209 jack_error("JackALSARawMidiDriver::Execute - a fatal "
210 "error occurred while processing ALSA "
211 "output events.");
212 goto cleanup;
214 output_port_timeouts[i] = port_timeout;
216 if (port_timeout && ((! timeout_frame) ||
217 (port_timeout < timeout_frame))) {
218 timeout_frame = port_timeout;
221 continue;
224 // See if it's time to shutdown.
226 unsigned short revents = poll_fds[0].revents;
227 if (revents) {
228 if (revents & (~ POLLHUP)) {
229 jack_error("JackALSARawMidiDriver::Execute - unexpected poll "
230 "event on pipe file descriptor.");
232 break;
235 // Handle I/O events *and* timeout events on output ports.
237 for (int i = 0; i < fPlaybackChannels; i++) {
238 port_timeout = output_port_timeouts[i];
239 bool timeout = port_timeout && (port_timeout <= current_frame);
240 if (! output_ports[i]->ProcessPollEvents(true, timeout,
241 &port_timeout)) {
242 jack_error("JackALSARawMidiDriver::Execute - a fatal error "
243 "occurred while processing ALSA output events.");
244 goto cleanup;
246 output_port_timeouts[i] = port_timeout;
247 if (port_timeout && ((! timeout_frame) ||
248 (port_timeout < timeout_frame))) {
249 timeout_frame = port_timeout;
253 // Handle I/O events on input ports. We handle these last because we
254 // already computed the arrival time above, and will impose a delay on
255 // the events by 'period-size' frames anyway, which gives us a bit of
256 // borrowed time.
258 for (int i = 0; i < fCaptureChannels; i++) {
259 if (! input_ports[i]->ProcessPollEvents(current_frame)) {
260 jack_error("JackALSARawMidiDriver::Execute - a fatal error "
261 "occurred while processing ALSA input events.");
262 goto cleanup;
266 cleanup:
267 close(fds[0]);
268 fds[0] = -1;
270 jack_info("JackALSARawMidiDriver::Execute - ALSA thread exiting.");
272 return false;
275 void
276 JackALSARawMidiDriver::
277 FreeDeviceInfo(std::vector<snd_rawmidi_info_t *> *in_info_list,
278 std::vector<snd_rawmidi_info_t *> *out_info_list)
280 size_t length = in_info_list->size();
281 for (size_t i = 0; i < length; i++) {
282 snd_rawmidi_info_free(in_info_list->at(i));
284 length = out_info_list->size();
285 for (size_t i = 0; i < length; i++) {
286 snd_rawmidi_info_free(out_info_list->at(i));
290 void
291 JackALSARawMidiDriver::
292 GetDeviceInfo(snd_ctl_t *control, snd_rawmidi_info_t *info,
293 std::vector<snd_rawmidi_info_t *> *info_list)
295 snd_rawmidi_info_set_subdevice(info, 0);
296 int code = snd_ctl_rawmidi_info(control, info);
297 if (code) {
298 if (code != -ENOENT) {
299 HandleALSAError("GetDeviceInfo", "snd_ctl_rawmidi_info", code);
301 return;
303 unsigned int count = snd_rawmidi_info_get_subdevices_count(info);
304 for (unsigned int i = 0; i < count; i++) {
305 snd_rawmidi_info_set_subdevice(info, i);
306 int code = snd_ctl_rawmidi_info(control, info);
307 if (code) {
308 HandleALSAError("GetDeviceInfo", "snd_ctl_rawmidi_info", code);
309 continue;
311 snd_rawmidi_info_t *info_copy;
312 code = snd_rawmidi_info_malloc(&info_copy);
313 if (code) {
314 HandleALSAError("GetDeviceInfo", "snd_rawmidi_info_malloc", code);
315 continue;
317 snd_rawmidi_info_copy(info_copy, info);
318 try {
319 info_list->push_back(info_copy);
320 } catch (std::bad_alloc &e) {
321 snd_rawmidi_info_free(info_copy);
322 jack_error("JackALSARawMidiDriver::GetDeviceInfo - "
323 "std::vector::push_back: %s", e.what());
328 void
329 JackALSARawMidiDriver::HandleALSAError(const char *driver_func,
330 const char *alsa_func, int code)
332 jack_error("JackALSARawMidiDriver::%s - %s: %s", driver_func, alsa_func,
333 snd_strerror(code));
336 bool
337 JackALSARawMidiDriver::Init()
339 set_threaded_log_function();
340 if (thread->AcquireSelfRealTime(fEngineControl->fServerPriority + 1)) {
341 jack_error("JackALSARawMidiDriver::Init - could not acquire realtime "
342 "scheduling. Continuing anyway.");
344 return true;
348 JackALSARawMidiDriver::Open(bool capturing, bool playing, int in_channels,
349 int out_channels, bool monitor,
350 const char *capture_driver_name,
351 const char *playback_driver_name,
352 jack_nframes_t capture_latency,
353 jack_nframes_t playback_latency)
355 snd_rawmidi_info_t *info;
356 int code = snd_rawmidi_info_malloc(&info);
357 if (code) {
358 HandleALSAError("Open", "snd_rawmidi_info_malloc", code);
359 return -1;
361 std::vector<snd_rawmidi_info_t *> in_info_list;
362 std::vector<snd_rawmidi_info_t *> out_info_list;
363 for (int card = -1;;) {
364 int code = snd_card_next(&card);
365 if (code) {
366 HandleALSAError("Open", "snd_card_next", code);
367 continue;
369 if (card == -1) {
370 break;
372 char name[32];
373 snprintf(name, sizeof(name), "hw:%d", card);
374 snd_ctl_t *control;
375 code = snd_ctl_open(&control, name, SND_CTL_NONBLOCK);
376 if (code) {
377 HandleALSAError("Open", "snd_ctl_open", code);
378 continue;
380 for (int device = -1;;) {
381 code = snd_ctl_rawmidi_next_device(control, &device);
382 if (code) {
383 HandleALSAError("Open", "snd_ctl_rawmidi_next_device", code);
384 continue;
386 if (device == -1) {
387 break;
389 snd_rawmidi_info_set_device(info, device);
390 snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_INPUT);
391 GetDeviceInfo(control, info, &in_info_list);
392 snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT);
393 GetDeviceInfo(control, info, &out_info_list);
395 snd_ctl_close(control);
397 snd_rawmidi_info_free(info);
398 size_t potential_inputs = in_info_list.size();
399 size_t potential_outputs = out_info_list.size();
400 if (! (potential_inputs || potential_outputs)) {
401 jack_error("JackALSARawMidiDriver::Open - no ALSA raw MIDI input or "
402 "output ports found.");
403 FreeDeviceInfo(&in_info_list, &out_info_list);
404 return -1;
406 size_t num_inputs = 0;
407 size_t num_outputs = 0;
408 if (potential_inputs) {
409 try {
410 input_ports = new JackALSARawMidiInputPort *[potential_inputs];
411 } catch (std::exception e) {
412 jack_error("JackALSARawMidiDriver::Open - while creating input "
413 "port array: %s", e.what());
414 FreeDeviceInfo(&in_info_list, &out_info_list);
415 return -1;
418 if (potential_outputs) {
419 try {
420 output_ports = new JackALSARawMidiOutputPort *[potential_outputs];
421 } catch (std::exception e) {
422 jack_error("JackALSARawMidiDriver::Open - while creating output "
423 "port array: %s", e.what());
424 FreeDeviceInfo(&in_info_list, &out_info_list);
425 goto delete_input_ports;
428 for (size_t i = 0; i < potential_inputs; i++) {
429 snd_rawmidi_info_t *info = in_info_list.at(i);
430 try {
431 input_ports[num_inputs] = new JackALSARawMidiInputPort(info, i);
432 num_inputs++;
433 } catch (std::exception e) {
434 jack_error("JackALSARawMidiDriver::Open - while creating new "
435 "JackALSARawMidiInputPort: %s", e.what());
437 snd_rawmidi_info_free(info);
439 for (size_t i = 0; i < potential_outputs; i++) {
440 snd_rawmidi_info_t *info = out_info_list.at(i);
441 try {
442 output_ports[num_outputs] = new JackALSARawMidiOutputPort(info, i);
443 num_outputs++;
444 } catch (std::exception e) {
445 jack_error("JackALSARawMidiDriver::Open - while creating new "
446 "JackALSARawMidiOutputPort: %s", e.what());
448 snd_rawmidi_info_free(info);
450 if (! (num_inputs || num_outputs)) {
451 jack_error("JackALSARawMidiDriver::Open - none of the potential "
452 "inputs or outputs were successfully opened.");
453 } else if (JackMidiDriver::Open(capturing, playing, num_inputs,
454 num_outputs, monitor, capture_driver_name,
455 playback_driver_name, capture_latency,
456 playback_latency)) {
457 jack_error("JackALSARawMidiDriver::Open - JackMidiDriver::Open error");
458 } else {
459 return 0;
461 if (output_ports) {
462 for (size_t i = 0; i < num_outputs; i++) {
463 delete output_ports[i];
465 delete[] output_ports;
466 output_ports = 0;
468 delete_input_ports:
469 if (input_ports) {
470 for (size_t i = 0; i < num_inputs; i++) {
471 delete input_ports[i];
473 delete[] input_ports;
474 input_ports = 0;
476 return -1;
480 JackALSARawMidiDriver::Read()
482 jack_nframes_t buffer_size = fEngineControl->fBufferSize;
483 for (int i = 0; i < fCaptureChannels; i++) {
484 if (! input_ports[i]->ProcessJack(GetInputBuffer(i), buffer_size)) {
485 return -1;
488 return 0;
492 JackALSARawMidiDriver::Start()
495 jack_info("JackALSARawMidiDriver::Start - Starting 'alsarawmidi' driver.");
497 JackMidiDriver::Start();
498 poll_fd_count = 1;
499 for (int i = 0; i < fCaptureChannels; i++) {
500 poll_fd_count += input_ports[i]->GetPollDescriptorCount();
502 for (int i = 0; i < fPlaybackChannels; i++) {
503 poll_fd_count += output_ports[i]->GetPollDescriptorCount();
505 try {
506 poll_fds = new pollfd[poll_fd_count];
507 } catch (std::exception e) {
508 jack_error("JackALSARawMidiDriver::Start - creating poll descriptor "
509 "structures failed: %s", e.what());
510 return -1;
512 if (fPlaybackChannels) {
513 try {
514 output_port_timeouts = new jack_nframes_t[fPlaybackChannels];
515 } catch (std::exception e) {
516 jack_error("JackALSARawMidiDriver::Start - creating array for "
517 "output port timeout values failed: %s", e.what());
518 goto free_poll_descriptors;
521 struct pollfd *poll_fd_iter;
522 try {
523 CreateNonBlockingPipe(fds);
524 } catch (std::exception e) {
525 jack_error("JackALSARawMidiDriver::Start - while creating wake pipe: "
526 "%s", e.what());
527 goto free_output_port_timeouts;
529 poll_fds[0].events = POLLERR | POLLIN | POLLNVAL;
530 poll_fds[0].fd = fds[0];
531 poll_fd_iter = poll_fds + 1;
532 for (int i = 0; i < fCaptureChannels; i++) {
533 JackALSARawMidiInputPort *input_port = input_ports[i];
534 input_port->PopulatePollDescriptors(poll_fd_iter);
535 poll_fd_iter += input_port->GetPollDescriptorCount();
537 for (int i = 0; i < fPlaybackChannels; i++) {
538 JackALSARawMidiOutputPort *output_port = output_ports[i];
539 output_port->PopulatePollDescriptors(poll_fd_iter);
540 poll_fd_iter += output_port->GetPollDescriptorCount();
541 output_port_timeouts[i] = 0;
544 jack_info("JackALSARawMidiDriver::Start - starting ALSA thread ...");
546 if (! thread->StartSync()) {
548 jack_info("JackALSARawMidiDriver::Start - started ALSA thread.");
550 return 0;
552 jack_error("JackALSARawMidiDriver::Start - failed to start MIDI "
553 "processing thread.");
555 DestroyNonBlockingPipe(fds);
556 fds[1] = -1;
557 fds[0] = -1;
558 free_output_port_timeouts:
559 delete[] output_port_timeouts;
560 output_port_timeouts = 0;
561 free_poll_descriptors:
562 delete[] poll_fds;
563 poll_fds = 0;
564 return -1;
568 JackALSARawMidiDriver::Stop()
570 jack_info("JackALSARawMidiDriver::Stop - stopping 'alsarawmidi' driver.");
571 JackMidiDriver::Stop();
573 if (fds[1] != -1) {
574 close(fds[1]);
575 fds[1] = -1;
577 int result;
578 const char *verb;
579 switch (thread->GetStatus()) {
580 case JackThread::kIniting:
581 case JackThread::kStarting:
582 result = thread->Kill();
583 verb = "kill";
584 break;
585 case JackThread::kRunning:
586 result = thread->Stop();
587 verb = "stop";
588 break;
589 default:
590 result = 0;
591 verb = 0;
593 if (fds[0] != -1) {
594 close(fds[0]);
595 fds[0] = -1;
597 if (output_port_timeouts) {
598 delete[] output_port_timeouts;
599 output_port_timeouts = 0;
601 if (poll_fds) {
602 delete[] poll_fds;
603 poll_fds = 0;
605 if (result) {
606 jack_error("JackALSARawMidiDriver::Stop - could not %s MIDI "
607 "processing thread.", verb);
609 return result;
613 JackALSARawMidiDriver::Write()
615 jack_nframes_t buffer_size = fEngineControl->fBufferSize;
616 for (int i = 0; i < fPlaybackChannels; i++) {
617 if (! output_ports[i]->ProcessJack(GetOutputBuffer(i), buffer_size)) {
618 return -1;
621 return 0;
624 #ifdef __cplusplus
625 extern "C" {
626 #endif
628 SERVER_EXPORT jack_driver_desc_t *
629 driver_get_descriptor()
631 // X: There could be parameters here regarding setting I/O buffer
632 // sizes. I don't think MIDI drivers can accept parameters right
633 // now without being set as the main driver.
635 return jack_driver_descriptor_construct("alsarawmidi", JackDriverSlave, "Alternative ALSA raw MIDI backend.", NULL);
638 SERVER_EXPORT Jack::JackDriverClientInterface *
639 driver_initialize(Jack::JackLockedEngine *engine, Jack::JackSynchro *table,
640 const JSList *params)
642 Jack::JackDriverClientInterface *driver =
643 new Jack::JackALSARawMidiDriver("system_midi", "alsarawmidi",
644 engine, table);
645 if (driver->Open(1, 1, 0, 0, false, "midi in", "midi out", 0, 0)) {
646 delete driver;
647 driver = 0;
649 return driver;
652 #ifdef __cplusplus
654 #endif