From 6e92bd8020ecf88fc8ccb6c3bec8b7ef6727480c Mon Sep 17 00:00:00 2001 From: Devin Anderson Date: Tue, 22 Mar 2011 15:10:59 -0700 Subject: [PATCH] Add 'alsarawmidi' driver, which is a slave MIDI driver that makes ALSA MIDI ports available to JACK. The driver uses the rawmidi devices, and uses the raw MIDI queues to optimize output. --- common/JackMidiRawInputWriteQueue.h | 2 +- common/JackMidiRawOutputWriteQueue.h | 2 +- example-clients/midi_latency_test.c | 4 - linux/alsarawmidi/JackALSARawMidiDriver.cpp | 586 ++++++++++++++++++ linux/alsarawmidi/JackALSARawMidiDriver.h | 79 +++ .../alsarawmidi/JackALSARawMidiInputPort.cpp | 121 ++++ linux/alsarawmidi/JackALSARawMidiInputPort.h | 43 ++ .../alsarawmidi/JackALSARawMidiOutputPort.cpp | 146 +++++ linux/alsarawmidi/JackALSARawMidiOutputPort.h | 44 ++ linux/alsarawmidi/JackALSARawMidiPort.cpp | 158 +++++ linux/alsarawmidi/JackALSARawMidiPort.h | 51 ++ .../JackALSARawMidiReceiveQueue.cpp | 35 ++ .../alsarawmidi/JackALSARawMidiReceiveQueue.h | 32 + .../alsarawmidi/JackALSARawMidiSendQueue.cpp | 40 ++ linux/alsarawmidi/JackALSARawMidiSendQueue.h | 32 + linux/wscript | 10 + 16 files changed, 1379 insertions(+), 6 deletions(-) create mode 100755 linux/alsarawmidi/JackALSARawMidiDriver.cpp create mode 100755 linux/alsarawmidi/JackALSARawMidiDriver.h create mode 100755 linux/alsarawmidi/JackALSARawMidiInputPort.cpp create mode 100755 linux/alsarawmidi/JackALSARawMidiInputPort.h create mode 100755 linux/alsarawmidi/JackALSARawMidiOutputPort.cpp create mode 100755 linux/alsarawmidi/JackALSARawMidiOutputPort.h create mode 100755 linux/alsarawmidi/JackALSARawMidiPort.cpp create mode 100755 linux/alsarawmidi/JackALSARawMidiPort.h create mode 100755 linux/alsarawmidi/JackALSARawMidiReceiveQueue.cpp create mode 100755 linux/alsarawmidi/JackALSARawMidiReceiveQueue.h create mode 100755 linux/alsarawmidi/JackALSARawMidiSendQueue.cpp create mode 100755 linux/alsarawmidi/JackALSARawMidiSendQueue.h diff --git a/common/JackMidiRawInputWriteQueue.h b/common/JackMidiRawInputWriteQueue.h index 2feff527..4639107f 100644 --- a/common/JackMidiRawInputWriteQueue.h +++ b/common/JackMidiRawInputWriteQueue.h @@ -161,7 +161,7 @@ namespace Jack { */ jack_nframes_t - Process(jack_nframes_t boundary_frame); + Process(jack_nframes_t boundary_frame=0); }; diff --git a/common/JackMidiRawOutputWriteQueue.h b/common/JackMidiRawOutputWriteQueue.h index b5e336dc..0437ec93 100644 --- a/common/JackMidiRawOutputWriteQueue.h +++ b/common/JackMidiRawOutputWriteQueue.h @@ -138,7 +138,7 @@ namespace Jack { */ jack_nframes_t - Process(jack_nframes_t boundary_frame); + Process(jack_nframes_t boundary_frame=0); }; diff --git a/example-clients/midi_latency_test.c b/example-clients/midi_latency_test.c index bcf54641..27c651c8 100644 --- a/example-clients/midi_latency_test.c +++ b/example-clients/midi_latency_test.c @@ -477,10 +477,6 @@ main(int argc, char **argv) error_source = "jack_connect"; goto deactivate_client; } - jack_port_get_latency_range(remote_in_port, JackCaptureLatency, - &in_latency_range); - jack_port_get_latency_range(remote_out_port, JackPlaybackLatency, - &out_latency_range); code = pthread_mutex_unlock(&start_mutex); if (code) { error_message = strerror(code); diff --git a/linux/alsarawmidi/JackALSARawMidiDriver.cpp b/linux/alsarawmidi/JackALSARawMidiDriver.cpp new file mode 100755 index 00000000..adbb89fb --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiDriver.cpp @@ -0,0 +1,586 @@ +#include +#include +#include + +#include + +#include "JackALSARawMidiDriver.h" +#include "JackEngineControl.h" +#include "JackError.h" +#include "JackMidiUtil.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); + fCaptureChannels = 0; + fds[0] = -1; + fds[1] = -1; + fPlaybackChannels = 0; + input_ports = 0; + output_ports = 0; + poll_fds = 0; +} + +JackALSARawMidiDriver::~JackALSARawMidiDriver() +{ + Stop(); + delete thread; + Close(); +} + +int +JackALSARawMidiDriver::Attach() +{ + jack_nframes_t buffer_size = fEngineControl->fBufferSize; + jack_port_id_t index; + const char *name; + JackPort *port; + for (int i = 0; i < fCaptureChannels; i++) { + JackALSARawMidiInputPort *input_port = input_ports[i]; + name = input_port->GetName(); + index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, + JACK_DEFAULT_MIDI_TYPE, + CaptureDriverFlags, buffer_size); + if (index == NO_PORT) { + jack_error("JackALSARawMidiDriver::Attach - cannot register input " + "port with name '%s'.", name); + // X: Do we need to deallocate ports? + return -1; + } + port = fGraphManager->GetPort(index); + port->SetAlias(input_port->GetAlias()); + port->SetLatency(buffer_size); + fCapturePortList[i] = index; + } + for (int i = 0; i < fPlaybackChannels; i++) { + JackALSARawMidiOutputPort *output_port = output_ports[i]; + name = output_port->GetName(); + index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, + JACK_DEFAULT_MIDI_TYPE, + PlaybackDriverFlags, buffer_size); + if (index == NO_PORT) { + jack_error("JackALSARawMidiDriver::Attach - cannot register " + "output port with name '%s'.", name); + // X: Do we need to deallocate ports? + return -1; + } + port = fGraphManager->GetPort(index); + port->SetAlias(output_port->GetAlias()); + port->SetLatency(buffer_size); + fPlaybackPortList[i] = index; + } + return 0; +} + +int +JackALSARawMidiDriver::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 0; +} + +bool +JackALSARawMidiDriver::Execute() +{ + jack_nframes_t timeout_frame = 0; + for (;;) { + jack_time_t wait_time; + unsigned short revents; + if (! timeout_frame) { + wait_time = 0; + } else { + jack_time_t next_time = GetTimeFromFrames(timeout_frame); + jack_time_t now = GetMicroSeconds(); + + if (next_time <= now) { + goto handle_ports; + } + wait_time = next_time - now; + } + + jack_info("Calling 'Poll' with wait time '%d'.", wait_time); + + if (Poll(wait_time) == -1) { + if (errno == EINTR) { + continue; + } + jack_error("JackALSARawMidiDriver::Execute - poll error: %s", + strerror(errno)); + break; + } + revents = poll_fds[0].revents; + if (revents & POLLHUP) { + close(fds[0]); + fds[0] = -1; + break; + } + if (revents & (~(POLLHUP | POLLIN))) { + jack_error("JackALSARawMidiDriver::Execute - unexpected poll " + "event on pipe file descriptor."); + break; + } + handle_ports: + jack_nframes_t process_frame; + jack_nframes_t timeout_frame = 0; + for (int i = 0; i < fCaptureChannels; i++) { + process_frame = input_ports[i]->ProcessALSA(); + if (process_frame && ((! timeout_frame) || + (process_frame < timeout_frame))) { + timeout_frame = process_frame; + } + } + for (int i = 0; i < fPlaybackChannels; i++) { + process_frame = output_ports[i]->ProcessALSA(fds[0]); + if (process_frame && ((! timeout_frame) || + (process_frame < timeout_frame))) { + timeout_frame = process_frame; + } + } + } + + jack_info("ALSA thread is exiting."); + + return false; +} + +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."); + return -1; + } + + // XXX: Can't use auto_ptr here. These are arrays, and require the + // delete[] operator. + std::auto_ptr input_ptr; + if (potential_inputs) { + input_ports = new JackALSARawMidiInputPort *[potential_inputs]; + input_ptr.reset(input_ports); + } + std::auto_ptr output_ptr; + if (potential_outputs) { + output_ports = new JackALSARawMidiOutputPort *[potential_outputs]; + output_ptr.reset(output_ports); + } + + size_t num_inputs = 0; + size_t num_outputs = 0; + 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(info, i); + + jack_info("JackALSARawMidiDriver::Open - Input port: card=%d, " + "device=%d, subdevice=%d, id=%s, name=%s, subdevice " + "name=%s", + snd_rawmidi_info_get_card(info), + snd_rawmidi_info_get_device(info), + snd_rawmidi_info_get_subdevice(info), + snd_rawmidi_info_get_id(info), + snd_rawmidi_info_get_name(info), + snd_rawmidi_info_get_subdevice_name(info)); + + 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(info, i); + + jack_info("JackALSARawMidiDriver::Open - Output port: card=%d, " + "device=%d, subdevice=%d, id=%s, name=%s, subdevice " + "name=%s", + snd_rawmidi_info_get_card(info), + snd_rawmidi_info_get_device(info), + snd_rawmidi_info_get_subdevice(info), + snd_rawmidi_info_get_id(info), + snd_rawmidi_info_get_name(info), + snd_rawmidi_info_get_subdevice_name(info)); + + 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) { + if (! JackMidiDriver::Open(capturing, playing, num_inputs, num_outputs, + monitor, capture_driver_name, + playback_driver_name, capture_latency, + playback_latency)) { + if (potential_inputs) { + input_ptr.release(); + } + if (potential_outputs) { + output_ptr.release(); + } + return 0; + } + jack_error("JackALSARawMidiDriver::Open - JackMidiDriver::Open error"); + } else { + jack_error("JackALSARawMidiDriver::Open - none of the potential " + "inputs or outputs were successfully opened."); + } + Close(); + return -1; +} + +#ifdef HAVE_PPOLL + +int +JackALSARawMidiDriver::Poll(jack_time_t wait_time) +{ + struct timespec timeout; + struct timespec *timeout_ptr; + if (! wait_time) { + timeout_ptr = 0; + } else { + timeout.tv_sec = wait_time / 1000000; + timeout.tv_nsec = (wait_time % 1000000) * 1000; + timeout_ptr = &timeout; + } + return ppoll(poll_fds, poll_fd_count, timeout_ptr, 0); +} + +#else + +int +JackALSARawMidiDriver::Poll(jack_time_t wait_time) +{ + int result = poll(poll_fds, poll_fd_count, + ! wait_time ? -1 : (int) (wait_time / 1000)); + if ((! result) && wait_time) { + wait_time %= 1000; + if (wait_time) { + // Cheap hack. + usleep(wait_time); + result = poll(poll_fds, poll_fd_count, 0); + } + } + return result; +} + +#endif + +int +JackALSARawMidiDriver::Read() +{ + for (int i = 0; i < fCaptureChannels; i++) { + input_ports[i]->ProcessJack(GetInputBuffer(i), + fEngineControl->fBufferSize); + } + 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::bad_alloc e) { + jack_error("JackALSARawMidiDriver::Start - creating poll descriptor " + "structures failed: %s", e.what()); + return -1; + } + int flags; + struct pollfd *poll_fd_iter; + if (pipe(fds) == -1) { + jack_error("JackALSARawMidiDriver::Start - while creating wake pipe: " + "%s", strerror(errno)); + goto free_poll_descriptors; + } + flags = fcntl(fds[0], F_GETFL); + if (flags == -1) { + jack_error("JackALSARawMidiDriver::Start = while getting flags for " + "read file descriptor: %s", strerror(errno)); + goto close_fds; + } + if (fcntl(fds[0], F_SETFL, flags | O_NONBLOCK) == -1) { + jack_error("JackALSARawMidiDriver::Start - while setting non-blocking " + "mode for read file descriptor: %s", strerror(errno)); + goto close_fds; + } + flags = fcntl(fds[1], F_GETFL); + if (flags == -1) { + jack_error("JackALSARawMidiDriver::Start = while getting flags for " + "write file descriptor: %s", strerror(errno)); + goto close_fds; + } + if (fcntl(fds[1], F_SETFL, flags | O_NONBLOCK) == -1) { + jack_error("JackALSARawMidiDriver::Start - while setting non-blocking " + "mode for write file descriptor: %s", strerror(errno)); + goto close_fds; + } + 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(); + } + + jack_info("Starting ALSA thread ..."); + + if (! thread->StartSync()) { + + jack_info("Started ALSA thread."); + + return 0; + } + jack_error("JackALSARawMidiDriver::Start - failed to start MIDI " + "processing thread."); + close_fds: + close(fds[1]); + fds[1] = -1; + close(fds[0]); + fds[0] = -1; + free_poll_descriptors: + delete[] poll_fds; + poll_fds = 0; + return -1; +} + +int +JackALSARawMidiDriver::Stop() +{ + + jack_info("Stopping 'alsarawmidi' driver."); + + 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 (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; + int write_fd = fds[1]; + for (int i = 0; i < fPlaybackChannels; i++) { + output_ports[i]->ProcessJack(GetOutputBuffer(i), buffer_size, + write_fd); + } + return 0; +} + +#ifdef __cplusplus +extern "C" { +#endif + + SERVER_EXPORT jack_driver_desc_t * + driver_get_descriptor() + { + jack_driver_desc_t *desc = + (jack_driver_desc_t *) malloc(sizeof(jack_driver_desc_t)); + if (desc) { + strcpy(desc->desc, "Alternative ALSA raw MIDI backend."); + strcpy(desc->name, "alsarawmidi"); + + // 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. + desc->nparams = 0; + desc->params = 0; + } + return desc; + } + + SERVER_EXPORT Jack::JackDriverClientInterface * + driver_initialize(Jack::JackLockedEngine *engine, Jack::JackSynchro *table, + const JSList *params) + { + Jack::JackDriverClientInterface *driver = + new Jack::JackALSARawMidiDriver("system", "alsarawmidi", engine, + table); + if (driver->Open(1, 1, 0, 0, false, "midi in", "midi out", 0, 0)) { + delete driver; + driver = 0; + } + return driver; + } + +#ifdef __cplusplus +} +#endif diff --git a/linux/alsarawmidi/JackALSARawMidiDriver.h b/linux/alsarawmidi/JackALSARawMidiDriver.h new file mode 100755 index 00000000..5a793ebb --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiDriver.h @@ -0,0 +1,79 @@ +#ifndef __JackALSARawMidiDriver__ +#define __JackALSARawMidiDriver__ + +#include + +#include +#include + +#include "JackALSARawMidiInputPort.h" +#include "JackALSARawMidiOutputPort.h" +#include "JackMidiDriver.h" +#include "JackThread.h" + +namespace Jack { + + class JackALSARawMidiDriver: + public JackMidiDriver, public JackRunnableInterface { + + private: + + int fds[2]; + JackALSARawMidiInputPort **input_ports; + JackALSARawMidiOutputPort **output_ports; + nfds_t poll_fd_count; + struct pollfd *poll_fds; + JackThread *thread; + + void + GetDeviceInfo(snd_ctl_t *control, snd_rawmidi_info_t *info, + std::vector *info_list); + + void + HandleALSAError(const char *driver_func, const char *alsa_func, + int code); + + int + Poll(jack_time_t wait_time); + + public: + + JackALSARawMidiDriver(const char *name, const char *alias, + JackLockedEngine *engine, JackSynchro *table); + ~JackALSARawMidiDriver(); + + int + Attach(); + + int + Close(); + + bool + Execute(); + + bool + Init(); + + int + Open(bool capturing, bool playing, int in_channels, int out_channels, + bool monitoring, const char *capture_driver_name, + const char *playback_driver_name, jack_nframes_t capture_latency, + jack_nframes_t playback_latency); + + int + Read(); + + int + Start(); + + int + Stop(); + + int + Write(); + + }; + +} + +#endif diff --git a/linux/alsarawmidi/JackALSARawMidiInputPort.cpp b/linux/alsarawmidi/JackALSARawMidiInputPort.cpp new file mode 100755 index 00000000..fe0d7911 --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiInputPort.cpp @@ -0,0 +1,121 @@ +#include + +#include "JackALSARawMidiInputPort.h" +#include "JackMidiUtil.h" + +using Jack::JackALSARawMidiInputPort; + +JackALSARawMidiInputPort::JackALSARawMidiInputPort(snd_rawmidi_info_t *info, + size_t index, + size_t max_bytes, + size_t max_messages): + JackALSARawMidiPort(info, index) +{ + alsa_event = 0; + jack_event = 0; + receive_queue = new JackALSARawMidiReceiveQueue(rawmidi, max_bytes); + std::auto_ptr receive_ptr(receive_queue); + thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages); + std::auto_ptr thread_ptr(thread_queue); + write_queue = new JackMidiBufferWriteQueue(); + std::auto_ptr write_ptr(write_queue); + raw_queue = new JackMidiRawInputWriteQueue(thread_queue, max_bytes, + max_messages); + write_ptr.release(); + thread_ptr.release(); + receive_ptr.release(); +} + +JackALSARawMidiInputPort::~JackALSARawMidiInputPort() +{ + delete raw_queue; + delete receive_queue; + delete thread_queue; + delete write_queue; +} + +jack_nframes_t +JackALSARawMidiInputPort::EnqueueALSAEvent() +{ + switch (raw_queue->EnqueueEvent(alsa_event)) { + case JackMidiWriteQueue::BUFFER_FULL: + // Processing events early might free up some space in the raw queue. + raw_queue->Process(); + switch (raw_queue->EnqueueEvent(alsa_event)) { + case JackMidiWriteQueue::BUFFER_TOO_SMALL: + jack_error("JackALSARawMidiInputPort::Process - **BUG** " + "JackMidiRawInputWriteQueue::EnqueueEvent returned " + "`BUFFER_FULL` and then returned `BUFFER_TOO_SMALL` " + "after a `Process()` call."); + // Fallthrough on purpose + case JackMidiWriteQueue::OK: + return 0; + default: + ; + } + break; + case JackMidiWriteQueue::BUFFER_TOO_SMALL: + jack_error("JackALSARawMidiInputPort::Execute - The thread queue " + "couldn't enqueue a %d-byte packet. Dropping event.", + alsa_event->size); + // Fallthrough on purpose + case JackMidiWriteQueue::OK: + return 0; + default: + ; + } + jack_nframes_t alsa_time = alsa_event->time; + jack_nframes_t next_time = raw_queue->Process(); + return (next_time < alsa_time) ? next_time : alsa_time; +} + +jack_nframes_t +JackALSARawMidiInputPort::ProcessALSA() +{ + unsigned short revents = ProcessPollEvents(); + jack_nframes_t frame; + if (alsa_event) { + frame = EnqueueALSAEvent(); + if (frame) { + return frame; + } + } + if (revents & POLLIN) { + for (alsa_event = receive_queue->DequeueEvent(); alsa_event; + alsa_event = receive_queue->DequeueEvent()) { + frame = EnqueueALSAEvent(); + if (frame) { + return frame; + } + } + } + return raw_queue->Process(); +} + +void +JackALSARawMidiInputPort::ProcessJack(JackMidiBuffer *port_buffer, + jack_nframes_t frames) +{ + write_queue->ResetMidiBuffer(port_buffer, frames); + if (! jack_event) { + jack_event = thread_queue->DequeueEvent(); + } + for (; jack_event; jack_event = thread_queue->DequeueEvent()) { + // We add `frames` so that MIDI events align with audio as closely as + // possible. + switch (write_queue->EnqueueEvent(jack_event->time + frames, + jack_event->size, + jack_event->buffer)) { + case JackMidiWriteQueue::BUFFER_TOO_SMALL: + jack_error("JackALSARawMidiInputPort::Process - The write queue " + "couldn't enqueue a %d-byte event. Dropping event.", + jack_event->size); + // Fallthrough on purpose + case JackMidiWriteQueue::OK: + continue; + default: + ; + } + break; + } +} diff --git a/linux/alsarawmidi/JackALSARawMidiInputPort.h b/linux/alsarawmidi/JackALSARawMidiInputPort.h new file mode 100755 index 00000000..a5d5a2da --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiInputPort.h @@ -0,0 +1,43 @@ +#ifndef __JackALSARawMidiInputPort__ +#define __JackALSARawMidiInputPort__ + +#include "JackALSARawMidiPort.h" +#include "JackALSARawMidiReceiveQueue.h" +#include "JackMidiAsyncQueue.h" +#include "JackMidiBufferWriteQueue.h" +#include "JackMidiRawInputWriteQueue.h" + +namespace Jack { + + class JackALSARawMidiInputPort: public JackALSARawMidiPort { + + private: + + jack_midi_event_t *alsa_event; + jack_midi_event_t *jack_event; + JackMidiRawInputWriteQueue *raw_queue; + JackALSARawMidiReceiveQueue *receive_queue; + JackMidiAsyncQueue *thread_queue; + JackMidiBufferWriteQueue *write_queue; + + jack_nframes_t + EnqueueALSAEvent(); + + public: + + JackALSARawMidiInputPort(snd_rawmidi_info_t *info, size_t index, + size_t max_bytes=4096, + size_t max_messages=1024); + ~JackALSARawMidiInputPort(); + + jack_nframes_t + ProcessALSA(); + + void + ProcessJack(JackMidiBuffer *port_buffer, jack_nframes_t frames); + + }; + +} + +#endif diff --git a/linux/alsarawmidi/JackALSARawMidiOutputPort.cpp b/linux/alsarawmidi/JackALSARawMidiOutputPort.cpp new file mode 100755 index 00000000..640d13f6 --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiOutputPort.cpp @@ -0,0 +1,146 @@ +#include + +#include "JackALSARawMidiOutputPort.h" + +using Jack::JackALSARawMidiOutputPort; + +JackALSARawMidiOutputPort::JackALSARawMidiOutputPort(snd_rawmidi_info_t *info, + size_t index, + size_t max_bytes, + size_t max_messages): + JackALSARawMidiPort(info, index) +{ + alsa_event = 0; + blocked = false; + read_queue = new JackMidiBufferReadQueue(); + std::auto_ptr read_ptr(read_queue); + send_queue = new JackALSARawMidiSendQueue(rawmidi); + std::auto_ptr send_ptr(send_queue); + thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages); + std::auto_ptr thread_ptr(thread_queue); + raw_queue = new JackMidiRawOutputWriteQueue(send_queue, max_bytes, + max_messages, max_messages); + thread_ptr.release(); + send_ptr.release(); + read_ptr.release(); +} + +JackALSARawMidiOutputPort::~JackALSARawMidiOutputPort() +{ + delete raw_queue; + delete read_queue; + delete send_queue; + delete thread_queue; +} + +jack_midi_event_t * +JackALSARawMidiOutputPort::DequeueALSAEvent(int read_fd) +{ + jack_midi_event_t *event = thread_queue->DequeueEvent(); + if (event) { + char c; + ssize_t result = read(read_fd, &c, 1); + if (! result) { + jack_error("JackALSARawMidiOutputPort::DequeueALSAEvent - **BUG** " + "An event was dequeued from the thread queue, but no " + "byte was available for reading from the pipe file " + "descriptor."); + } else if (result < 0) { + jack_error("JackALSARawMidiOutputPort::DequeueALSAEvent - error " + "reading a byte from the pipe file descriptor: %s", + strerror(errno)); + } + } + return event; +} + +jack_nframes_t +JackALSARawMidiOutputPort::ProcessALSA(int read_fd) +{ + unsigned short revents = ProcessPollEvents(); + if (blocked) { + if (! (revents & POLLOUT)) { + return 0; + } + blocked = false; + } + if (! alsa_event) { + alsa_event = DequeueALSAEvent(read_fd); + } + for (; alsa_event; alsa_event = DequeueALSAEvent(read_fd)) { + switch (raw_queue->EnqueueEvent(alsa_event)) { + case JackMidiWriteQueue::BUFFER_FULL: + // Try to free up some space by processing events early. + raw_queue->Process(); + switch (raw_queue->EnqueueEvent(alsa_event)) { + case JackMidiWriteQueue::BUFFER_TOO_SMALL: + jack_error("JackALSARawMidiOutputPort::ProcessALSA - **BUG** " + "JackMidiRawOutputWriteQueue::EnqueueEvent " + "returned `BUFFER_FULL`, and then returned " + "`BUFFER_TOO_SMALL` after a Process() call."); + // Fallthrough on purpose + case JackMidiWriteQueue::OK: + continue; + default: + ; + } + goto process_events; + case JackMidiWriteQueue::BUFFER_TOO_SMALL: + jack_error("JackALSARawMidiOutputPort::ProcessALSA - The raw " + "output queue couldn't enqueue a %d-byte event. " + "Dropping event.", alsa_event->size); + // Fallthrough on purpose + case JackMidiWriteQueue::OK: + continue; + default: + ; + } + break; + } + process_events: + jack_nframes_t next_frame = raw_queue->Process(); + blocked = send_queue->IsBlocked(); + if (blocked) { + SetPollEventMask(POLLERR | POLLNVAL | POLLOUT); + return 0; + } + SetPollEventMask(POLLERR | POLLNVAL); + return next_frame; +} + +void +JackALSARawMidiOutputPort::ProcessJack(JackMidiBuffer *port_buffer, + jack_nframes_t frames, int write_fd) +{ + read_queue->ResetMidiBuffer(port_buffer); + for (jack_midi_event_t *event = read_queue->DequeueEvent(); event; + event = read_queue->DequeueEvent()) { + if (event->size > thread_queue->GetAvailableSpace()) { + jack_error("JackALSARawMidiOutputPort::ProcessJack - The thread " + "queue doesn't have enough room to enqueue a %d-byte " + "event. Dropping event.", event->size); + continue; + } + char c = 1; + + jack_info("Attempting to write to file descriptor '%d'", write_fd); + + ssize_t result = write(write_fd, &c, 1); + assert(result <= 1); + if (! result) { + jack_error("JackALSARawMidiOutputPort::ProcessJack - Couldn't " + "write a byte to the pipe file descriptor. Dropping " + "event."); + } else if (result < 0) { + jack_error("JackALSARawMidiOutputPort::ProcessJack - error " + "writing a byte to the pipe file descriptor: %s", + strerror(errno)); + } else if (thread_queue->EnqueueEvent(event->time + frames, + event->size, event->buffer) != + JackMidiWriteQueue::OK) { + jack_error("JackALSARawMidiOutputPort::ProcessJack - **BUG** The " + "thread queue said it had enough space to enqueue a " + "%d-byte event, but failed to enqueue the event."); + } + } +} diff --git a/linux/alsarawmidi/JackALSARawMidiOutputPort.h b/linux/alsarawmidi/JackALSARawMidiOutputPort.h new file mode 100755 index 00000000..aea8f2ea --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiOutputPort.h @@ -0,0 +1,44 @@ +#ifndef __JackALSARawMidiOutputPort__ +#define __JackALSARawMidiOutputPort__ + +#include "JackALSARawMidiPort.h" +#include "JackALSARawMidiSendQueue.h" +#include "JackMidiAsyncQueue.h" +#include "JackMidiBufferReadQueue.h" +#include "JackMidiRawOutputWriteQueue.h" + +namespace Jack { + + class JackALSARawMidiOutputPort: public JackALSARawMidiPort { + + private: + + jack_midi_event_t *alsa_event; + bool blocked; + JackMidiRawOutputWriteQueue *raw_queue; + JackMidiBufferReadQueue *read_queue; + JackALSARawMidiSendQueue *send_queue; + JackMidiAsyncQueue *thread_queue; + + jack_midi_event_t * + DequeueALSAEvent(int read_fd); + + public: + + JackALSARawMidiOutputPort(snd_rawmidi_info_t *info, size_t index, + size_t max_bytes=4096, + size_t max_messages=1024); + ~JackALSARawMidiOutputPort(); + + jack_nframes_t + ProcessALSA(int read_fd); + + void + ProcessJack(JackMidiBuffer *port_buffer, jack_nframes_t frames, + int write_fd); + + }; + +} + +#endif diff --git a/linux/alsarawmidi/JackALSARawMidiPort.cpp b/linux/alsarawmidi/JackALSARawMidiPort.cpp new file mode 100755 index 00000000..db476003 --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiPort.cpp @@ -0,0 +1,158 @@ +#include +#include + +#include "JackALSARawMidiPort.h" +#include "JackError.h" + +using Jack::JackALSARawMidiPort; + +JackALSARawMidiPort::JackALSARawMidiPort(snd_rawmidi_info_t *info, + size_t index) +{ + char device_id[32]; + snprintf(device_id, sizeof(device_id), "hw:%d,%d,%d", + snd_rawmidi_info_get_card(info), + snd_rawmidi_info_get_device(info), + snd_rawmidi_info_get_subdevice(info)); + const char *alias_prefix; + const char *error_message; + snd_rawmidi_t **in; + snd_rawmidi_t **out; + const char *name_suffix; + if (snd_rawmidi_info_get_stream(info) == SND_RAWMIDI_STREAM_OUTPUT) { + alias_prefix = "system:midi_playback_"; + in = 0; + name_suffix = "out"; + out = &rawmidi; + } else { + alias_prefix = "system:midi_capture_"; + in = &rawmidi; + name_suffix = "in"; + out = 0; + } + const char *device_name; + const char *func; + int code = snd_rawmidi_open(in, out, device_id, SND_RAWMIDI_NONBLOCK); + if (code) { + error_message = snd_strerror(code); + func = "snd_rawmidi_open"; + goto handle_error; + } + snd_rawmidi_params_t *params; + code = snd_rawmidi_params_malloc(¶ms); + if (code) { + error_message = snd_strerror(code); + func = "snd_rawmidi_params_malloc"; + goto close; + } + code = snd_rawmidi_params_current(rawmidi, params); + if (code) { + error_message = snd_strerror(code); + func = "snd_rawmidi_params_current"; + goto close; + } + code = snd_rawmidi_params_set_avail_min(rawmidi, params, 1); + if (code) { + error_message = snd_strerror(code); + func = "snd_rawmidi_params_set_avail_min"; + goto free_params; + } + code = snd_rawmidi_params_set_no_active_sensing(rawmidi, params, 1); + if (code) { + error_message = snd_strerror(code); + func = "snd_rawmidi_params_set_no_active_sensing"; + goto free_params; + } + snd_rawmidi_params_free(params); + num_fds = snd_rawmidi_poll_descriptors_count(rawmidi); + if (! num_fds) { + error_message = "returned '0' count for poll descriptors"; + func = "snd_rawmidi_poll_descriptors_count"; + goto close; + } + snprintf(alias, sizeof(alias), "%s%d", alias_prefix, index); + device_name = snd_rawmidi_info_get_subdevice_name(info); + if (! strlen(device_name)) { + device_name = snd_rawmidi_info_get_name(info); + } + snprintf(name, sizeof(name), "system:%s %s", device_name, name_suffix); + return; + free_params: + snd_rawmidi_params_free(params); + close: + snd_rawmidi_close(rawmidi); + handle_error: + throw std::runtime_error(std::string(func) + ": " + error_message); +} + +JackALSARawMidiPort::~JackALSARawMidiPort() +{ + if (rawmidi) { + int code = snd_rawmidi_close(rawmidi); + if (code) { + jack_error("JackALSARawMidiPort::~JackALSARawMidiPort - " + "snd_rawmidi_close: %s", snd_strerror(code)); + } + rawmidi = 0; + } +} + +const char * +JackALSARawMidiPort::GetAlias() +{ + return alias; +} + +const char * +JackALSARawMidiPort::GetName() +{ + return name; +} + +int +JackALSARawMidiPort::GetPollDescriptorCount() +{ + return num_fds; +} + +bool +JackALSARawMidiPort::PopulatePollDescriptors(struct pollfd *poll_fd) +{ + bool result = snd_rawmidi_poll_descriptors(rawmidi, poll_fd, num_fds) == + num_fds; + if (result) { + poll_fds = poll_fd; + } + return result; +} + +int +JackALSARawMidiPort::ProcessPollEvents() +{ + unsigned short revents; + int code = snd_rawmidi_poll_descriptors_revents(rawmidi, poll_fds, num_fds, + &revents); + if (code) { + jack_error("JackALSARawMidiInputPort::ProcessPollEvents - " + "snd_rawmidi_poll_descriptors_revents: %s", + snd_strerror(code)); + return 0; + } + if (revents & POLLNVAL) { + jack_error("JackALSARawMidiInputPort::ProcessPollEvents - the file " + "descriptor is invalid."); + } + if (revents & POLLERR) { + jack_error("JackALSARawMidiInputPort::ProcessPollEvents - an error " + "has occurred on the device or stream."); + } + return revents; +} + +void +JackALSARawMidiPort::SetPollEventMask(unsigned short events) +{ + for (int i = 0; i < num_fds; i++) { + (poll_fds + i)->events = events; + } +} diff --git a/linux/alsarawmidi/JackALSARawMidiPort.h b/linux/alsarawmidi/JackALSARawMidiPort.h new file mode 100755 index 00000000..31ffd078 --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiPort.h @@ -0,0 +1,51 @@ +#ifndef __JackALSARawMidiPort__ +#define __JackALSARawMidiPort__ + +#include +#include + +#include "JackConstants.h" + +namespace Jack { + + class JackALSARawMidiPort { + + private: + + char alias[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; + char name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; + int num_fds; + struct pollfd *poll_fds; + + protected: + + snd_rawmidi_t *rawmidi; + + int + ProcessPollEvents(); + + void + SetPollEventMask(unsigned short events); + + public: + + JackALSARawMidiPort(snd_rawmidi_info_t *info, size_t index); + virtual ~JackALSARawMidiPort(); + + const char * + GetAlias(); + + const char * + GetName(); + + int + GetPollDescriptorCount(); + + bool + PopulatePollDescriptors(struct pollfd *poll_fd); + + }; + +} + +#endif diff --git a/linux/alsarawmidi/JackALSARawMidiReceiveQueue.cpp b/linux/alsarawmidi/JackALSARawMidiReceiveQueue.cpp new file mode 100755 index 00000000..a4b24bef --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiReceiveQueue.cpp @@ -0,0 +1,35 @@ +#include "JackALSARawMidiReceiveQueue.h" +#include "JackError.h" +#include "JackMidiUtil.h" + +using Jack::JackALSARawMidiReceiveQueue; + +JackALSARawMidiReceiveQueue:: +JackALSARawMidiReceiveQueue(snd_rawmidi_t *rawmidi, size_t buffer_size) +{ + buffer = new jack_midi_data_t[buffer_size]; + this->buffer_size = buffer_size; + this->rawmidi = rawmidi; +} + +JackALSARawMidiReceiveQueue::~JackALSARawMidiReceiveQueue() +{ + delete[] buffer; +} + +jack_midi_event_t * +JackALSARawMidiReceiveQueue::DequeueEvent() +{ + ssize_t result = snd_rawmidi_read(rawmidi, buffer, buffer_size); + if (result > 0) { + event.buffer = buffer; + event.size = (size_t) result; + event.time = GetCurrentFrame(); + return &event; + } + if (result && (result != -EWOULDBLOCK)) { + jack_error("JackALSARawMidiReceiveQueue::DequeueEvent - " + "snd_rawmidi_read: %s", snd_strerror(result)); + } + return 0; +} diff --git a/linux/alsarawmidi/JackALSARawMidiReceiveQueue.h b/linux/alsarawmidi/JackALSARawMidiReceiveQueue.h new file mode 100755 index 00000000..a76c1e54 --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiReceiveQueue.h @@ -0,0 +1,32 @@ +#ifndef __JackALSARawMidiReceiveQueue__ +#define __JackALSARawMidiReceiveQueue__ + +#include + +#include "JackMidiReceiveQueue.h" + +namespace Jack { + + class JackALSARawMidiReceiveQueue: public JackMidiReceiveQueue { + + private: + + jack_midi_data_t *buffer; + size_t buffer_size; + jack_midi_event_t event; + snd_rawmidi_t *rawmidi; + + public: + + JackALSARawMidiReceiveQueue(snd_rawmidi_t *rawmidi, + size_t buffer_size=4096); + ~JackALSARawMidiReceiveQueue(); + + jack_midi_event_t * + DequeueEvent(); + + }; + +} + +#endif diff --git a/linux/alsarawmidi/JackALSARawMidiSendQueue.cpp b/linux/alsarawmidi/JackALSARawMidiSendQueue.cpp new file mode 100755 index 00000000..5f314263 --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiSendQueue.cpp @@ -0,0 +1,40 @@ +#include + +#include "JackALSARawMidiSendQueue.h" +#include "JackMidiUtil.h" + +using Jack::JackALSARawMidiSendQueue; + +JackALSARawMidiSendQueue::JackALSARawMidiSendQueue(snd_rawmidi_t *rawmidi) +{ + this->rawmidi = rawmidi; + blocked = false; +} + +Jack::JackMidiWriteQueue::EnqueueResult +JackALSARawMidiSendQueue::EnqueueEvent(jack_nframes_t time, size_t size, + jack_midi_data_t *buffer) +{ + assert(size == 1); + if (time > GetCurrentFrame()) { + return EVENT_EARLY; + } + ssize_t result = snd_rawmidi_write(rawmidi, buffer, 1); + switch (result) { + case 1: + blocked = false; + return OK; + case -EWOULDBLOCK: + blocked = true; + return BUFFER_FULL; + } + jack_error("JackALSARawMidiSendQueue::EnqueueEvent - snd_rawmidi_write: " + "%s", snd_strerror(result)); + return ERROR; +} + +bool +JackALSARawMidiSendQueue::IsBlocked() +{ + return blocked; +} diff --git a/linux/alsarawmidi/JackALSARawMidiSendQueue.h b/linux/alsarawmidi/JackALSARawMidiSendQueue.h new file mode 100755 index 00000000..f3f6542c --- /dev/null +++ b/linux/alsarawmidi/JackALSARawMidiSendQueue.h @@ -0,0 +1,32 @@ +#ifndef __JackALSARawMidiSendQueue__ +#define __JackALSARawMidiSendQueue__ + +#include + +#include "JackMidiSendQueue.h" + +namespace Jack { + + class JackALSARawMidiSendQueue: public JackMidiSendQueue { + + private: + + bool blocked; + snd_rawmidi_t *rawmidi; + + public: + + JackALSARawMidiSendQueue(snd_rawmidi_t *rawmidi); + + JackMidiWriteQueue::EnqueueResult + EnqueueEvent(jack_nframes_t time, size_t size, + jack_midi_data_t *buffer); + + bool + IsBlocked(); + + }; + +} + +#endif diff --git a/linux/wscript b/linux/wscript index c28da170..9c45817b 100644 --- a/linux/wscript +++ b/linux/wscript @@ -57,6 +57,14 @@ def build(bld): 'alsa/ice1712.c' ] + alsarawmidi_driver_src = ['alsarawmidi/JackALSARawMidiDriver.cpp', + 'alsarawmidi/JackALSARawMidiInputPort.cpp', + 'alsarawmidi/JackALSARawMidiOutputPort.cpp', + 'alsarawmidi/JackALSARawMidiPort.cpp', + 'alsarawmidi/JackALSARawMidiReceiveQueue.cpp', + 'alsarawmidi/JackALSARawMidiSendQueue.cpp' + ] + ffado_driver_src = ['firewire/JackFFADODriver.cpp', 'firewire/JackFFADOMidiInputPort.cpp', 'firewire/JackFFADOMidiOutputPort.cpp', @@ -66,6 +74,8 @@ def build(bld): if bld.env['BUILD_DRIVER_ALSA'] == True: create_jack_driver_obj(bld, 'alsa', alsa_driver_src, "ALSA") + create_jack_driver_obj(bld, 'alsarawmidi', alsarawmidi_driver_src, + "ALSA") if bld.env['BUILD_DRIVER_FREEBOB'] == True: create_jack_driver_obj(bld, 'freebob', 'freebob/JackFreebobDriver.cpp', "LIBFREEBOB")