git-svn-id: http://subversion.jackaudio.org/jack/jack2/trunk/jackmp@3833 0c269be4-1314-0410-8aa9-9f06e86f4224tags/v1.9.5
@@ -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. | |||
@@ -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 <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); | |||
} | |||
} | |||
} |
@@ -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 |
@@ -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 <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 { | |||
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; | |||
} | |||
} |
@@ -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 |
@@ -131,6 +131,8 @@ def build(bld): | |||
'JackNetInterface.cpp', | |||
'JackArgParser.cpp', | |||
'JackDummyDriver.cpp', | |||
'JackPhysicalMidiInput.cpp', | |||
'JackPhysicalMidiOutput.cpp', | |||
] | |||
if bld.env['IS_LINUX']: | |||
@@ -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 <string.h> | |||
#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); | |||
@@ -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 <cassert> | |||
#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; | |||
} | |||
} |
@@ -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 |
@@ -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 <cassert> | |||
#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; | |||
} | |||
} |
@@ -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 |
@@ -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 <types.h> | |||
#include <assert.h> | |||
#include <jack/midiport.h> | |||
#include "../alsa/midi_pack.h" | |||
#include "../alsa/midi_unpack.h" | |||
//#include <jack/midiport.h> | |||
// 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; | |||
@@ -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') | |||