| @@ -129,10 +129,6 @@ JackMidiRawOutputWriteQueue::SendByte(jack_nframes_t time, | |||
| case BUFFER_TOO_SMALL: | |||
| HandleWriteQueueBug(time, byte); | |||
| case OK: | |||
| jack_info("JackMidiRawOutputWriteQueue::SendByte - '%d', '%d'", time, | |||
| GetCurrentFrame()); | |||
| return true; | |||
| default: | |||
| // This is here to stop compilers from warning us about not handling | |||
| @@ -55,10 +55,11 @@ Jack::ApplyRunningStatus(jack_midi_event_t *event, | |||
| jack_nframes_t | |||
| Jack::GetCurrentFrame() | |||
| { | |||
| jack_time_t time = GetMicroSeconds(); | |||
| JackEngineControl *control = GetEngineControl(); | |||
| JackTimer timer; | |||
| control->ReadFrameTime(&timer); | |||
| return timer.Time2Frames(GetMicroSeconds(), control->fBufferSize); | |||
| return timer.Time2Frames(time, control->fBufferSize); | |||
| } | |||
| jack_nframes_t | |||
| @@ -24,6 +24,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
| #include <alsa/asoundlib.h> | |||
| #include "JackALSARawMidiDriver.h" | |||
| #include "JackALSARawMidiUtil.h" | |||
| #include "JackEngineControl.h" | |||
| #include "JackError.h" | |||
| #include "JackMidiUtil.h" | |||
| @@ -43,6 +44,7 @@ JackALSARawMidiDriver::JackALSARawMidiDriver(const char *name, | |||
| fPlaybackChannels = 0; | |||
| input_ports = 0; | |||
| output_ports = 0; | |||
| output_port_timeouts = 0; | |||
| poll_fds = 0; | |||
| } | |||
| @@ -72,7 +74,7 @@ JackALSARawMidiDriver::Attach() | |||
| if (index == NO_PORT) { | |||
| jack_error("JackALSARawMidiDriver::Attach - cannot register input " | |||
| "port with name '%s'.", name); | |||
| // X: Do we need to deallocate ports? | |||
| // XX: Do we need to deallocate ports? | |||
| return -1; | |||
| } | |||
| alias = input_port->GetAlias(); | |||
| @@ -99,7 +101,7 @@ JackALSARawMidiDriver::Attach() | |||
| if (index == NO_PORT) { | |||
| jack_error("JackALSARawMidiDriver::Attach - cannot register " | |||
| "output port with name '%s'.", name); | |||
| // X: Do we need to deallocate ports? | |||
| // XX: Do we need to deallocate ports? | |||
| return -1; | |||
| } | |||
| alias = output_port->GetAlias(); | |||
| @@ -143,21 +145,31 @@ JackALSARawMidiDriver::Execute() | |||
| { | |||
| jack_nframes_t timeout_frame = 0; | |||
| for (;;) { | |||
| jack_nframes_t process_frame; | |||
| unsigned short revents; | |||
| struct timespec timeout; | |||
| struct timespec *timeout_ptr; | |||
| if (! timeout_frame) { | |||
| timeout_ptr = 0; | |||
| } else { | |||
| // We use a relative timeout here. By the time ppoll is called, | |||
| // the wait time is larger than it needs to be. Maybe we should | |||
| // use a timerfd instead. | |||
| // 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. | |||
| // | |||
| // Also, ppoll is inefficient in certain cases. We might want to | |||
| // consider replacing it with epoll, though I'm uncertain as to | |||
| // whether or not it would be more efficient in this case. | |||
| // Another alternative would be to use 'epoll' interface. 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. | |||
| // | |||
| // Of course, a bigger 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); | |||
| @@ -171,7 +183,14 @@ JackALSARawMidiDriver::Execute() | |||
| timeout.tv_nsec = (wait_time % 1000000) * 1000; | |||
| } | |||
| } | |||
| if (ppoll(poll_fds, poll_fd_count, timeout_ptr, 0) == -1) { | |||
| 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; | |||
| } | |||
| @@ -179,44 +198,73 @@ JackALSARawMidiDriver::Execute() | |||
| strerror(errno)); | |||
| break; | |||
| } | |||
| if (timeout_ptr) { | |||
| jack_info("JackALSARawMidiDriver::Execute - '%d', '%d'", | |||
| timeout_frame, GetCurrentFrame()); | |||
| 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; | |||
| } | |||
| revents = poll_fds[0].revents; | |||
| if (revents & POLLHUP) { | |||
| // Driver is being stopped. | |||
| break; | |||
| } | |||
| if (revents & (~ POLLIN)) { | |||
| jack_error("JackALSARawMidiDriver::Execute - unexpected poll " | |||
| "event on pipe file descriptor."); | |||
| // 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; | |||
| } | |||
| timeout_frame = 0; | |||
| // Handle I/O events *and* timeout events on output ports. | |||
| for (int i = 0; i < fPlaybackChannels; i++) { | |||
| if (! output_ports[i]->ProcessALSA(fds[0], &process_frame)) { | |||
| 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; | |||
| } | |||
| if (process_frame && ((! timeout_frame) || | |||
| (process_frame < timeout_frame))) { | |||
| timeout_frame = process_frame; | |||
| 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]->ProcessALSA(&process_frame)) { | |||
| if (! input_ports[i]->ProcessPollEvents(current_frame)) { | |||
| jack_error("JackALSARawMidiDriver::Execute - a fatal error " | |||
| "occurred while processing ALSA input events."); | |||
| goto cleanup; | |||
| } | |||
| if (process_frame && ((! timeout_frame) || | |||
| (process_frame < timeout_frame))) { | |||
| timeout_frame = process_frame; | |||
| } | |||
| } | |||
| } | |||
| cleanup: | |||
| @@ -460,39 +508,27 @@ JackALSARawMidiDriver::Start() | |||
| } | |||
| try { | |||
| poll_fds = new pollfd[poll_fd_count]; | |||
| } catch (std::bad_alloc e) { | |||
| } catch (std::exception e) { | |||
| jack_error("JackALSARawMidiDriver::Start - creating poll descriptor " | |||
| "structures failed: %s", e.what()); | |||
| return -1; | |||
| } | |||
| int flags; | |||
| 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; | |||
| if (pipe(fds) == -1) { | |||
| try { | |||
| CreateNonBlockingPipe(fds); | |||
| } catch (std::exception e) { | |||
| 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; | |||
| "%s", e.what()); | |||
| goto free_output_port_timeouts; | |||
| } | |||
| poll_fds[0].events = POLLERR | POLLIN | POLLNVAL; | |||
| poll_fds[0].fd = fds[0]; | |||
| @@ -506,6 +542,7 @@ JackALSARawMidiDriver::Start() | |||
| 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 ..."); | |||
| @@ -518,11 +555,13 @@ JackALSARawMidiDriver::Start() | |||
| } | |||
| jack_error("JackALSARawMidiDriver::Start - failed to start MIDI " | |||
| "processing thread."); | |||
| close_fds: | |||
| close(fds[1]); | |||
| DestroyNonBlockingPipe(fds); | |||
| fds[1] = -1; | |||
| close(fds[0]); | |||
| fds[0] = -1; | |||
| free_output_port_timeouts: | |||
| delete[] output_port_timeouts; | |||
| output_port_timeouts = 0; | |||
| free_poll_descriptors: | |||
| delete[] poll_fds; | |||
| poll_fds = 0; | |||
| @@ -558,6 +597,10 @@ JackALSARawMidiDriver::Stop() | |||
| 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; | |||
| @@ -573,10 +616,8 @@ int | |||
| JackALSARawMidiDriver::Write() | |||
| { | |||
| jack_nframes_t buffer_size = fEngineControl->fBufferSize; | |||
| int write_fd = fds[1]; | |||
| for (int i = 0; i < fPlaybackChannels; i++) { | |||
| if (! output_ports[i]->ProcessJack(GetOutputBuffer(i), buffer_size, | |||
| write_fd)) { | |||
| if (! output_ports[i]->ProcessJack(GetOutputBuffer(i), buffer_size)) { | |||
| return -1; | |||
| } | |||
| } | |||
| @@ -40,6 +40,7 @@ namespace Jack { | |||
| int fds[2]; | |||
| JackALSARawMidiInputPort **input_ports; | |||
| JackALSARawMidiOutputPort **output_ports; | |||
| jack_nframes_t *output_port_timeouts; | |||
| nfds_t poll_fd_count; | |||
| struct pollfd *poll_fds; | |||
| JackThread *thread; | |||
| @@ -17,6 +17,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
| */ | |||
| #include <cassert> | |||
| #include <memory> | |||
| #include "JackALSARawMidiInputPort.h" | |||
| @@ -28,7 +29,7 @@ JackALSARawMidiInputPort::JackALSARawMidiInputPort(snd_rawmidi_info_t *info, | |||
| size_t index, | |||
| size_t max_bytes, | |||
| size_t max_messages): | |||
| JackALSARawMidiPort(info, index) | |||
| JackALSARawMidiPort(info, index, POLLIN) | |||
| { | |||
| alsa_event = 0; | |||
| jack_event = 0; | |||
| @@ -53,91 +54,69 @@ JackALSARawMidiInputPort::~JackALSARawMidiInputPort() | |||
| 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; | |||
| } | |||
| bool | |||
| JackALSARawMidiInputPort::ProcessALSA(jack_nframes_t *frame) | |||
| { | |||
| unsigned short revents; | |||
| if (! ProcessPollEvents(&revents)) { | |||
| return false; | |||
| } | |||
| if (alsa_event) { | |||
| *frame = EnqueueALSAEvent(); | |||
| if (*frame) { | |||
| return true; | |||
| } | |||
| } | |||
| if (revents & POLLIN) { | |||
| for (alsa_event = receive_queue->DequeueEvent(); alsa_event; | |||
| alsa_event = receive_queue->DequeueEvent()) { | |||
| *frame = EnqueueALSAEvent(); | |||
| if (*frame) { | |||
| return true; | |||
| } | |||
| } | |||
| } | |||
| *frame = raw_queue->Process(); | |||
| return true; | |||
| } | |||
| bool | |||
| JackALSARawMidiInputPort::ProcessJack(JackMidiBuffer *port_buffer, | |||
| jack_nframes_t frames) | |||
| { | |||
| write_queue->ResetMidiBuffer(port_buffer, frames); | |||
| bool dequeued = false; | |||
| if (! jack_event) { | |||
| jack_event = thread_queue->DequeueEvent(); | |||
| goto dequeue_event; | |||
| } | |||
| for (; jack_event; jack_event = thread_queue->DequeueEvent()) { | |||
| // We add `frames` so that MIDI events align with audio as closely as | |||
| // possible. | |||
| for (;;) { | |||
| switch (write_queue->EnqueueEvent(jack_event, frames)) { | |||
| case JackMidiWriteQueue::BUFFER_TOO_SMALL: | |||
| jack_error("JackALSARawMidiInputPort::ProcessJack - The write " | |||
| "queue couldn't enqueue a %d-byte event. Dropping " | |||
| "event.", jack_event->size); | |||
| // Fallthrough on purpose | |||
| // Fallthrough on purpose. | |||
| case JackMidiWriteQueue::OK: | |||
| continue; | |||
| break; | |||
| default: | |||
| ; | |||
| goto trigger_queue_event; | |||
| } | |||
| dequeue_event: | |||
| jack_event = thread_queue->DequeueEvent(); | |||
| if (! jack_event) { | |||
| break; | |||
| } | |||
| dequeued = true; | |||
| } | |||
| trigger_queue_event: | |||
| return dequeued ? TriggerQueueEvent() : true; | |||
| } | |||
| bool | |||
| JackALSARawMidiInputPort::ProcessPollEvents(jack_nframes_t current_frame) | |||
| { | |||
| if (GetQueuePollEvent() == -1) { | |||
| return false; | |||
| } | |||
| int io_event = GetIOPollEvent(); | |||
| switch (io_event) { | |||
| case -1: | |||
| return false; | |||
| case 1: | |||
| alsa_event = receive_queue->DequeueEvent(); | |||
| } | |||
| if (alsa_event) { | |||
| size_t size = alsa_event->size; | |||
| size_t space = raw_queue->GetAvailableSpace(); | |||
| bool enough_room = space >= size; | |||
| if (enough_room) { | |||
| assert(raw_queue->EnqueueEvent(current_frame, size, | |||
| alsa_event->buffer) == | |||
| JackMidiWriteQueue::OK); | |||
| alsa_event = 0; | |||
| } else if (space) { | |||
| assert(raw_queue->EnqueueEvent(current_frame, space, | |||
| alsa_event->buffer) == | |||
| JackMidiWriteQueue::OK); | |||
| alsa_event->buffer += space; | |||
| alsa_event->size -= space; | |||
| } | |||
| break; | |||
| SetIOEventsEnabled(enough_room); | |||
| } | |||
| raw_queue->Process(); | |||
| return true; | |||
| } | |||
| @@ -39,21 +39,19 @@ namespace Jack { | |||
| 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(); | |||
| bool | |||
| ProcessALSA(jack_nframes_t *frame); | |||
| ProcessJack(JackMidiBuffer *port_buffer, jack_nframes_t frames); | |||
| bool | |||
| ProcessJack(JackMidiBuffer *port_buffer, jack_nframes_t frames); | |||
| ProcessPollEvents(jack_nframes_t current_frame); | |||
| }; | |||
| @@ -17,6 +17,7 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
| */ | |||
| #include <cassert> | |||
| #include <memory> | |||
| #include "JackALSARawMidiOutputPort.h" | |||
| @@ -25,15 +26,15 @@ using Jack::JackALSARawMidiOutputPort; | |||
| JackALSARawMidiOutputPort::JackALSARawMidiOutputPort(snd_rawmidi_info_t *info, | |||
| size_t index, | |||
| size_t max_bytes_per_poll, | |||
| size_t max_bytes, | |||
| size_t max_messages): | |||
| JackALSARawMidiPort(info, index) | |||
| JackALSARawMidiPort(info, index, POLLOUT) | |||
| { | |||
| alsa_event = 0; | |||
| blocked = false; | |||
| read_queue = new JackMidiBufferReadQueue(); | |||
| std::auto_ptr<JackMidiBufferReadQueue> read_ptr(read_queue); | |||
| send_queue = new JackALSARawMidiSendQueue(rawmidi); | |||
| send_queue = new JackALSARawMidiSendQueue(rawmidi, max_bytes_per_poll); | |||
| std::auto_ptr<JackALSARawMidiSendQueue> send_ptr(send_queue); | |||
| thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages); | |||
| std::auto_ptr<JackMidiAsyncQueue> thread_ptr(thread_queue); | |||
| @@ -52,117 +53,94 @@ JackALSARawMidiOutputPort::~JackALSARawMidiOutputPort() | |||
| delete thread_queue; | |||
| } | |||
| jack_midi_event_t * | |||
| JackALSARawMidiOutputPort::DequeueALSAEvent(int read_fd) | |||
| bool | |||
| JackALSARawMidiOutputPort::ProcessJack(JackMidiBuffer *port_buffer, | |||
| jack_nframes_t frames) | |||
| { | |||
| 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)); | |||
| read_queue->ResetMidiBuffer(port_buffer); | |||
| bool enqueued = false; | |||
| for (jack_midi_event_t *event = read_queue->DequeueEvent(); event; | |||
| event = read_queue->DequeueEvent()) { | |||
| switch (thread_queue->EnqueueEvent(event, frames)) { | |||
| case JackMidiWriteQueue::BUFFER_FULL: | |||
| jack_error("JackALSARawMidiOutputPort::ProcessJack - The thread " | |||
| "queue doesn't have enough room to enqueue a %d-byte " | |||
| "event. Dropping event.", event->size); | |||
| continue; | |||
| case JackMidiWriteQueue::BUFFER_TOO_SMALL: | |||
| jack_error("JackALSARawMidiOutputPort::ProcessJack - The thread " | |||
| "queue is too small to enqueue a %d-byte event. " | |||
| "Dropping event.", event->size); | |||
| continue; | |||
| default: | |||
| enqueued = true; | |||
| } | |||
| } | |||
| return event; | |||
| return enqueued ? TriggerQueueEvent() : true; | |||
| } | |||
| bool | |||
| JackALSARawMidiOutputPort::ProcessALSA(int read_fd, jack_nframes_t *frame) | |||
| JackALSARawMidiOutputPort::ProcessPollEvents(bool handle_output, bool timeout, | |||
| jack_nframes_t *frame) | |||
| { | |||
| unsigned short revents; | |||
| if (! ProcessPollEvents(&revents)) { | |||
| int io_event; | |||
| int queue_event; | |||
| send_queue->ResetPollByteCount(); | |||
| if (! handle_output) { | |||
| assert(timeout); | |||
| goto process_raw_queue; | |||
| } | |||
| io_event = GetIOPollEvent(); | |||
| if (io_event == -1) { | |||
| return false; | |||
| } | |||
| if (blocked) { | |||
| if (! (revents & POLLOUT)) { | |||
| *frame = 0; | |||
| return true; | |||
| } | |||
| blocked = false; | |||
| queue_event = GetQueuePollEvent(); | |||
| if (queue_event == -1) { | |||
| return false; | |||
| } | |||
| if (io_event || timeout) { | |||
| process_raw_queue: | |||
| // We call the 'Process' event early because there are events waiting | |||
| // to be processed that either need to be sent now, or before now. | |||
| raw_queue->Process(); | |||
| } else if (! queue_event) { | |||
| return true; | |||
| } | |||
| if (! alsa_event) { | |||
| alsa_event = DequeueALSAEvent(read_fd); | |||
| alsa_event = thread_queue->DequeueEvent(); | |||
| } | |||
| for (; alsa_event; alsa_event = DequeueALSAEvent(read_fd)) { | |||
| for (; alsa_event; alsa_event = thread_queue->DequeueEvent()) { | |||
| 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 " | |||
| jack_error("JackALSARawMidiOutputPort::ProcessQueues - The raw " | |||
| "output queue couldn't enqueue a %d-byte event. " | |||
| "Dropping event.", alsa_event->size); | |||
| // Fallthrough on purpose | |||
| // Fallthrough on purpose. | |||
| case JackMidiWriteQueue::OK: | |||
| continue; | |||
| default: | |||
| ; | |||
| } | |||
| break; | |||
| // Try to free up some space by processing events early. | |||
| *frame = raw_queue->Process(); | |||
| switch (raw_queue->EnqueueEvent(alsa_event)) { | |||
| case JackMidiWriteQueue::BUFFER_FULL: | |||
| goto set_io_events; | |||
| case JackMidiWriteQueue::BUFFER_TOO_SMALL: | |||
| // This shouldn't happen. | |||
| assert(false); | |||
| default: | |||
| ; | |||
| } | |||
| } | |||
| process_events: | |||
| *frame = raw_queue->Process(); | |||
| blocked = send_queue->IsBlocked(); | |||
| set_io_events: | |||
| bool blocked = send_queue->IsBlocked(); | |||
| SetIOEventsEnabled(blocked); | |||
| if (blocked) { | |||
| SetPollEventMask(POLLERR | POLLNVAL | POLLOUT); | |||
| *frame = 0; | |||
| } else { | |||
| SetPollEventMask(POLLERR | POLLNVAL); | |||
| } | |||
| return true; | |||
| } | |||
| bool | |||
| 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; | |||
| ssize_t result = write(write_fd, &c, 1); | |||
| assert(result <= 1); | |||
| if (result < 0) { | |||
| jack_error("JackALSARawMidiOutputPort::ProcessJack - error " | |||
| "writing a byte to the pipe file descriptor: %s", | |||
| strerror(errno)); | |||
| return false; | |||
| } | |||
| if (! result) { | |||
| // Recoverable. | |||
| jack_error("JackALSARawMidiOutputPort::ProcessJack - Couldn't " | |||
| "write a byte to the pipe file descriptor. Dropping " | |||
| "event."); | |||
| } else { | |||
| assert(thread_queue->EnqueueEvent(event, frames) == | |||
| JackMidiWriteQueue::OK); | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| @@ -33,28 +33,26 @@ namespace Jack { | |||
| 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_per_poll=3, | |||
| size_t max_bytes=4096, | |||
| size_t max_messages=1024); | |||
| ~JackALSARawMidiOutputPort(); | |||
| bool | |||
| ProcessALSA(int read_fd, jack_nframes_t *frame); | |||
| ProcessJack(JackMidiBuffer *port_buffer, jack_nframes_t frames); | |||
| bool | |||
| ProcessJack(JackMidiBuffer *port_buffer, jack_nframes_t frames, | |||
| int write_fd); | |||
| ProcessPollEvents(bool handle_output, bool timeout, | |||
| jack_nframes_t *frame); | |||
| }; | |||
| @@ -17,16 +17,18 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
| */ | |||
| #include <cassert> | |||
| #include <stdexcept> | |||
| #include <string> | |||
| #include "JackALSARawMidiPort.h" | |||
| #include "JackALSARawMidiUtil.h" | |||
| #include "JackError.h" | |||
| using Jack::JackALSARawMidiPort; | |||
| JackALSARawMidiPort::JackALSARawMidiPort(snd_rawmidi_info_t *info, | |||
| size_t index) | |||
| size_t index, unsigned short io_mask) | |||
| { | |||
| int card = snd_rawmidi_info_get_card(info); | |||
| unsigned int device = snd_rawmidi_info_get_device(info); | |||
| @@ -50,7 +52,6 @@ JackALSARawMidiPort::JackALSARawMidiPort(snd_rawmidi_info_t *info, | |||
| 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) { | |||
| @@ -77,15 +78,6 @@ JackALSARawMidiPort::JackALSARawMidiPort(snd_rawmidi_info_t *info, | |||
| func = "snd_rawmidi_params_set_avail_min"; | |||
| goto free_params; | |||
| } | |||
| // Smallest valid buffer size. | |||
| code = snd_rawmidi_params_set_buffer_size(rawmidi, params, 32); | |||
| if (code) { | |||
| error_message = snd_strerror(code); | |||
| func = "snd_rawmidi_params_set_buffer_size"; | |||
| goto free_params; | |||
| } | |||
| code = snd_rawmidi_params_set_no_active_sensing(rawmidi, params, 1); | |||
| if (code) { | |||
| error_message = snd_strerror(code); | |||
| @@ -99,15 +91,23 @@ JackALSARawMidiPort::JackALSARawMidiPort(snd_rawmidi_info_t *info, | |||
| goto free_params; | |||
| } | |||
| snd_rawmidi_params_free(params); | |||
| num_fds = snd_rawmidi_poll_descriptors_count(rawmidi); | |||
| if (! num_fds) { | |||
| alsa_poll_fd_count = snd_rawmidi_poll_descriptors_count(rawmidi); | |||
| if (! alsa_poll_fd_count) { | |||
| error_message = "returned '0' count for poll descriptors"; | |||
| func = "snd_rawmidi_poll_descriptors_count"; | |||
| goto close; | |||
| } | |||
| try { | |||
| CreateNonBlockingPipe(fds); | |||
| } catch (std::exception e) { | |||
| error_message = e.what(); | |||
| func = "CreateNonBlockingPipe"; | |||
| goto close; | |||
| } | |||
| snprintf(alias, sizeof(alias), "%s%d", alias_prefix, index + 1); | |||
| snprintf(name, sizeof(name), "system:%d-%d %s %d %s", card + 1, device + 1, | |||
| snd_rawmidi_info_get_name(info), subdevice + 1, name_suffix); | |||
| this->io_mask = io_mask; | |||
| return; | |||
| free_params: | |||
| snd_rawmidi_params_free(params); | |||
| @@ -119,6 +119,7 @@ JackALSARawMidiPort::JackALSARawMidiPort(snd_rawmidi_info_t *info, | |||
| JackALSARawMidiPort::~JackALSARawMidiPort() | |||
| { | |||
| DestroyNonBlockingPipe(fds); | |||
| if (rawmidi) { | |||
| int code = snd_rawmidi_close(rawmidi); | |||
| if (code) { | |||
| @@ -135,6 +136,32 @@ JackALSARawMidiPort::GetAlias() | |||
| return alias; | |||
| } | |||
| int | |||
| JackALSARawMidiPort::GetIOPollEvent() | |||
| { | |||
| unsigned short events; | |||
| int code = snd_rawmidi_poll_descriptors_revents(rawmidi, alsa_poll_fds, | |||
| alsa_poll_fd_count, | |||
| &events); | |||
| if (code) { | |||
| jack_error("JackALSARawMidiPort::GetIOPollEvents - " | |||
| "snd_rawmidi_poll_descriptors_revents: %s", | |||
| snd_strerror(code)); | |||
| return -1; | |||
| } | |||
| if (events & POLLNVAL) { | |||
| jack_error("JackALSARawMidiPort::GetIOPollEvents - the file " | |||
| "descriptor is invalid."); | |||
| return -1; | |||
| } | |||
| if (events & POLLERR) { | |||
| jack_error("JackALSARawMidiPort::GetIOPollEvents - an error has " | |||
| "occurred on the device or stream."); | |||
| return -1; | |||
| } | |||
| return (events & io_mask) ? 1 : 0; | |||
| } | |||
| const char * | |||
| JackALSARawMidiPort::GetName() | |||
| { | |||
| @@ -144,48 +171,76 @@ JackALSARawMidiPort::GetName() | |||
| 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; | |||
| return alsa_poll_fd_count + 1; | |||
| } | |||
| bool | |||
| JackALSARawMidiPort::ProcessPollEvents(unsigned short *revents) | |||
| int | |||
| JackALSARawMidiPort::GetQueuePollEvent() | |||
| { | |||
| int code = snd_rawmidi_poll_descriptors_revents(rawmidi, poll_fds, num_fds, | |||
| revents); | |||
| if (code) { | |||
| jack_error("JackALSARawMidiPort::ProcessPollEvents - " | |||
| "snd_rawmidi_poll_descriptors_revents: %s", | |||
| snd_strerror(code)); | |||
| return false; | |||
| } | |||
| if ((*revents) & POLLNVAL) { | |||
| jack_error("JackALSARawMidiPort::ProcessPollEvents - the file " | |||
| unsigned short events = queue_poll_fd->revents; | |||
| if (events & POLLNVAL) { | |||
| jack_error("JackALSARawMidiPort::GetQueuePollEvents - the file " | |||
| "descriptor is invalid."); | |||
| return false; | |||
| return -1; | |||
| } | |||
| if ((*revents) & POLLERR) { | |||
| jack_error("JackALSARawMidiPort::ProcessPollEvents - an error has " | |||
| if (events & POLLERR) { | |||
| jack_error("JackALSARawMidiPort::GetQueuePollEvents - an error has " | |||
| "occurred on the device or stream."); | |||
| return false; | |||
| return -1; | |||
| } | |||
| int event = events & POLLIN ? 1 : 0; | |||
| if (event) { | |||
| char c; | |||
| ssize_t result = read(fds[0], &c, 1); | |||
| assert(result); | |||
| if (result < 0) { | |||
| jack_error("JackALSARawMidiPort::GetQueuePollEvents - error " | |||
| "reading a byte from the pipe file descriptor: %s", | |||
| strerror(errno)); | |||
| return -1; | |||
| } | |||
| } | |||
| return true; | |||
| return event; | |||
| } | |||
| void | |||
| JackALSARawMidiPort::PopulatePollDescriptors(struct pollfd *poll_fd) | |||
| { | |||
| alsa_poll_fds = poll_fd + 1; | |||
| assert(snd_rawmidi_poll_descriptors(rawmidi, alsa_poll_fds, | |||
| alsa_poll_fd_count) == | |||
| alsa_poll_fd_count); | |||
| queue_poll_fd = poll_fd; | |||
| queue_poll_fd->events = POLLERR | POLLIN | POLLNVAL; | |||
| queue_poll_fd->fd = fds[0]; | |||
| SetIOEventsEnabled(true); | |||
| } | |||
| void | |||
| JackALSARawMidiPort::SetPollEventMask(unsigned short events) | |||
| JackALSARawMidiPort::SetIOEventsEnabled(bool enabled) | |||
| { | |||
| unsigned short mask = POLLNVAL | POLLERR | (enabled ? io_mask : 0); | |||
| for (int i = 0; i < alsa_poll_fd_count; i++) { | |||
| (alsa_poll_fds + i)->events = mask; | |||
| } | |||
| } | |||
| bool | |||
| JackALSARawMidiPort::TriggerQueueEvent() | |||
| { | |||
| for (int i = 0; i < num_fds; i++) { | |||
| (poll_fds + i)->events = events; | |||
| char c; | |||
| ssize_t result = write(fds[1], &c, 1); | |||
| assert(result <= 1); | |||
| switch (result) { | |||
| case 1: | |||
| return true; | |||
| case 0: | |||
| jack_error("JackALSARawMidiPort::TriggerQueueEvent - error writing a " | |||
| "byte to the pipe file descriptor: %s", strerror(errno)); | |||
| break; | |||
| default: | |||
| jack_error("JackALSARawMidiPort::TriggerQueueEvent - couldn't write a " | |||
| "byte to the pipe file descriptor."); | |||
| } | |||
| return false; | |||
| } | |||
| @@ -32,23 +32,36 @@ namespace Jack { | |||
| private: | |||
| char alias[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; | |||
| struct pollfd *alsa_poll_fds; | |||
| int alsa_poll_fd_count; | |||
| int fds[2]; | |||
| unsigned short io_mask; | |||
| char name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; | |||
| int num_fds; | |||
| struct pollfd *poll_fds; | |||
| struct pollfd *queue_poll_fd; | |||
| protected: | |||
| snd_rawmidi_t *rawmidi; | |||
| bool | |||
| ProcessPollEvents(unsigned short *revents); | |||
| int | |||
| GetIOPollEvent(); | |||
| int | |||
| GetQueuePollEvent(); | |||
| void | |||
| SetIOEventsEnabled(bool enabled); | |||
| void | |||
| SetPollEventMask(unsigned short events); | |||
| SetQueueEventsEnabled(bool enabled); | |||
| bool | |||
| TriggerQueueEvent(); | |||
| public: | |||
| JackALSARawMidiPort(snd_rawmidi_info_t *info, size_t index); | |||
| JackALSARawMidiPort(snd_rawmidi_info_t *info, size_t index, | |||
| unsigned short io_mask); | |||
| virtual | |||
| ~JackALSARawMidiPort(); | |||
| @@ -62,7 +75,7 @@ namespace Jack { | |||
| int | |||
| GetPollDescriptorCount(); | |||
| bool | |||
| void | |||
| PopulatePollDescriptors(struct pollfd *poll_fd); | |||
| }; | |||
| @@ -24,10 +24,14 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
| using Jack::JackALSARawMidiSendQueue; | |||
| JackALSARawMidiSendQueue::JackALSARawMidiSendQueue(snd_rawmidi_t *rawmidi) | |||
| JackALSARawMidiSendQueue::JackALSARawMidiSendQueue(snd_rawmidi_t *rawmidi, | |||
| size_t bytes_per_poll) | |||
| { | |||
| assert(bytes_per_poll > 0); | |||
| this->bytes_per_poll = bytes_per_poll; | |||
| this->rawmidi = rawmidi; | |||
| blocked = false; | |||
| bytes_available = bytes_per_poll; | |||
| } | |||
| Jack::JackMidiWriteQueue::EnqueueResult | |||
| @@ -38,10 +42,14 @@ JackALSARawMidiSendQueue::EnqueueEvent(jack_nframes_t time, size_t size, | |||
| if (time > GetCurrentFrame()) { | |||
| return EVENT_EARLY; | |||
| } | |||
| if (! bytes_available) { | |||
| return BUFFER_FULL; | |||
| } | |||
| ssize_t result = snd_rawmidi_write(rawmidi, buffer, 1); | |||
| switch (result) { | |||
| case 1: | |||
| blocked = false; | |||
| bytes_available--; | |||
| return OK; | |||
| case -EWOULDBLOCK: | |||
| blocked = true; | |||
| @@ -57,3 +65,9 @@ JackALSARawMidiSendQueue::IsBlocked() | |||
| { | |||
| return blocked; | |||
| } | |||
| void | |||
| JackALSARawMidiSendQueue::ResetPollByteCount() | |||
| { | |||
| bytes_available = bytes_per_poll; | |||
| } | |||
| @@ -31,11 +31,14 @@ namespace Jack { | |||
| private: | |||
| bool blocked; | |||
| size_t bytes_available; | |||
| size_t bytes_per_poll; | |||
| snd_rawmidi_t *rawmidi; | |||
| public: | |||
| JackALSARawMidiSendQueue(snd_rawmidi_t *rawmidi); | |||
| JackALSARawMidiSendQueue(snd_rawmidi_t *rawmidi, | |||
| size_t bytes_per_poll=0); | |||
| JackMidiWriteQueue::EnqueueResult | |||
| EnqueueEvent(jack_nframes_t time, size_t size, | |||
| @@ -44,6 +47,9 @@ namespace Jack { | |||
| bool | |||
| IsBlocked(); | |||
| void | |||
| ResetPollByteCount(); | |||
| }; | |||
| } | |||
| @@ -0,0 +1,43 @@ | |||
| #include <cerrno> | |||
| #include <cstring> | |||
| #include <stdexcept> | |||
| #include <fcntl.h> | |||
| #include <unistd.h> | |||
| #include "JackALSARawMidiUtil.h" | |||
| void | |||
| Jack::CreateNonBlockingPipe(int *fds) | |||
| { | |||
| if (pipe(fds) == -1) { | |||
| throw std::runtime_error(strerror(errno)); | |||
| } | |||
| try { | |||
| SetNonBlocking(fds[0]); | |||
| SetNonBlocking(fds[1]); | |||
| } catch (...) { | |||
| close(fds[1]); | |||
| close(fds[0]); | |||
| throw; | |||
| } | |||
| } | |||
| void | |||
| Jack::DestroyNonBlockingPipe(int *fds) | |||
| { | |||
| close(fds[1]); | |||
| close(fds[0]); | |||
| } | |||
| void | |||
| Jack::SetNonBlocking(int fd) | |||
| { | |||
| int flags = fcntl(fd, F_GETFL); | |||
| if (flags == -1) { | |||
| throw std::runtime_error(strerror(errno)); | |||
| } | |||
| if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { | |||
| throw std::runtime_error(strerror(errno)); | |||
| } | |||
| } | |||
| @@ -0,0 +1,36 @@ | |||
| /* | |||
| 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. | |||
| */ | |||
| #ifndef __JackALSARawMidiUtil__ | |||
| #define __JackALSARawMidiUtil__ | |||
| namespace Jack { | |||
| void | |||
| CreateNonBlockingPipe(int *fds); | |||
| void | |||
| DestroyNonBlockingPipe(int *fds); | |||
| void | |||
| SetNonBlocking(int fd); | |||
| } | |||
| #endif | |||
| @@ -13,12 +13,14 @@ def configure(conf): | |||
| conf.define('HAVE_PPOLL', 1 ) | |||
| def create_jack_driver_obj(bld, target, sources, uselib = None): | |||
| driver = bld.new_task_gen('cxx', 'shlib') | |||
| driver.features.append('cc') | |||
| driver.env['shlib_PATTERN'] = 'jack_%s.so' | |||
| driver.defines = ['HAVE_CONFIG_H','SERVER_SIDE', 'HAVE_PPOLL'] | |||
| #driver.defines = ['HAVE_CONFIG_H','SERVER_SIDE', 'HAVE_PPOLL'] | |||
| driver.defines = ['HAVE_CONFIG_H','SERVER_SIDE', 'HAVE_PPOLL', 'HAVE_TIMERFD'] | |||
| driver.includes = ['.', '../linux', '../posix', '../common', '../common/jack', '../dbus'] | |||
| driver.target = target | |||
| driver.source = sources | |||
| @@ -62,7 +64,8 @@ def build(bld): | |||
| 'alsarawmidi/JackALSARawMidiOutputPort.cpp', | |||
| 'alsarawmidi/JackALSARawMidiPort.cpp', | |||
| 'alsarawmidi/JackALSARawMidiReceiveQueue.cpp', | |||
| 'alsarawmidi/JackALSARawMidiSendQueue.cpp' | |||
| 'alsarawmidi/JackALSARawMidiSendQueue.cpp', | |||
| 'alsarawmidi/JackALSARawMidiUtil.cpp' | |||
| ] | |||
| ffado_driver_src = ['firewire/JackFFADODriver.cpp', | |||