Commit 23a88af2 authored by sletz's avatar sletz
Browse files

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
parent 80dca4ef
......@@ -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 <letz@grame.fr>
* Devin Anderson patch for Jack FFADO driver issues with lost MIDI bytes between periods (and more).
2009-11-29 Stephane Letz <letz@grame.fr>
* More robust sample rate change handling code in JackCoreAudioDriver.
......
/*
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 <cassert>
#include <cstring>
#include <new>
#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);
}
}
}
/*
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
/*
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 <cassert>
#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 {