From 23a88af2534084e66a3da834f81122f38237b940 Mon Sep 17 00:00:00 2001 From: sletz Date: Mon, 30 Nov 2009 22:06:01 +0000 Subject: [PATCH] Devin Anderson patch for Jack FFADO driver issues with lost MIDI bytes between periods (and more). git-svn-id: http://subversion.jackaudio.org/jack/jack2/trunk/jackmp@3833 0c269be4-1314-0410-8aa9-9f06e86f4224 --- ChangeLog | 7 +- common/JackPhysicalMidiInput.cpp | 287 ++++++++++++++++++++++ common/JackPhysicalMidiInput.h | 146 +++++++++++ common/JackPhysicalMidiOutput.cpp | 321 +++++++++++++++++++++++++ common/JackPhysicalMidiOutput.h | 118 +++++++++ common/wscript | 2 + linux/firewire/JackFFADODriver.cpp | 118 ++------- linux/firewire/JackFFADOMidiInput.cpp | 59 +++++ linux/firewire/JackFFADOMidiInput.h | 54 +++++ linux/firewire/JackFFADOMidiOutput.cpp | 60 +++++ linux/firewire/JackFFADOMidiOutput.h | 57 +++++ linux/firewire/ffado_driver.h | 14 +- linux/wscript | 9 +- 13 files changed, 1148 insertions(+), 104 deletions(-) create mode 100644 common/JackPhysicalMidiInput.cpp create mode 100644 common/JackPhysicalMidiInput.h create mode 100644 common/JackPhysicalMidiOutput.cpp create mode 100644 common/JackPhysicalMidiOutput.h create mode 100644 linux/firewire/JackFFADOMidiInput.cpp create mode 100644 linux/firewire/JackFFADOMidiInput.h create mode 100644 linux/firewire/JackFFADOMidiOutput.cpp create mode 100644 linux/firewire/JackFFADOMidiOutput.h diff --git a/ChangeLog b/ChangeLog index 6bc8e85a..1f5aca52 100644 --- a/ChangeLog +++ b/ChangeLog @@ -20,12 +20,17 @@ Florian Faber Michael Voigt Torben Hohn Paul Davis -Peter L Jones +Peter L Jones +Devin Anderson --------------------------- Jackdmp changes log --------------------------- +2009-11-30 Stephane Letz + + * Devin Anderson patch for Jack FFADO driver issues with lost MIDI bytes between periods (and more). + 2009-11-29 Stephane Letz * More robust sample rate change handling code in JackCoreAudioDriver. diff --git a/common/JackPhysicalMidiInput.cpp b/common/JackPhysicalMidiInput.cpp new file mode 100644 index 00000000..311dad0b --- /dev/null +++ b/common/JackPhysicalMidiInput.cpp @@ -0,0 +1,287 @@ +/* +Copyright (C) 2009 Devin Anderson + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include +#include +#include + +#include "JackError.h" +#include "JackPhysicalMidiInput.h" + +namespace Jack { + +JackPhysicalMidiInput::JackPhysicalMidiInput(size_t buffer_size) +{ + size_t datum_size = sizeof(jack_midi_data_t); + assert(buffer_size > 0); + input_ring = jack_ringbuffer_create((buffer_size + 1) * datum_size); + if (! input_ring) { + throw std::bad_alloc(); + } + jack_ringbuffer_mlock(input_ring); + Clear(); + expected_data_bytes = 0; + status_byte = 0; +} + +JackPhysicalMidiInput::~JackPhysicalMidiInput() +{ + jack_ringbuffer_free(input_ring); +} + +void +JackPhysicalMidiInput::Clear() +{ + jack_ringbuffer_reset(input_ring); + buffered_bytes = 0; + unbuffered_bytes = 0; +} + +void +JackPhysicalMidiInput::HandleBufferFailure(size_t unbuffered_bytes, + size_t total_bytes) +{ + jack_error("%d MIDI byte(s) of a %d byte message could not be buffered - " + "message dropped", unbuffered_bytes, total_bytes); +} + +void +JackPhysicalMidiInput::HandleIncompleteMessage(size_t bytes) +{ + jack_error("Discarding %d MIDI byte(s) - incomplete message (cable " + "unplugged?)", bytes); +} + +void +JackPhysicalMidiInput::HandleInvalidStatusByte(jack_midi_data_t status) +{ + jack_error("Dropping invalid MIDI status byte '%x'", + (unsigned int) status); +} + +void +JackPhysicalMidiInput::HandleUnexpectedSysexEnd(size_t bytes) +{ + jack_error("Discarding %d MIDI byte(s) - received sysex end without sysex " + "start (cable unplugged?)", bytes); +} + +void +JackPhysicalMidiInput::HandleWriteFailure(size_t bytes) +{ + jack_error("Failed to write a %d byte MIDI message to the port buffer", + bytes); +} + +void +JackPhysicalMidiInput::Process(jack_nframes_t frames) +{ + assert(port_buffer); + port_buffer->Reset(frames); + jack_nframes_t current_frame = 0; + size_t datum_size = sizeof(jack_midi_data_t); + for (;;) { + jack_midi_data_t datum; + current_frame = Receive(&datum, current_frame, frames); + if (current_frame >= frames) { + break; + } + + jack_log("JackPhysicalMidiInput::Process (%d) - Received '%x' byte", + current_frame, (unsigned int) datum); + + if (datum >= 0xf8) { + // Realtime + if (datum == 0xfd) { + HandleInvalidStatusByte(datum); + } else { + + jack_log("JackPhysicalMidiInput::Process - Writing realtime " + "event."); + + WriteByteEvent(current_frame, datum); + } + continue; + } + if (datum == 0xf7) { + // Sysex end + if (status_byte != 0xf0) { + HandleUnexpectedSysexEnd(buffered_bytes + unbuffered_bytes); + Clear(); + expected_data_bytes = 0; + status_byte = 0; + } else { + + jack_log("JackPhysicalMidiInput::Process - Writing sysex " + "event."); + + WriteBufferedSysexEvent(current_frame); + } + continue; + } + if (datum >= 0x80) { + + // We're handling a non-realtime status byte + + jack_log("JackPhysicalMidiInput::Process - Handling non-realtime " + "status byte."); + + if (buffered_bytes || unbuffered_bytes) { + HandleIncompleteMessage(buffered_bytes + unbuffered_bytes + 1); + Clear(); + } + status_byte = datum; + switch (datum & 0xf0) { + case 0x80: + case 0x90: + case 0xa0: + case 0xb0: + case 0xe0: + // Note On, Note Off, Aftertouch, Control Change, Pitch Wheel + expected_data_bytes = 2; + break; + case 0xc0: + case 0xd0: + // Program Change, Channel Pressure + expected_data_bytes = 1; + break; + case 0xf0: + switch (datum) { + case 0xf0: + // Sysex message + expected_data_bytes = 0; + break; + case 0xf1: + case 0xf3: + // MTC Quarter frame, Song Select + expected_data_bytes = 1; + break; + case 0xf2: + // Song Position + expected_data_bytes = 2; + break; + case 0xf4: + case 0xf5: + // Undefined + HandleInvalidStatusByte(datum); + expected_data_bytes = 0; + status_byte = 0; + break; + case 0xf6: + // Tune Request + WriteByteEvent(current_frame, datum); + expected_data_bytes = 0; + status_byte = 0; + } + break; + } + continue; + } + + // We're handling a data byte + + jack_log("JackPhysicalMidiInput::Process - Buffering data byte."); + + if (jack_ringbuffer_write(input_ring, (const char *) &datum, + datum_size) == datum_size) { + buffered_bytes++; + } else { + unbuffered_bytes++; + } + unsigned long total_bytes = buffered_bytes + unbuffered_bytes; + assert((! expected_data_bytes) || + (total_bytes <= expected_data_bytes)); + if (total_bytes == expected_data_bytes) { + if (! unbuffered_bytes) { + + jack_log("JackPhysicalMidiInput::Process - Writing buffered " + "event."); + + WriteBufferedEvent(current_frame); + } else { + HandleBufferFailure(unbuffered_bytes, total_bytes); + Clear(); + } + if (status_byte >= 0xf0) { + expected_data_bytes = 0; + status_byte = 0; + } + } + } +} + +void +JackPhysicalMidiInput::WriteBufferedEvent(jack_nframes_t frame) +{ + assert(port_buffer && port_buffer->IsValid()); + size_t space = jack_ringbuffer_read_space(input_ring); + jack_midi_data_t *event = port_buffer->ReserveEvent(frame, space + 1); + if (event) { + jack_ringbuffer_data_t vector[2]; + jack_ringbuffer_get_read_vector(input_ring, vector); + event[0] = status_byte; + size_t data_length_1 = vector[0].len; + memcpy(event + 1, vector[0].buf, data_length_1); + size_t data_length_2 = vector[1].len; + if (data_length_2) { + memcpy(event + data_length_1 + 1, vector[1].buf, data_length_2); + } + } else { + HandleWriteFailure(space + 1); + } + Clear(); +} + +void +JackPhysicalMidiInput::WriteBufferedSysexEvent(jack_nframes_t frame) +{ + assert(port_buffer && port_buffer->IsValid()); + size_t space = jack_ringbuffer_read_space(input_ring); + jack_midi_data_t *event = port_buffer->ReserveEvent(frame, space + 2); + if (event) { + jack_ringbuffer_data_t vector[2]; + jack_ringbuffer_get_read_vector(input_ring, vector); + event[0] = status_byte; + size_t data_length_1 = vector[0].len; + memcpy(event + 1, vector[0].buf, data_length_1); + size_t data_length_2 = vector[1].len; + if (data_length_2) { + memcpy(event + data_length_1 + 1, vector[1].buf, data_length_2); + } + event[data_length_1 + data_length_2 + 1] = 0xf7; + } else { + HandleWriteFailure(space + 2); + } + Clear(); +} + +void +JackPhysicalMidiInput::WriteByteEvent(jack_nframes_t frame, + jack_midi_data_t datum) +{ + assert(port_buffer && port_buffer->IsValid()); + jack_midi_data_t *event = port_buffer->ReserveEvent(frame, 1); + if (event) { + event[0] = datum; + } else { + HandleWriteFailure(1); + } +} + +} diff --git a/common/JackPhysicalMidiInput.h b/common/JackPhysicalMidiInput.h new file mode 100644 index 00000000..6ba3b471 --- /dev/null +++ b/common/JackPhysicalMidiInput.h @@ -0,0 +1,146 @@ +/* +Copyright (C) 2009 Devin Anderson + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __JackPhysicalMidiInput__ +#define __JackPhysicalMidiInput__ + +#include "JackMidiPort.h" +#include "ringbuffer.h" + +namespace Jack { + + class JackPhysicalMidiInput { + + private: + + size_t buffered_bytes; + size_t expected_data_bytes; + jack_ringbuffer_t *input_ring; + JackMidiBuffer *port_buffer; + jack_midi_data_t status_byte; + size_t unbuffered_bytes; + + void + Clear(); + + void + WriteBufferedEvent(jack_nframes_t); + + void + WriteBufferedSysexEvent(jack_nframes_t); + + void + WriteByteEvent(jack_nframes_t, jack_midi_data_t); + + protected: + + /** + * Override to specify how to react when 1 or more bytes of a MIDI + * message are lost because there wasn't enough room in the input + * buffer. The first argument is the amount of bytes that couldn't be + * buffered, and the second argument is the total amount of bytes in + * the MIDI message. The default implementation calls 'jack_error' + * with a basic error message. + */ + + virtual void + HandleBufferFailure(size_t, size_t); + + /** + * Override to specify how to react when a new status byte is received + * before all of the data bytes in a message are received. The + * argument is the number of bytes being discarded. The default + * implementation calls 'jack_error' with a basic error message. + */ + + virtual void + HandleIncompleteMessage(size_t); + + /** + * Override to specify how to react when an invalid status byte (0xf4, + * 0xf5, 0xfd) is received. The argument contains the invalid status + * byte. The default implementation calls 'jack_error' with a basic + * error message. + */ + + virtual void + HandleInvalidStatusByte(jack_midi_data_t); + + /** + * Override to specify how to react when a sysex end byte (0xf7) is + * received without first receiving a sysex start byte (0xf0). The + * argument contains the amount of bytes that will be discarded. The + * default implementation calls 'jack_error' with a basic error + * message. + */ + + virtual void + HandleUnexpectedSysexEnd(size_t); + + /** + * Override to specify how to react when a MIDI message can not be + * written to the port buffer. The argument specifies the length of + * the MIDI message. The default implementation calls 'jack_error' + * with a basic error message. + */ + + virtual void + HandleWriteFailure(size_t); + + /** + * This method *must* be overridden to handle receiving MIDI bytes. + * The first argument is a pointer to the memory location at which the + * MIDI byte should be stored. The second argument is the last frame + * at which a MIDI byte was received, except at the beginning of the + * period when the value is 0. The third argument is the total number + * of frames in the period. The return value is the frame at which the + * MIDI byte is received at, or the value of the third argument is no + * more MIDI bytes can be received in this period. + */ + + virtual jack_nframes_t + Receive(jack_midi_data_t *, jack_nframes_t, jack_nframes_t) = 0; + + public: + + JackPhysicalMidiInput(size_t buffer_size=1024); + ~JackPhysicalMidiInput(); + + /** + * Called to process MIDI data during a period. + */ + + void + Process(jack_nframes_t); + + /** + * Set the MIDI buffer that will receive incoming messages. + */ + + inline void + SetPortBuffer(JackMidiBuffer *port_buffer) + { + this->port_buffer = port_buffer; + } + + }; + +} + +#endif diff --git a/common/JackPhysicalMidiOutput.cpp b/common/JackPhysicalMidiOutput.cpp new file mode 100644 index 00000000..e435f25b --- /dev/null +++ b/common/JackPhysicalMidiOutput.cpp @@ -0,0 +1,321 @@ +/* +Copyright (C) 2009 Devin Anderson + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include + +#include "JackError.h" +#include "JackPhysicalMidiOutput.h" + +namespace Jack { + +JackPhysicalMidiOutput::JackPhysicalMidiOutput(size_t non_rt_buffer_size, + size_t rt_buffer_size) +{ + size_t datum_size = sizeof(jack_midi_data_t); + assert(non_rt_buffer_size > 0); + assert(rt_buffer_size > 0); + output_ring = jack_ringbuffer_create((non_rt_buffer_size + 1) * + datum_size); + if (! output_ring) { + throw std::bad_alloc(); + } + rt_output_ring = jack_ringbuffer_create((rt_buffer_size + 1) * + datum_size); + if (! rt_output_ring) { + jack_ringbuffer_free(output_ring); + throw std::bad_alloc(); + } + jack_ringbuffer_mlock(output_ring); + jack_ringbuffer_mlock(rt_output_ring); + running_status = 0; +} + +JackPhysicalMidiOutput::~JackPhysicalMidiOutput() +{ + jack_ringbuffer_free(output_ring); + jack_ringbuffer_free(rt_output_ring); +} + +jack_nframes_t +JackPhysicalMidiOutput::Advance(jack_nframes_t frame) +{ + return frame; +} + +inline jack_midi_data_t +JackPhysicalMidiOutput::ApplyRunningStatus(jack_midi_data_t **buffer, + size_t *size) +{ + + // Stolen and modified from alsa/midi_pack.h + + jack_midi_data_t status = (*buffer)[0]; + if ((status >= 0x80) && (status < 0xf0)) { + if (status == running_status) { + (*buffer)++; + (*size)--; + } else { + running_status = status; + } + } else if (status < 0xf8) { + running_status = 0; + } + return status; +} + +void +JackPhysicalMidiOutput::HandleEventLoss(JackMidiEvent *event) +{ + jack_error("%d byte MIDI event lost", event->size); +} + +void +JackPhysicalMidiOutput::Process(jack_nframes_t frames) +{ + assert(port_buffer); + jack_nframes_t current_frame = Advance(0); + jack_nframes_t current_midi_event = 0; + jack_midi_data_t datum; + size_t datum_size = sizeof(jack_midi_data_t); + JackMidiEvent *midi_event; + jack_midi_data_t *midi_event_buffer; + size_t midi_event_size; + jack_nframes_t midi_events = port_buffer->event_count; + + // First, send any realtime MIDI data that's left from last cycle. + + if ((current_frame < frames) && + jack_ringbuffer_read_space(rt_output_ring)) { + + jack_log("JackPhysicalMidiOutput::Process (%d) - Sending buffered " + "realtime data from last period.", current_frame); + + current_frame = SendBufferedData(rt_output_ring, current_frame, + frames); + + jack_log("JackPhysicalMidiOutput::Process (%d) - Sent", current_frame); + + } + + // Iterate through the events in this cycle. + + for (; (current_midi_event < midi_events) && (current_frame < frames); + current_midi_event++) { + + // Once we're inside this loop, we know that the realtime buffer + // is empty. As long as we don't find a realtime message, we can + // concentrate on sending non-realtime data. + + midi_event = &(port_buffer->events[current_midi_event]); + jack_nframes_t midi_event_time = midi_event->time; + midi_event_buffer = midi_event->GetData(port_buffer); + midi_event_size = midi_event->size; + datum = ApplyRunningStatus(&midi_event_buffer, &midi_event_size); + if (current_frame < midi_event_time) { + + // We have time before this event is scheduled to be sent. + // Send data in the non-realtime buffer. + + if (jack_ringbuffer_read_space(output_ring)) { + + jack_log("JackPhysicalMidiOutput::Process (%d) - Sending " + "buffered non-realtime data from last period.", + current_frame); + + current_frame = SendBufferedData(output_ring, current_frame, + midi_event_time); + + jack_log("JackPhysicalMidiOutput::Process (%d) - Sent", + current_frame); + + } + if (current_frame < midi_event_time) { + + // We _still_ have time before this event is scheduled to + // be sent. Let's send as much of this event as we can + // (save for one byte, which will need to be sent at or + // after its scheduled time). First though, we need to + // make sure that we can buffer this data if we need to. + // Otherwise, we might start sending a message that we + // can't finish. + + if (midi_event_size > 1) { + if (jack_ringbuffer_write_space(output_ring) < + ((midi_event_size - 1) * datum_size)) { + HandleEventLoss(midi_event); + continue; + } + + // Send as much of the event as possible (save for one + // byte). + + do { + + jack_log("JackPhysicalMidiOutput::Process (%d) - " + "Sending unbuffered event byte early.", + current_frame); + + current_frame = Send(current_frame, + *midi_event_buffer); + + jack_log("JackPhysicalMidiOutput::Process (%d) - " + "Sent.", current_frame); + + midi_event_buffer++; + midi_event_size--; + if (current_frame >= midi_event_time) { + + // The event we're processing must be a + // non-realtime event. It has more than one + // byte. + + goto buffer_non_realtime_data; + } + } while (midi_event_size > 1); + } + + jack_log("JackPhysicalMidiOutput::Process (%d) - Advancing to " + ">= %d", current_frame, midi_event_time); + + current_frame = Advance(midi_event_time); + + jack_log("JackPhysicalMidiOutput::Process (%d) - Advanced.", + current_frame); + + } + } + + // If the event is realtime, then we'll send the event now. + // Otherwise, we attempt to put the rest of the event bytes in the + // non-realtime buffer. + + if (datum >= 0xf8) { + + jack_log("JackPhysicalMidiOutput::Process (%d) - Sending " + "unbuffered realtime event.", current_frame); + + current_frame = Send(current_frame, datum); + + jack_log("JackPhysicalMidiOutput::Process (%d) - Sent.", + current_frame); + + } else if (jack_ringbuffer_write_space(output_ring) >= + (midi_event_size * datum_size)) { + buffer_non_realtime_data: + + jack_log("JackPhysicalMidiOutput::Process (%d) - Buffering %d " + "byte(s) of non-realtime data.", current_frame, + midi_event_size); + + jack_ringbuffer_write(output_ring, + (const char *) midi_event_buffer, + midi_event_size); + } else { + HandleEventLoss(midi_event); + } + } + + if (current_frame < frames) { + + // If we have time left to send data, then we know that all of the + // data in the realtime buffer has been sent, and that all of the + // non-realtime messages have either been sent, or buffered. We + // use whatever time is left to send data in the non-realtime + // buffer. + + if (jack_ringbuffer_read_space(output_ring)) { + + jack_log("JackPhysicalMidiOutput::Process (%d) - All events " + "processed. Sending buffered non-realtime data.", + current_frame); + + current_frame = SendBufferedData(output_ring, current_frame, + frames); + + jack_log("JackPhysicalMidiOutput::Process (%d) - Sent.", + current_frame); + + } + } else { + + // Since we have no time left, we need to put all remaining midi + // events in their appropriate buffers, and send them next period. + + for (current_midi_event++; current_midi_event < midi_events; + current_midi_event++) { + midi_event = &(port_buffer->events[current_midi_event]); + midi_event_buffer = midi_event->GetData(port_buffer); + midi_event_size = midi_event->size; + datum = ApplyRunningStatus(&midi_event_buffer, &midi_event_size); + if (datum >= 0xf8) { + + // Realtime. + + if (jack_ringbuffer_write_space(rt_output_ring) >= + datum_size) { + + jack_log("JackPhysicalMidiOutput::Process - Buffering " + "realtime event for next period."); + + jack_ringbuffer_write(rt_output_ring, + (const char *) &datum, datum_size); + continue; + } + } else { + + // Non-realtime. + + if (jack_ringbuffer_write_space(output_ring) >= + (midi_event_size * datum_size)) { + + jack_log("JackPhysicalMidiOutput::Process - Buffering " + "non-realtime event for next period."); + + jack_ringbuffer_write(output_ring, + (const char *) midi_event_buffer, + midi_event_size * datum_size); + continue; + } + } + HandleEventLoss(midi_event); + } + } +} + +jack_nframes_t +JackPhysicalMidiOutput::SendBufferedData(jack_ringbuffer_t *buffer, + jack_nframes_t current_frame, + jack_nframes_t boundary) +{ + assert(buffer); + assert(current_frame < boundary); + size_t datum_size = sizeof(jack_midi_data_t); + size_t data_length = jack_ringbuffer_read_space(buffer) / datum_size; + for (size_t i = 0; i < data_length; i++) { + jack_midi_data_t datum; + jack_ringbuffer_read(buffer, (char *) &datum, datum_size); + current_frame = Send(current_frame, datum); + if (current_frame >= boundary) { + break; + } + } + return current_frame; +} + +} diff --git a/common/JackPhysicalMidiOutput.h b/common/JackPhysicalMidiOutput.h new file mode 100644 index 00000000..f76a233a --- /dev/null +++ b/common/JackPhysicalMidiOutput.h @@ -0,0 +1,118 @@ +/* +Copyright (C) 2009 Devin Anderson + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __JackPhysicalMidiOutput__ +#define __JackPhysicalMidiOutput__ + +#include "JackMidiPort.h" +#include "ringbuffer.h" + +namespace Jack { + + class JackPhysicalMidiOutput { + + private: + + jack_midi_data_t + ApplyRunningStatus(jack_midi_data_t **, size_t *); + + jack_ringbuffer_t *output_ring; + JackMidiBuffer *port_buffer; + jack_ringbuffer_t *rt_output_ring; + jack_midi_data_t running_status; + + protected: + + /** + * Override to specify the next frame at which a midi byte can be sent. + * The returned frame must be greater than or equal to the frame + * argument. The default returns the frame passed to it. + */ + + virtual jack_nframes_t + Advance(jack_nframes_t); + + /** + * Override to customize how to react when a MIDI event can't be + * buffered and can't be sent immediately. The default calls + * 'jack_error' and specifies the number of bytes lost. + */ + + virtual void + HandleEventLoss(JackMidiEvent *); + + /** + * This method *must* be overridden to specify what happens when a MIDI + * byte is sent at the specfied frame. The frame argument specifies + * the frame at which the MIDI byte should be sent, and the second + * argument specifies the byte itself. The return value is the next + * frame at which a MIDI byte can be sent, and must be greater than or + * equal to the frame argument. + */ + + virtual jack_nframes_t + Send(jack_nframes_t, jack_midi_data_t) = 0; + + /** + * Override to optimize behavior when sending MIDI data that's in the + * ringbuffer. The first frame argument is the current frame, and the + * second frame argument is the boundary frame. The function returns + * the next frame at which MIDI data can be sent, regardless of whether + * or not the boundary is reached. The default implementation calls + * 'Send' with each byte in the ringbuffer until either the ringbuffer + * is empty, or a frame beyond the boundary frame is returned by + * 'Send'. + */ + + virtual jack_nframes_t + SendBufferedData(jack_ringbuffer_t *, jack_nframes_t, jack_nframes_t); + + public: + + /** + * The non-realtime buffer size and the realtime buffer size are both + * optional arguments. + */ + + JackPhysicalMidiOutput(size_t non_rt_buffer_size=1024, + size_t rt_buffer_size=64); + ~JackPhysicalMidiOutput(); + + /** + * Called to process MIDI data during a period. + */ + + void + Process(jack_nframes_t); + + /** + * Set the MIDI buffer that will contain the outgoing MIDI messages. + */ + + inline void + SetPortBuffer(JackMidiBuffer *port_buffer) + { + this->port_buffer = port_buffer; + } + + }; + +} + +#endif diff --git a/common/wscript b/common/wscript index 6089ff81..e4e3bdbc 100644 --- a/common/wscript +++ b/common/wscript @@ -131,6 +131,8 @@ def build(bld): 'JackNetInterface.cpp', 'JackArgParser.cpp', 'JackDummyDriver.cpp', + 'JackPhysicalMidiInput.cpp', + 'JackPhysicalMidiOutput.cpp', ] if bld.env['IS_LINUX']: diff --git a/linux/firewire/JackFFADODriver.cpp b/linux/firewire/JackFFADODriver.cpp index b6080d69..a9021917 100644 --- a/linux/firewire/JackFFADODriver.cpp +++ b/linux/firewire/JackFFADODriver.cpp @@ -2,6 +2,7 @@ Copyright (C) 2001 Paul Davis Copyright (C) 2004 Grame Copyright (C) 2007 Pieter Palmers +Copyright (C) 2009 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 @@ -35,6 +36,8 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. #include #include "JackFFADODriver.h" +#include "JackFFADOMidiInput.h" +#include "JackFFADOMidiOutput.h" #include "JackEngineControl.h" #include "JackClientControl.h" #include "JackPort.h" @@ -91,29 +94,14 @@ JackFFADODriver::ffado_driver_read (ffado_driver_t * driver, jack_nframes_t nfra /* process the midi data */ for (chn = 0; chn < driver->capture_nchannels; chn++) { if (driver->capture_channels[chn].stream_type == ffado_stream_type_midi) { - jack_nframes_t i; - int done; - uint32_t *midi_buffer = driver->capture_channels[chn].midi_buffer; - midi_unpack_t *midi_unpack = &driver->capture_channels[chn].midi_unpack; - buf = (jack_default_audio_sample_t*)fGraphManager->GetBuffer(fCapturePortList[chn], nframes); - jack_midi_clear_buffer(buf); - - /* if the returned buffer is invalid, discard the midi data */ - if (!buf) continue; - /* else unpack - note that libffado guarantees that midi bytes are on 8-byte aligned indexes - */ - for (i = 0; i < nframes; i += 8) { - if (midi_buffer[i] & 0xFF000000) { - done = midi_unpack_buf(midi_unpack, (unsigned char *)(midi_buffer + i), 1, buf, i); - if (done != 1) { - printError("buffer overflow in channel %d\n", chn); - break; - } - - printMessage("MIDI IN: %08X (i=%d)", midi_buffer[i], i); - } + JackFFADOMidiInput *midi_input = (JackFFADOMidiInput *) driver->capture_channels[chn].midi_input; + JackMidiBuffer *buffer = (JackMidiBuffer *) fGraphManager->GetBuffer(fCapturePortList[chn], nframes); + if (! buffer) { + continue; } + midi_input->SetInputBuffer(driver->capture_channels[chn].midi_buffer); + midi_input->SetPortBuffer(buffer); + midi_input->Process(nframes); } } @@ -146,80 +134,20 @@ JackFFADODriver::ffado_driver_write (ffado_driver_t * driver, jack_nframes_t nfr ffado_streaming_set_playback_stream_buffer(driver->dev, chn, (char *)(buf)); ffado_streaming_playback_stream_onoff(driver->dev, chn, 1); } else if (driver->playback_channels[chn].stream_type == ffado_stream_type_midi) { - jack_nframes_t nevents; - jack_nframes_t i; - midi_pack_t *midi_pack = &driver->playback_channels[chn].midi_pack; uint32_t *midi_buffer = driver->playback_channels[chn].midi_buffer; - buf = (jack_default_audio_sample_t*)fGraphManager->GetBuffer(fPlaybackPortList[chn], nframes); - jack_nframes_t min_next_pos = 0; - memset(midi_buffer, 0, nframes * sizeof(uint32_t)); + buf = (jack_default_audio_sample_t *) fGraphManager->GetBuffer(fPlaybackPortList[chn], nframes); ffado_streaming_set_playback_stream_buffer(driver->dev, chn, (char *)(midi_buffer)); - /* if the returned buffer is invalid, continue */ if (!buf) { ffado_streaming_playback_stream_onoff(driver->dev, chn, 0); continue; } ffado_streaming_playback_stream_onoff(driver->dev, chn, 1); - - // check if we still have to process bytes from the previous period - if (driver->playback_channels[chn].nb_overflow_bytes) { - printMessage("have to process %d bytes from previous period", driver->playback_channels[chn].nb_overflow_bytes); - } - for (i = 0; i < driver->playback_channels[chn].nb_overflow_bytes; ++i) { - midi_buffer[min_next_pos] = 0x01000000 | (driver->playback_channels[chn].overflow_buffer[i] & 0xFF); - min_next_pos += 8; - } - driver->playback_channels[chn].nb_overflow_bytes = 0; - - // process the events in this period - nevents = jack_midi_get_event_count(buf); - //if (nevents) - // printMessage("MIDI: %d events in ch %d", nevents, chn); - - for (i = 0; i < nevents; ++i) { - size_t j; - jack_midi_event_t event; - jack_midi_event_get(&event, buf, i); - - midi_pack_event(midi_pack, &event); - - // floor the initial position to be a multiple of 8 - jack_nframes_t pos = event.time & 0xFFFFFFF8; - for (j = 0; j < event.size; j++) { - // make sure we don't overwrite a previous byte - while (pos < min_next_pos && pos < nframes) { - pos += 8; - printMessage("have to correct pos to %d", pos); - } - - if (pos >= nframes) { - unsigned int f; - printMessage("midi message crosses period boundary"); - driver->playback_channels[chn].nb_overflow_bytes = event.size - j; - if (driver->playback_channels[chn].nb_overflow_bytes > MIDI_OVERFLOW_BUFFER_SIZE) { - printError("too much midi bytes cross period boundary"); - driver->playback_channels[chn].nb_overflow_bytes = MIDI_OVERFLOW_BUFFER_SIZE; - } - // save the bytes that still have to be transmitted in the next period - for (f = 0; f < driver->playback_channels[chn].nb_overflow_bytes; f++) { - driver->playback_channels[chn].overflow_buffer[f] = event.buffer[j+f]; - } - // exit since we can't transmit anything anymore. - // the rate should be controlled - if (i < nevents - 1) { - printError("%d midi events lost due to period crossing", nevents - i - 1); - } - break; - } else { - midi_buffer[pos] = 0x01000000 | (event.buffer[j] & 0xFF); - pos += 8; - min_next_pos = pos; - } - } - //printMessage("MIDI: sent %d-byte event at %ld", (int)event.size, (long)event.time); - } + JackFFADOMidiOutput *midi_output = (JackFFADOMidiOutput *) driver->playback_channels[chn].midi_output; + midi_output->SetPortBuffer((JackMidiBuffer *) buf); + midi_output->SetOutputBuffer(midi_buffer); + midi_output->Process(nframes); } else { // always have a valid buffer ffado_streaming_set_playback_stream_buffer(driver->dev, chn, (char *)(driver->nullbuffer)); @@ -541,9 +469,7 @@ int JackFFADODriver::Attach() printError(" cannot enable port %s", buf); } - // setup midi unpacker - midi_unpack_init(&driver->capture_channels[chn].midi_unpack); - midi_unpack_reset(&driver->capture_channels[chn].midi_unpack); + driver->capture_channels[chn].midi_input = new JackFFADOMidiInput(); // setup the midi buffer driver->capture_channels[chn].midi_buffer = (uint32_t *)calloc(driver->period_size, sizeof(uint32_t)); @@ -616,9 +542,13 @@ int JackFFADODriver::Attach() if (ffado_streaming_playback_stream_onoff(driver->dev, chn, 0)) { printError(" cannot enable port %s", buf); } - // setup midi packer - midi_pack_reset(&driver->playback_channels[chn].midi_pack); // setup the midi buffer + + // This constructor optionally accepts arguments for the + // non-realtime buffer size and the realtime buffer size. Ideally, + // these would become command-line options for the FFADO driver. + driver->playback_channels[chn].midi_output = new JackFFADOMidiOutput(); + driver->playback_channels[chn].midi_buffer = (uint32_t *)calloc(driver->period_size, sizeof(uint32_t)); port = fGraphManager->GetPort(port_index); @@ -658,12 +588,16 @@ int JackFFADODriver::Detach() for (chn = 0; chn < driver->capture_nchannels; chn++) { if (driver->capture_channels[chn].midi_buffer) free(driver->capture_channels[chn].midi_buffer); + if (driver->capture_channels[chn].midi_input) + delete ((JackFFADOMidiInput *) (driver->capture_channels[chn].midi_input)); } free(driver->capture_channels); for (chn = 0; chn < driver->playback_nchannels; chn++) { if (driver->playback_channels[chn].midi_buffer) free(driver->playback_channels[chn].midi_buffer); + if (driver->playback_channels[chn].midi_output) + delete ((JackFFADOMidiOutput *) (driver->playback_channels[chn].midi_output)); } free(driver->playback_channels); diff --git a/linux/firewire/JackFFADOMidiInput.cpp b/linux/firewire/JackFFADOMidiInput.cpp new file mode 100644 index 00000000..38ed539b --- /dev/null +++ b/linux/firewire/JackFFADOMidiInput.cpp @@ -0,0 +1,59 @@ +/* +Copyright (C) 2009 Devin Anderson + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include + +#include "JackFFADOMidiInput.h" + +namespace Jack { + +JackFFADOMidiInput::JackFFADOMidiInput(size_t buffer_size): + JackPhysicalMidiInput(buffer_size) +{ + new_period = true; +} + +JackFFADOMidiInput::~JackFFADOMidiInput() +{ + // Empty +} + +jack_nframes_t +JackFFADOMidiInput::Receive(jack_midi_data_t *datum, + jack_nframes_t current_frame, + jack_nframes_t total_frames) +{ + assert(input_buffer); + if (! new_period) { + current_frame += 8; + } else { + new_period = false; + } + for (; current_frame < total_frames; current_frame += 8) { + uint32_t data = input_buffer[current_frame]; + if (data & 0xff000000) { + *datum = (jack_midi_data_t) (data & 0xff); + return current_frame; + } + } + new_period = true; + return total_frames; +} + +} diff --git a/linux/firewire/JackFFADOMidiInput.h b/linux/firewire/JackFFADOMidiInput.h new file mode 100644 index 00000000..12dc043c --- /dev/null +++ b/linux/firewire/JackFFADOMidiInput.h @@ -0,0 +1,54 @@ +/* +Copyright (C) 2009 Devin Anderson + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __JackFFADOMidiInput__ +#define __JackFFADOMidiInput__ + +#include "JackPhysicalMidiInput.h" + +namespace Jack { + + class JackFFADOMidiInput: public JackPhysicalMidiInput { + + private: + + uint32_t *input_buffer; + bool new_period; + + protected: + + jack_nframes_t + Receive(jack_midi_data_t *, jack_nframes_t, jack_nframes_t); + + public: + + JackFFADOMidiInput(size_t buffer_size=1024); + ~JackFFADOMidiInput(); + + inline void + SetInputBuffer(uint32_t *input_buffer) + { + this->input_buffer = input_buffer; + } + + }; + +} + +#endif diff --git a/linux/firewire/JackFFADOMidiOutput.cpp b/linux/firewire/JackFFADOMidiOutput.cpp new file mode 100644 index 00000000..995f2d28 --- /dev/null +++ b/linux/firewire/JackFFADOMidiOutput.cpp @@ -0,0 +1,60 @@ +/* +Copyright (C) 2009 Devin Anderson + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#include + +#include "JackError.h" +#include "JackFFADOMidiOutput.h" + +namespace Jack { + +JackFFADOMidiOutput::JackFFADOMidiOutput(size_t non_rt_buffer_size, + size_t rt_buffer_size): + JackPhysicalMidiOutput(non_rt_buffer_size, rt_buffer_size) +{ + // Empty +} + +JackFFADOMidiOutput::~JackFFADOMidiOutput() +{ + // Empty +} + +jack_nframes_t +JackFFADOMidiOutput::Advance(jack_nframes_t current_frame) +{ + if (current_frame % 8) { + current_frame = (current_frame & (~ ((jack_nframes_t) 7))) + 8; + } + return current_frame; +} + +jack_nframes_t +JackFFADOMidiOutput::Send(jack_nframes_t current_frame, jack_midi_data_t datum) +{ + assert(output_buffer); + + jack_log("JackFFADOMidiOutput::Send (%d) - Sending '%x' byte.", + current_frame, (unsigned int) datum); + + output_buffer[current_frame] = 0x01000000 | ((uint32_t) datum); + return current_frame + 8; +} + +} diff --git a/linux/firewire/JackFFADOMidiOutput.h b/linux/firewire/JackFFADOMidiOutput.h new file mode 100644 index 00000000..309e7f61 --- /dev/null +++ b/linux/firewire/JackFFADOMidiOutput.h @@ -0,0 +1,57 @@ +/* +Copyright (C) 2009 Devin Anderson + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +*/ + +#ifndef __JackFFADOMidiOutput__ +#define __JackFFADOMidiOutput__ + +#include "JackPhysicalMidiOutput.h" + +namespace Jack { + + class JackFFADOMidiOutput: public JackPhysicalMidiOutput { + + private: + + uint32_t *output_buffer; + + protected: + + jack_nframes_t + Advance(jack_nframes_t); + + jack_nframes_t + Send(jack_nframes_t, jack_midi_data_t); + + public: + + JackFFADOMidiOutput(size_t non_rt_buffer_size=1024, + size_t rt_buffer_size=64); + ~JackFFADOMidiOutput(); + + inline void + SetOutputBuffer(uint32_t *output_buffer) + { + this->output_buffer = output_buffer; + } + + }; + +} + +#endif diff --git a/linux/firewire/ffado_driver.h b/linux/firewire/ffado_driver.h index a851d845..a06c1361 100644 --- a/linux/firewire/ffado_driver.h +++ b/linux/firewire/ffado_driver.h @@ -7,6 +7,7 @@ * http://www.jackaudio.org * * Copyright (C) 2005-2007 Pieter Palmers + * Copyright (C) 2009 Devin Anderson * * adapted for JackMP by Pieter Palmers * @@ -51,9 +52,7 @@ #include #include -#include -#include "../alsa/midi_pack.h" -#include "../alsa/midi_unpack.h" +//#include // debug print control flags #define DEBUG_LEVEL_BUFFERS (1<<0) @@ -143,21 +142,16 @@ struct _ffado_jack_settings typedef struct _ffado_capture_channel { ffado_streaming_stream_type stream_type; - midi_unpack_t midi_unpack; uint32_t *midi_buffer; + void *midi_input; } ffado_capture_channel_t; -#define MIDI_OVERFLOW_BUFFER_SIZE 4 typedef struct _ffado_playback_channel { ffado_streaming_stream_type stream_type; - midi_pack_t midi_pack; uint32_t *midi_buffer; - // to hold the midi bytes that couldn't be transferred - // during the previous period - char overflow_buffer[MIDI_OVERFLOW_BUFFER_SIZE]; - unsigned int nb_overflow_bytes; + void *midi_output; } ffado_playback_channel_t; diff --git a/linux/wscript b/linux/wscript index 56a77330..20695a96 100644 --- a/linux/wscript +++ b/linux/wscript @@ -55,6 +55,13 @@ def build(bld): 'alsa/ice1712.c' ] + ffado_driver_src = ['firewire/JackFFADODriver.cpp', + 'firewire/JackFFADOMidiInput.cpp', + 'firewire/JackFFADOMidiOutput.cpp', + '../common/JackPhysicalMidiInput.cpp', + '../common/JackPhysicalMidiOutput.cpp' + ] + if bld.env['BUILD_DRIVER_ALSA'] == True: create_jack_driver_obj(bld, 'alsa', alsa_driver_src, "ALSA") @@ -62,7 +69,7 @@ def build(bld): create_jack_driver_obj(bld, 'freebob', 'freebob/JackFreebobDriver.cpp', "LIBFREEBOB") if bld.env['BUILD_DRIVER_FFADO'] == True: - create_jack_driver_obj(bld, 'firewire', 'firewire/JackFFADODriver.cpp', "LIBFFADO") + create_jack_driver_obj(bld, 'firewire', ffado_driver_src, "LIBFFADO") create_jack_driver_obj(bld, 'net', '../common/JackNetDriver.cpp')