/* Copyright (C) 2011 Devin Anderson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include "JackALSARawMidiDriver.h" #include "JackALSARawMidiUtil.h" #include "JackEngineControl.h" #include "JackError.h" #include "JackMidiUtil.h" #include "driver_interface.h" using Jack::JackALSARawMidiDriver; JackALSARawMidiDriver::JackALSARawMidiDriver(const char *name, const char *alias, JackLockedEngine *engine, JackSynchro *table): JackMidiDriver(name, alias, engine, table) { thread = new JackThread(this); fds[0] = -1; fds[1] = -1; input_ports = 0; output_ports = 0; output_port_timeouts = 0; poll_fds = 0; } JackALSARawMidiDriver::~JackALSARawMidiDriver() { delete thread; } int JackALSARawMidiDriver::Attach() { const char *alias; jack_nframes_t buffer_size = fEngineControl->fBufferSize; jack_port_id_t index; jack_nframes_t latency = buffer_size; jack_latency_range_t latency_range; const char *name; JackPort *port; latency_range.max = latency; latency_range.min = latency; for (int i = 0; i < fCaptureChannels; i++) { JackALSARawMidiInputPort *input_port = input_ports[i]; name = input_port->GetName(); fEngine->PortRegister(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, CaptureDriverFlags, buffer_size, &index); if (index == NO_PORT) { jack_error("JackALSARawMidiDriver::Attach - cannot register input " "port with name '%s'.", name); // XX: Do we need to deallocate ports? return -1; } alias = input_port->GetAlias(); port = fGraphManager->GetPort(index); port->SetAlias(alias); port->SetLatencyRange(JackCaptureLatency, &latency_range); fEngine->PortSetDefaultMetadata(fClientControl.fRefNum, index, input_port->GetDeviceName()); fCapturePortList[i] = index; jack_info("JackALSARawMidiDriver::Attach - input port registered " "(name='%s', alias='%s').", name, alias); } if (! fEngineControl->fSyncMode) { latency += buffer_size; latency_range.max = latency; latency_range.min = latency; } for (int i = 0; i < fPlaybackChannels; i++) { JackALSARawMidiOutputPort *output_port = output_ports[i]; name = output_port->GetName(); fEngine->PortRegister(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, PlaybackDriverFlags, buffer_size, &index); if (index == NO_PORT) { jack_error("JackALSARawMidiDriver::Attach - cannot register " "output port with name '%s'.", name); // XX: Do we need to deallocate ports? return -1; } alias = output_port->GetAlias(); port = fGraphManager->GetPort(index); port->SetAlias(alias); port->SetLatencyRange(JackPlaybackLatency, &latency_range); fEngine->PortSetDefaultMetadata(fClientControl.fRefNum, index, output_port->GetDeviceName()); fPlaybackPortList[i] = index; jack_info("JackALSARawMidiDriver::Attach - output port registered " "(name='%s', alias='%s').", name, alias); } return 0; } int JackALSARawMidiDriver::Close() { // Generic MIDI driver close int result = JackMidiDriver::Close(); if (input_ports) { for (int i = 0; i < fCaptureChannels; i++) { delete input_ports[i]; } delete[] input_ports; input_ports = 0; } if (output_ports) { for (int i = 0; i < fPlaybackChannels; i++) { delete output_ports[i]; } delete[] output_ports; output_ports = 0; } return result; } bool JackALSARawMidiDriver::Execute() { jack_nframes_t timeout_frame = 0; for (;;) { struct timespec timeout; struct timespec *timeout_ptr; if (! timeout_frame) { timeout_ptr = 0; } else { // The timeout value is relative to the time that // 'GetMicroSeconds()' is called, not the time that 'poll()' is // called. This means that the amount of time that passes between // 'GetMicroSeconds()' and 'ppoll()' is time that will be lost // while waiting for 'poll() to timeout. // // I tried to replace the timeout with a 'timerfd' with absolute // times, but, strangely, it actually slowed things down, and made // the code a lot more complicated. // // I wonder about using the 'epoll' interface instead of 'ppoll()'. // The problem with the 'epoll' interface is that the timeout // resolution of 'epoll_wait()' is set in milliseconds. We need // microsecond resolution. Without microsecond resolution, we // impose the same jitter as USB MIDI. // // Another problem is that 'ppoll()' returns later than the wait // time. The problem can be minimized with high precision timers. timeout_ptr = &timeout; jack_time_t next_time = GetTimeFromFrames(timeout_frame); jack_time_t now = GetMicroSeconds(); if (next_time <= now) { timeout.tv_sec = 0; timeout.tv_nsec = 0; } else { jack_time_t wait_time = next_time - now; timeout.tv_sec = wait_time / 1000000; timeout.tv_nsec = (wait_time % 1000000) * 1000; } } int poll_result = ppoll(poll_fds, poll_fd_count, timeout_ptr, 0); // Getting the current frame value here allows us to use it for // incoming MIDI bytes. This makes sense, as the data has already // arrived at this point. jack_nframes_t current_frame = GetCurrentFrame(); if (poll_result == -1) { if (errno == EINTR) { continue; } jack_error("JackALSARawMidiDriver::Execute - poll error: %s", strerror(errno)); break; } jack_nframes_t port_timeout; timeout_frame = 0; if (! poll_result) { // No I/O events occurred. So, only handle timeout events on // output ports. for (int i = 0; i < fPlaybackChannels; i++) { port_timeout = output_port_timeouts[i]; if (port_timeout && (port_timeout <= current_frame)) { if (! output_ports[i]->ProcessPollEvents(false, true, &port_timeout)) { jack_error("JackALSARawMidiDriver::Execute - a fatal " "error occurred while processing ALSA " "output events."); goto cleanup; } output_port_timeouts[i] = port_timeout; } if (port_timeout && ((! timeout_frame) || (port_timeout < timeout_frame))) { timeout_frame = port_timeout; } } continue; } // See if it's time to shutdown. unsigned short revents = poll_fds[0].revents; if (revents) { if (revents & (~ POLLHUP)) { jack_error("JackALSARawMidiDriver::Execute - unexpected poll " "event on pipe file descriptor."); } break; } // Handle I/O events *and* timeout events on output ports. for (int i = 0; i < fPlaybackChannels; i++) { port_timeout = output_port_timeouts[i]; bool timeout = port_timeout && (port_timeout <= current_frame); if (! output_ports[i]->ProcessPollEvents(true, timeout, &port_timeout)) { jack_error("JackALSARawMidiDriver::Execute - a fatal error " "occurred while processing ALSA output events."); goto cleanup; } output_port_timeouts[i] = port_timeout; if (port_timeout && ((! timeout_frame) || (port_timeout < timeout_frame))) { timeout_frame = port_timeout; } } // Handle I/O events on input ports. We handle these last because we // already computed the arrival time above, and will impose a delay on // the events by 'period-size' frames anyway, which gives us a bit of // borrowed time. for (int i = 0; i < fCaptureChannels; i++) { if (! input_ports[i]->ProcessPollEvents(current_frame)) { jack_error("JackALSARawMidiDriver::Execute - a fatal error " "occurred while processing ALSA input events."); goto cleanup; } } } cleanup: close(fds[0]); fds[0] = -1; jack_info("JackALSARawMidiDriver::Execute - ALSA thread exiting."); return false; } void JackALSARawMidiDriver:: FreeDeviceInfo(std::vector *in_info_list, std::vector *out_info_list) { size_t length = in_info_list->size(); for (size_t i = 0; i < length; i++) { snd_rawmidi_info_free(in_info_list->at(i)); } length = out_info_list->size(); for (size_t i = 0; i < length; i++) { snd_rawmidi_info_free(out_info_list->at(i)); } } void JackALSARawMidiDriver:: GetDeviceInfo(snd_ctl_t *control, snd_rawmidi_info_t *info, std::vector *info_list) { snd_rawmidi_info_set_subdevice(info, 0); int code = snd_ctl_rawmidi_info(control, info); if (code) { if (code != -ENOENT) { HandleALSAError("GetDeviceInfo", "snd_ctl_rawmidi_info", code); } return; } unsigned int count = snd_rawmidi_info_get_subdevices_count(info); for (unsigned int i = 0; i < count; i++) { snd_rawmidi_info_set_subdevice(info, i); int code = snd_ctl_rawmidi_info(control, info); if (code) { HandleALSAError("GetDeviceInfo", "snd_ctl_rawmidi_info", code); continue; } snd_rawmidi_info_t *info_copy; code = snd_rawmidi_info_malloc(&info_copy); if (code) { HandleALSAError("GetDeviceInfo", "snd_rawmidi_info_malloc", code); continue; } snd_rawmidi_info_copy(info_copy, info); try { info_list->push_back(info_copy); } catch (std::bad_alloc &e) { snd_rawmidi_info_free(info_copy); jack_error("JackALSARawMidiDriver::GetDeviceInfo - " "std::vector::push_back: %s", e.what()); } } } void JackALSARawMidiDriver::HandleALSAError(const char *driver_func, const char *alsa_func, int code) { jack_error("JackALSARawMidiDriver::%s - %s: %s", driver_func, alsa_func, snd_strerror(code)); } bool JackALSARawMidiDriver::Init() { set_threaded_log_function(); if (thread->AcquireSelfRealTime(fEngineControl->fServerPriority + 1)) { jack_error("JackALSARawMidiDriver::Init - could not acquire realtime " "scheduling. Continuing anyway."); } return true; } int JackALSARawMidiDriver::Open(bool capturing, bool playing, int in_channels, int out_channels, bool monitor, const char *capture_driver_name, const char *playback_driver_name, jack_nframes_t capture_latency, jack_nframes_t playback_latency) { snd_rawmidi_info_t *info; int code = snd_rawmidi_info_malloc(&info); if (code) { HandleALSAError("Open", "snd_rawmidi_info_malloc", code); return -1; } std::vector in_info_list; std::vector out_info_list; for (int card = -1;;) { int code = snd_card_next(&card); if (code) { HandleALSAError("Open", "snd_card_next", code); continue; } if (card == -1) { break; } char name[32]; snprintf(name, sizeof(name), "hw:%d", card); snd_ctl_t *control; code = snd_ctl_open(&control, name, SND_CTL_NONBLOCK); if (code) { HandleALSAError("Open", "snd_ctl_open", code); continue; } for (int device = -1;;) { code = snd_ctl_rawmidi_next_device(control, &device); if (code) { HandleALSAError("Open", "snd_ctl_rawmidi_next_device", code); continue; } if (device == -1) { break; } snd_rawmidi_info_set_device(info, device); snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_INPUT); GetDeviceInfo(control, info, &in_info_list); snd_rawmidi_info_set_stream(info, SND_RAWMIDI_STREAM_OUTPUT); GetDeviceInfo(control, info, &out_info_list); } snd_ctl_close(control); } snd_rawmidi_info_free(info); size_t potential_inputs = in_info_list.size(); size_t potential_outputs = out_info_list.size(); if (! (potential_inputs || potential_outputs)) { jack_error("JackALSARawMidiDriver::Open - no ALSA raw MIDI input or " "output ports found."); FreeDeviceInfo(&in_info_list, &out_info_list); return -1; } size_t num_inputs = 0; size_t num_outputs = 0; const char *client_name = fClientControl.fName; if (potential_inputs) { try { input_ports = new JackALSARawMidiInputPort *[potential_inputs]; } catch (std::exception& e) { jack_error("JackALSARawMidiDriver::Open - while creating input " "port array: %s", e.what()); FreeDeviceInfo(&in_info_list, &out_info_list); return -1; } } if (potential_outputs) { try { output_ports = new JackALSARawMidiOutputPort *[potential_outputs]; } catch (std::exception& e) { jack_error("JackALSARawMidiDriver::Open - while creating output " "port array: %s", e.what()); FreeDeviceInfo(&in_info_list, &out_info_list); goto delete_input_ports; } } for (size_t i = 0; i < potential_inputs; i++) { snd_rawmidi_info_t *info = in_info_list.at(i); try { input_ports[num_inputs] = new JackALSARawMidiInputPort(client_name, info, i); num_inputs++; } catch (std::exception& e) { jack_error("JackALSARawMidiDriver::Open - while creating new " "JackALSARawMidiInputPort: %s", e.what()); } snd_rawmidi_info_free(info); } for (size_t i = 0; i < potential_outputs; i++) { snd_rawmidi_info_t *info = out_info_list.at(i); try { output_ports[num_outputs] = new JackALSARawMidiOutputPort(client_name, info, i); num_outputs++; } catch (std::exception& e) { jack_error("JackALSARawMidiDriver::Open - while creating new " "JackALSARawMidiOutputPort: %s", e.what()); } snd_rawmidi_info_free(info); } if (! (num_inputs || num_outputs)) { jack_error("JackALSARawMidiDriver::Open - none of the potential " "inputs or outputs were successfully opened."); } else if (JackMidiDriver::Open(capturing, playing, num_inputs, num_outputs, monitor, capture_driver_name, playback_driver_name, capture_latency, playback_latency)) { jack_error("JackALSARawMidiDriver::Open - JackMidiDriver::Open error"); } else { return 0; } if (output_ports) { for (size_t i = 0; i < num_outputs; i++) { delete output_ports[i]; } delete[] output_ports; output_ports = 0; } delete_input_ports: if (input_ports) { for (size_t i = 0; i < num_inputs; i++) { delete input_ports[i]; } delete[] input_ports; input_ports = 0; } return -1; } int JackALSARawMidiDriver::Read() { jack_nframes_t buffer_size = fEngineControl->fBufferSize; for (int i = 0; i < fCaptureChannels; i++) { if (! input_ports[i]->ProcessJack(GetInputBuffer(i), buffer_size)) { return -1; } } return 0; } int JackALSARawMidiDriver::Start() { jack_info("JackALSARawMidiDriver::Start - Starting 'alsarawmidi' driver."); JackMidiDriver::Start(); poll_fd_count = 1; for (int i = 0; i < fCaptureChannels; i++) { poll_fd_count += input_ports[i]->GetPollDescriptorCount(); } for (int i = 0; i < fPlaybackChannels; i++) { poll_fd_count += output_ports[i]->GetPollDescriptorCount(); } try { poll_fds = new pollfd[poll_fd_count]; } catch (std::exception& e) { jack_error("JackALSARawMidiDriver::Start - creating poll descriptor " "structures failed: %s", e.what()); return -1; } if (fPlaybackChannels) { try { output_port_timeouts = new jack_nframes_t[fPlaybackChannels]; } catch (std::exception& e) { jack_error("JackALSARawMidiDriver::Start - creating array for " "output port timeout values failed: %s", e.what()); goto free_poll_descriptors; } } struct pollfd *poll_fd_iter; try { CreateNonBlockingPipe(fds); } catch (std::exception& e) { jack_error("JackALSARawMidiDriver::Start - while creating wake pipe: " "%s", e.what()); goto free_output_port_timeouts; } poll_fds[0].events = POLLERR | POLLIN | POLLNVAL; poll_fds[0].fd = fds[0]; poll_fd_iter = poll_fds + 1; for (int i = 0; i < fCaptureChannels; i++) { JackALSARawMidiInputPort *input_port = input_ports[i]; input_port->PopulatePollDescriptors(poll_fd_iter); poll_fd_iter += input_port->GetPollDescriptorCount(); } for (int i = 0; i < fPlaybackChannels; i++) { JackALSARawMidiOutputPort *output_port = output_ports[i]; output_port->PopulatePollDescriptors(poll_fd_iter); poll_fd_iter += output_port->GetPollDescriptorCount(); output_port_timeouts[i] = 0; } jack_info("JackALSARawMidiDriver::Start - starting ALSA thread ..."); if (! thread->StartSync()) { jack_info("JackALSARawMidiDriver::Start - started ALSA thread."); return 0; } jack_error("JackALSARawMidiDriver::Start - failed to start MIDI " "processing thread."); DestroyNonBlockingPipe(fds); fds[1] = -1; fds[0] = -1; free_output_port_timeouts: delete[] output_port_timeouts; output_port_timeouts = 0; free_poll_descriptors: delete[] poll_fds; poll_fds = 0; return -1; } int JackALSARawMidiDriver::Stop() { jack_info("JackALSARawMidiDriver::Stop - stopping 'alsarawmidi' driver."); JackMidiDriver::Stop(); if (fds[1] != -1) { close(fds[1]); fds[1] = -1; } int result; const char *verb; switch (thread->GetStatus()) { case JackThread::kIniting: case JackThread::kStarting: result = thread->Kill(); verb = "kill"; break; case JackThread::kRunning: result = thread->Stop(); verb = "stop"; break; default: result = 0; verb = 0; } if (fds[0] != -1) { close(fds[0]); fds[0] = -1; } if (output_port_timeouts) { delete[] output_port_timeouts; output_port_timeouts = 0; } if (poll_fds) { delete[] poll_fds; poll_fds = 0; } if (result) { jack_error("JackALSARawMidiDriver::Stop - could not %s MIDI " "processing thread.", verb); } return result; } int JackALSARawMidiDriver::Write() { jack_nframes_t buffer_size = fEngineControl->fBufferSize; for (int i = 0; i < fPlaybackChannels; i++) { if (! output_ports[i]->ProcessJack(GetOutputBuffer(i), buffer_size)) { return -1; } } return 0; } #ifdef __cplusplus extern "C" { #endif // singleton kind of driver static Jack::JackALSARawMidiDriver* driver = NULL; SERVER_EXPORT jack_driver_desc_t * driver_get_descriptor() { // X: There could be parameters here regarding setting I/O buffer // sizes. I don't think MIDI drivers can accept parameters right // now without being set as the main driver. return jack_driver_descriptor_construct("alsarawmidi", JackDriverSlave, "Alternative ALSA raw MIDI backend.", NULL); } SERVER_EXPORT Jack::JackDriverClientInterface * driver_initialize(Jack::JackLockedEngine *engine, Jack::JackSynchro *table, const JSList *params) { // singleton kind of driver if (!driver) { driver = new Jack::JackALSARawMidiDriver("system_midi", "alsarawmidi", engine, table); if (driver->Open(1, 1, 0, 0, false, "midi in", "midi out", 0, 0) == 0) { return driver; } else { delete driver; return NULL; } } else { jack_info("JackALSARawMidiDriver already allocated, cannot be loaded twice"); return NULL; } } #ifdef __cplusplus } #endif