@@ -0,0 +1,94 @@ | |||||
/* | |||||
Copyright (C) 2010 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 <new> | |||||
#include "JackMidiAsyncQueue.h" | |||||
using Jack::JackMidiAsyncQueue; | |||||
JackMidiAsyncQueue::JackMidiAsyncQueue(size_t max_bytes, size_t max_messages) | |||||
{ | |||||
data_buffer = new jack_midi_data_t[max_bytes]; | |||||
byte_ring = jack_ringbuffer_create(max_bytes + 1); | |||||
if (byte_ring) { | |||||
info_ring = jack_ringbuffer_create((max_messages * INFO_SIZE) + 1); | |||||
if (info_ring) { | |||||
jack_ringbuffer_mlock(byte_ring); | |||||
jack_ringbuffer_mlock(info_ring); | |||||
advance_space = 0; | |||||
this->max_bytes = max_bytes; | |||||
return; | |||||
} | |||||
jack_ringbuffer_free(byte_ring); | |||||
} | |||||
delete data_buffer; | |||||
throw std::bad_alloc(); | |||||
} | |||||
JackMidiAsyncQueue::~JackMidiAsyncQueue() | |||||
{ | |||||
jack_ringbuffer_free(byte_ring); | |||||
jack_ringbuffer_free(info_ring); | |||||
delete[] data_buffer; | |||||
} | |||||
jack_midi_event_t * | |||||
JackMidiAsyncQueue::DequeueEvent() | |||||
{ | |||||
jack_midi_event_t *event = 0; | |||||
if (jack_ringbuffer_read_space(info_ring) >= INFO_SIZE) { | |||||
if (advance_space) { | |||||
jack_ringbuffer_read_advance(byte_ring, advance_space); | |||||
} | |||||
event = &dequeue_event; | |||||
jack_ringbuffer_read(info_ring, (char *) &(event->time), | |||||
sizeof(jack_nframes_t)); | |||||
size_t size; | |||||
jack_ringbuffer_read(info_ring, (char *) &size, sizeof(size_t)); | |||||
event->size = size; | |||||
jack_ringbuffer_data_t vector[2]; | |||||
jack_ringbuffer_get_read_vector(byte_ring, vector); | |||||
size_t size1 = vector[0].len; | |||||
if (size1 >= size) { | |||||
event->buffer = (jack_midi_data_t *) vector[0].buf; | |||||
} else { | |||||
event->buffer = data_buffer; | |||||
memcpy(data_buffer, vector[0].buf, size1); | |||||
memcpy(data_buffer + size1, vector[1].buf, size - size1); | |||||
} | |||||
advance_space = size; | |||||
} | |||||
return event; | |||||
} | |||||
Jack::JackMidiWriteQueue::EnqueueResult | |||||
JackMidiAsyncQueue::EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer) | |||||
{ | |||||
if (! ((jack_ringbuffer_write_space(info_ring) >= INFO_SIZE) && | |||||
(jack_ringbuffer_write_space(byte_ring) >= size))) { | |||||
return size > max_bytes ? BUFFER_TOO_SMALL : BUFFER_FULL; | |||||
} | |||||
jack_ringbuffer_write(byte_ring, (const char *) buffer, size); | |||||
jack_ringbuffer_write(info_ring, (const char *) (&time), | |||||
sizeof(jack_nframes_t)); | |||||
jack_ringbuffer_write(info_ring, (const char *) (&size), sizeof(size_t)); | |||||
return OK; | |||||
} |
@@ -0,0 +1,92 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackMidiAsyncQueue__ | |||||
#define __JackMidiAsyncQueue__ | |||||
#include "JackMidiPort.h" | |||||
#include "JackMidiReadQueue.h" | |||||
#include "JackMidiWriteQueue.h" | |||||
#include "ringbuffer.h" | |||||
namespace Jack { | |||||
/** | |||||
* This is a MIDI message queue designed to allow two threads to pass MIDI | |||||
* messages between two threads (though it can also be used to buffer | |||||
* events internally). This is especially useful if the MIDI API | |||||
* you're attempting to interface with doesn't provide the ability to | |||||
* schedule MIDI events ahead of time and/or has blocking send/receive | |||||
* calls, as it allows a separate thread to handle input/output while the | |||||
* JACK process thread copies events from a `JackMidiBufferReadQueue` to | |||||
* this queue, or from this queue to a `JackMidiBufferWriteQueue`. | |||||
*/ | |||||
class SERVER_EXPORT JackMidiAsyncQueue: | |||||
public JackMidiReadQueue, public JackMidiWriteQueue { | |||||
private: | |||||
static const size_t INFO_SIZE = | |||||
sizeof(jack_nframes_t) + sizeof(size_t); | |||||
size_t advance_space; | |||||
jack_ringbuffer_t *byte_ring; | |||||
jack_midi_data_t *data_buffer; | |||||
jack_midi_event_t dequeue_event; | |||||
jack_ringbuffer_t *info_ring; | |||||
size_t max_bytes; | |||||
public: | |||||
using JackMidiWriteQueue::EnqueueEvent; | |||||
/** | |||||
* Creates a new asynchronous MIDI message queue. The queue can store | |||||
* up to `max_messages` MIDI messages and up to `max_bytes` of MIDI | |||||
* data before it starts rejecting messages. | |||||
*/ | |||||
JackMidiAsyncQueue(size_t max_bytes=4096, size_t max_messages=1024); | |||||
virtual ~JackMidiAsyncQueue(); | |||||
/** | |||||
* Dequeues and returns a MIDI event. Returns '0' if there are no MIDI | |||||
* events available. This method may be overridden. | |||||
*/ | |||||
virtual jack_midi_event_t * | |||||
DequeueEvent(); | |||||
/** | |||||
* Enqueues the MIDI event specified by the arguments. The return | |||||
* value indiciates whether or not the event was successfully enqueued. | |||||
* This method may be overridden. | |||||
*/ | |||||
virtual EnqueueResult | |||||
EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer); | |||||
}; | |||||
} | |||||
#endif |
@@ -0,0 +1,85 @@ | |||||
/* | |||||
Copyright (C) 2010 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 <new> | |||||
#include "JackMidiAsyncWaitQueue.h" | |||||
#include "JackMidiUtil.h" | |||||
#include "JackTime.h" | |||||
using Jack::JackMidiAsyncWaitQueue; | |||||
JackMidiAsyncWaitQueue::JackMidiAsyncWaitQueue(size_t max_bytes, | |||||
size_t max_messages): | |||||
JackMidiAsyncQueue(max_bytes, max_messages) | |||||
{ | |||||
if (semaphore.Allocate("JackMidiAsyncWaitQueue", "midi-thread", 0)) { | |||||
throw std::bad_alloc(); | |||||
} | |||||
} | |||||
JackMidiAsyncWaitQueue::~JackMidiAsyncWaitQueue() | |||||
{ | |||||
semaphore.Destroy(); | |||||
} | |||||
jack_midi_event_t * | |||||
JackMidiAsyncWaitQueue::DequeueEvent() | |||||
{ | |||||
return DequeueEvent((long) 0); | |||||
} | |||||
jack_midi_event_t * | |||||
JackMidiAsyncWaitQueue::DequeueEvent(jack_nframes_t frame) | |||||
{ | |||||
// XXX: I worry about timer resolution on Solaris and Windows. When the | |||||
// resolution for the `JackSynchro` object is milliseconds, the worst-case | |||||
// scenario for processor objects is that the wait time becomes less than a | |||||
// millisecond, and the processor object continually calls this method, | |||||
// expecting to wait a certain amount of microseconds, and ends up not | |||||
// waiting at all each time, essentially busy-waiting until the current | |||||
// frame is reached. Perhaps there should be a #define that indicates the | |||||
// wait time resolution for `JackSynchro` objects so that we can wait a | |||||
// little longer if necessary. | |||||
jack_time_t frame_time = GetTimeFromFrames(frame); | |||||
jack_time_t current_time = GetMicroSeconds(); | |||||
return DequeueEvent((frame_time < current_time) ? 0 : | |||||
(long) (frame_time - current_time)); | |||||
} | |||||
jack_midi_event_t * | |||||
JackMidiAsyncWaitQueue::DequeueEvent(long usec) | |||||
{ | |||||
return ((usec < 0) ? semaphore.Wait() : semaphore.TimedWait(usec)) ? | |||||
JackMidiAsyncQueue::DequeueEvent() : 0; | |||||
} | |||||
Jack::JackMidiWriteQueue::EnqueueResult | |||||
JackMidiAsyncWaitQueue::EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer) | |||||
{ | |||||
EnqueueResult result = JackMidiAsyncQueue::EnqueueEvent(time, size, | |||||
buffer); | |||||
if (result == OK) { | |||||
semaphore.Signal(); | |||||
} | |||||
return result; | |||||
} |
@@ -0,0 +1,97 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackMidiAsyncWaitQueue__ | |||||
#define __JackMidiAsyncWaitQueue__ | |||||
#include "JackMidiAsyncQueue.h" | |||||
namespace Jack { | |||||
/** | |||||
* This is an asynchronous wait queue that allows a thread to wait for a | |||||
* message, either indefinitely or for a specified time. This is one | |||||
* example of a way that the `JackMidiAsyncQueue` class can be extended so | |||||
* that process threads can interact with non-process threads to send MIDI | |||||
* events. | |||||
* | |||||
* XXX: As of right now, this code hasn't been tested. Also, note the | |||||
* warning in the JackMidiAsyncWaitQueue.cpp about semaphore wait | |||||
* resolution. | |||||
*/ | |||||
class SERVER_EXPORT JackMidiAsyncWaitQueue: public JackMidiAsyncQueue { | |||||
private: | |||||
JackSynchro semaphore; | |||||
public: | |||||
/** | |||||
* Creates a new asynchronous MIDI wait message queue. The queue can | |||||
* store up to `max_messages` MIDI messages and up to `max_bytes` of | |||||
* MIDI data before it starts rejecting messages. | |||||
*/ | |||||
JackMidiAsyncWaitQueue(size_t max_bytes=4096, | |||||
size_t max_messages=1024); | |||||
~JackMidiAsyncWaitQueue(); | |||||
/** | |||||
* Dequeues and returns a MIDI event. Returns '0' if there are no MIDI | |||||
* events available right now. | |||||
*/ | |||||
jack_midi_event_t * | |||||
DequeueEvent(); | |||||
/** | |||||
* Waits a specified time for a MIDI event to be available, or | |||||
* indefinitely if the time is negative. Returns the MIDI event, or | |||||
* '0' if time runs out and no MIDI event is available. | |||||
*/ | |||||
jack_midi_event_t * | |||||
DequeueEvent(long usecs); | |||||
/** | |||||
* Waits until the specified frame for a MIDI event to be available. | |||||
* Returns the MIDI event, or '0' if time runs out and no MIDI event is | |||||
* available. | |||||
*/ | |||||
jack_midi_event_t * | |||||
DequeueEvent(jack_nframes_t frame); | |||||
/** | |||||
* Enqueues the MIDI event specified by the arguments. The return | |||||
* value indiciates whether or not the event was successfully enqueued. | |||||
*/ | |||||
EnqueueResult | |||||
EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer); | |||||
}; | |||||
} | |||||
#endif |
@@ -0,0 +1,57 @@ | |||||
/* | |||||
Copyright (C) 2010 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 "JackMidiBufferReadQueue.h" | |||||
#include "JackMidiUtil.h" | |||||
using Jack::JackMidiBufferReadQueue; | |||||
JackMidiBufferReadQueue::JackMidiBufferReadQueue() | |||||
{ | |||||
event_count = 0; | |||||
index = 0; | |||||
} | |||||
jack_midi_event_t * | |||||
JackMidiBufferReadQueue::DequeueEvent() | |||||
{ | |||||
jack_midi_event_t *e = 0; | |||||
if (index < event_count) { | |||||
JackMidiEvent *event = &(buffer->events[index]); | |||||
midi_event.buffer = event->GetData(buffer); | |||||
midi_event.size = event->size; | |||||
midi_event.time = last_frame_time + event->time; | |||||
e = &midi_event; | |||||
index++; | |||||
} | |||||
return e; | |||||
} | |||||
void | |||||
JackMidiBufferReadQueue::ResetMidiBuffer(JackMidiBuffer *buffer) | |||||
{ | |||||
index = 0; | |||||
if (buffer) { | |||||
this->buffer = buffer; | |||||
event_count = buffer->event_count; | |||||
last_frame_time = GetLastFrame(); | |||||
} else { | |||||
event_count = 0; | |||||
} | |||||
} |
@@ -0,0 +1,60 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackMidiBufferReadQueue__ | |||||
#define __JackMidiBufferReadQueue__ | |||||
#include "JackMidiReadQueue.h" | |||||
namespace Jack { | |||||
/** | |||||
* Wrapper class to present a JackMidiBuffer in a read queue interface. | |||||
*/ | |||||
class SERVER_EXPORT JackMidiBufferReadQueue: public JackMidiReadQueue { | |||||
private: | |||||
JackMidiBuffer *buffer; | |||||
jack_nframes_t event_count; | |||||
jack_nframes_t index; | |||||
jack_nframes_t last_frame_time; | |||||
jack_midi_event_t midi_event; | |||||
public: | |||||
JackMidiBufferReadQueue(); | |||||
jack_midi_event_t * | |||||
DequeueEvent(); | |||||
/** | |||||
* This method must be called each period to reset the MIDI buffer for | |||||
* processing. | |||||
*/ | |||||
void | |||||
ResetMidiBuffer(JackMidiBuffer *buffer); | |||||
}; | |||||
} | |||||
#endif |
@@ -0,0 +1,57 @@ | |||||
/* | |||||
Copyright (C) 2010 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 "JackMidiBufferWriteQueue.h" | |||||
#include "JackMidiUtil.h" | |||||
using Jack::JackMidiBufferWriteQueue; | |||||
JackMidiBufferWriteQueue::JackMidiBufferWriteQueue() | |||||
{ | |||||
// Empty | |||||
} | |||||
Jack::JackMidiWriteQueue::EnqueueResult | |||||
JackMidiBufferWriteQueue::EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *data) | |||||
{ | |||||
if (time >= next_frame_time) { | |||||
return EVENT_EARLY; | |||||
} | |||||
if (time < last_frame_time) { | |||||
time = last_frame_time; | |||||
} | |||||
jack_midi_data_t *dst = buffer->ReserveEvent(time - last_frame_time, size); | |||||
if (! dst) { | |||||
return size > max_bytes ? BUFFER_TOO_SMALL : BUFFER_FULL; | |||||
} | |||||
memcpy(dst, data, size); | |||||
return OK; | |||||
} | |||||
void | |||||
JackMidiBufferWriteQueue::ResetMidiBuffer(JackMidiBuffer *buffer, | |||||
jack_nframes_t frames) | |||||
{ | |||||
this->buffer = buffer; | |||||
buffer->Reset(frames); | |||||
last_frame_time = GetLastFrame(); | |||||
max_bytes = buffer->MaxEventSize(); | |||||
next_frame_time = last_frame_time + frames; | |||||
} |
@@ -0,0 +1,62 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackMidiBufferWriteQueue__ | |||||
#define __JackMidiBufferWriteQueue__ | |||||
#include "JackMidiWriteQueue.h" | |||||
namespace Jack { | |||||
/** | |||||
* Wrapper class to present a JackMidiBuffer in a write queue interface. | |||||
*/ | |||||
class SERVER_EXPORT JackMidiBufferWriteQueue: public JackMidiWriteQueue { | |||||
private: | |||||
JackMidiBuffer *buffer; | |||||
jack_nframes_t last_frame_time; | |||||
size_t max_bytes; | |||||
jack_nframes_t next_frame_time; | |||||
public: | |||||
using JackMidiWriteQueue::EnqueueEvent; | |||||
JackMidiBufferWriteQueue(); | |||||
EnqueueResult | |||||
EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer); | |||||
/** | |||||
* This method must be called each period to reset the MIDI buffer for | |||||
* processing. | |||||
*/ | |||||
void | |||||
ResetMidiBuffer(JackMidiBuffer *buffer, jack_nframes_t frames); | |||||
}; | |||||
} | |||||
#endif |
@@ -0,0 +1,300 @@ | |||||
/* | |||||
Copyright (C) 2010 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 <memory> | |||||
#include <new> | |||||
#include "JackMidiRawInputWriteQueue.h" | |||||
using Jack::JackMidiRawInputWriteQueue; | |||||
JackMidiRawInputWriteQueue:: | |||||
JackMidiRawInputWriteQueue(JackMidiWriteQueue *write_queue, | |||||
size_t max_packet_data, size_t max_packets) | |||||
{ | |||||
packet_queue = new JackMidiAsyncQueue(max_packet_data, max_packets); | |||||
std::auto_ptr<JackMidiAsyncQueue> packet_queue_ptr(packet_queue); | |||||
input_ring = jack_ringbuffer_create(max_packet_data + 1); | |||||
if (! input_ring) { | |||||
throw std::bad_alloc(); | |||||
} | |||||
jack_ringbuffer_mlock(input_ring); | |||||
Clear(); | |||||
expected_bytes = 0; | |||||
event_pending = false; | |||||
packet = 0; | |||||
status_byte = 0; | |||||
this->write_queue = write_queue; | |||||
packet_queue_ptr.release(); | |||||
} | |||||
JackMidiRawInputWriteQueue::~JackMidiRawInputWriteQueue() | |||||
{ | |||||
jack_ringbuffer_free(input_ring); | |||||
delete packet_queue; | |||||
} | |||||
void | |||||
JackMidiRawInputWriteQueue::Clear() | |||||
{ | |||||
jack_ringbuffer_reset(input_ring); | |||||
total_bytes = 0; | |||||
unbuffered_bytes = 0; | |||||
} | |||||
Jack::JackMidiWriteQueue::EnqueueResult | |||||
JackMidiRawInputWriteQueue::EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer) | |||||
{ | |||||
return packet_queue->EnqueueEvent(time, size, buffer); | |||||
} | |||||
void | |||||
JackMidiRawInputWriteQueue::HandleBufferFailure(size_t unbuffered_bytes, | |||||
size_t total_bytes) | |||||
{ | |||||
jack_error("JackMidiRawInputWriteQueue::HandleBufferFailure - %d MIDI " | |||||
"byte(s) of a %d byte message could not be buffered. The " | |||||
"message has been dropped.", unbuffered_bytes, total_bytes); | |||||
} | |||||
void | |||||
JackMidiRawInputWriteQueue::HandleEventLoss(jack_midi_event_t *event) | |||||
{ | |||||
jack_error("JackMidiRawInputWriteQueue::HandleEventLoss - A %d byte MIDI " | |||||
"event scheduled for frame '%d' could not be processed because " | |||||
"the write queue cannot accomodate an event of that size. The " | |||||
"event has been discarded.", event->size, event->time); | |||||
} | |||||
void | |||||
JackMidiRawInputWriteQueue::HandleIncompleteMessage(size_t total_bytes) | |||||
{ | |||||
jack_error("JackMidiRawInputWriteQueue::HandleIncompleteMessage - " | |||||
"Discarding %d MIDI byte(s) of an incomplete message. The " | |||||
"MIDI cable may have been unplugged.", total_bytes); | |||||
} | |||||
void | |||||
JackMidiRawInputWriteQueue::HandleInvalidStatusByte(jack_midi_data_t byte) | |||||
{ | |||||
jack_error("JackMidiRawInputWriteQueue::HandleInvalidStatusByte - " | |||||
"Dropping invalid MIDI status byte '%x'.", (unsigned int) byte); | |||||
} | |||||
void | |||||
JackMidiRawInputWriteQueue::HandleUnexpectedSysexEnd(size_t total_bytes) | |||||
{ | |||||
jack_error("JackMidiRawInputWriteQueue::HandleUnexpectedSysexEnd - " | |||||
"Received a sysex end byte without first receiving a sysex " | |||||
"start byte. Discarding %d MIDI byte(s). The cable may have " | |||||
"been unplugged.", total_bytes); | |||||
} | |||||
bool | |||||
JackMidiRawInputWriteQueue::PrepareBufferedEvent(jack_nframes_t time) | |||||
{ | |||||
bool result = ! unbuffered_bytes; | |||||
if (! result) { | |||||
HandleBufferFailure(unbuffered_bytes, total_bytes); | |||||
} else { | |||||
size_t size = jack_ringbuffer_read_space(input_ring); | |||||
jack_ringbuffer_data_t vector[2]; | |||||
jack_ringbuffer_get_read_vector(input_ring, vector); | |||||
// We don't worry about the second part of the vector, as we reset the | |||||
// ringbuffer after each parsed message. | |||||
PrepareEvent(time, size, (jack_midi_data_t *) vector[0].buf); | |||||
} | |||||
Clear(); | |||||
if (status_byte >= 0xf0) { | |||||
expected_bytes = 0; | |||||
status_byte = 0; | |||||
} | |||||
return result; | |||||
} | |||||
bool | |||||
JackMidiRawInputWriteQueue::PrepareByteEvent(jack_nframes_t time, | |||||
jack_midi_data_t byte) | |||||
{ | |||||
event_byte = byte; | |||||
PrepareEvent(time, 1, &event_byte); | |||||
return true; | |||||
} | |||||
void | |||||
JackMidiRawInputWriteQueue::PrepareEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer) | |||||
{ | |||||
event.buffer = buffer; | |||||
event.size = size; | |||||
event.time = time; | |||||
event_pending = true; | |||||
} | |||||
jack_nframes_t | |||||
JackMidiRawInputWriteQueue::Process(jack_nframes_t boundary_frame) | |||||
{ | |||||
if (event_pending) { | |||||
if (! WriteEvent(boundary_frame)) { | |||||
return event.time; | |||||
} | |||||
} | |||||
if (! packet) { | |||||
packet = packet_queue->DequeueEvent(); | |||||
} | |||||
for (; packet; packet = packet_queue->DequeueEvent()) { | |||||
for (; packet->size; (packet->buffer)++, (packet->size)--) { | |||||
if (ProcessByte(packet->time, *(packet->buffer))) { | |||||
if (! WriteEvent(boundary_frame)) { | |||||
(packet->buffer)++; | |||||
(packet->size)--; | |||||
return event.time; | |||||
} | |||||
} | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
bool | |||||
JackMidiRawInputWriteQueue::ProcessByte(jack_nframes_t time, | |||||
jack_midi_data_t byte) | |||||
{ | |||||
if (byte >= 0xf8) { | |||||
// Realtime | |||||
if (byte == 0xfd) { | |||||
HandleInvalidStatusByte(byte); | |||||
return false; | |||||
} | |||||
return PrepareByteEvent(time, byte); | |||||
} | |||||
if (byte == 0xf7) { | |||||
// Sysex end | |||||
if (status_byte == 0xf0) { | |||||
RecordByte(byte); | |||||
return PrepareBufferedEvent(time); | |||||
} | |||||
HandleUnexpectedSysexEnd(total_bytes); | |||||
Clear(); | |||||
expected_bytes = 0; | |||||
status_byte = 0; | |||||
return false; | |||||
} | |||||
if (byte >= 0x80) { | |||||
// Non-realtime status byte | |||||
if (total_bytes) { | |||||
HandleIncompleteMessage(total_bytes); | |||||
Clear(); | |||||
} | |||||
status_byte = byte; | |||||
switch (byte & 0xf0) { | |||||
case 0x80: | |||||
case 0x90: | |||||
case 0xa0: | |||||
case 0xb0: | |||||
case 0xe0: | |||||
// Note On, Note Off, Aftertouch, Control Change, Pitch Wheel | |||||
expected_bytes = 3; | |||||
break; | |||||
case 0xc0: | |||||
case 0xd0: | |||||
// Program Change, Channel Pressure | |||||
expected_bytes = 2; | |||||
break; | |||||
case 0xf0: | |||||
switch (byte) { | |||||
case 0xf0: | |||||
// Sysex | |||||
expected_bytes = 0; | |||||
break; | |||||
case 0xf1: | |||||
case 0xf3: | |||||
// MTC Quarter Frame, Song Select | |||||
expected_bytes = 2; | |||||
break; | |||||
case 0xf2: | |||||
// Song Position | |||||
expected_bytes = 3; | |||||
break; | |||||
case 0xf4: | |||||
case 0xf5: | |||||
// Undefined | |||||
HandleInvalidStatusByte(byte); | |||||
expected_bytes = 0; | |||||
status_byte = 0; | |||||
return false; | |||||
case 0xf6: | |||||
// Tune Request | |||||
bool result = PrepareByteEvent(time, byte); | |||||
if (result) { | |||||
expected_bytes = 0; | |||||
status_byte = 0; | |||||
} | |||||
return result; | |||||
} | |||||
} | |||||
RecordByte(byte); | |||||
return false; | |||||
} | |||||
// Data byte | |||||
if (! status_byte) { | |||||
// Data bytes without a status will be discarded. | |||||
total_bytes++; | |||||
unbuffered_bytes++; | |||||
return false; | |||||
} | |||||
if (! total_bytes) { | |||||
// Apply running status. | |||||
RecordByte(status_byte); | |||||
} | |||||
RecordByte(byte); | |||||
return (total_bytes == expected_bytes) ? PrepareBufferedEvent(time) : | |||||
false; | |||||
} | |||||
void | |||||
JackMidiRawInputWriteQueue::RecordByte(jack_midi_data_t byte) | |||||
{ | |||||
if (jack_ringbuffer_write(input_ring, (const char *) &byte, 1) != 1) { | |||||
unbuffered_bytes++; | |||||
} | |||||
total_bytes++; | |||||
} | |||||
bool | |||||
JackMidiRawInputWriteQueue::WriteEvent(jack_nframes_t boundary_frame) | |||||
{ | |||||
if (event.time < boundary_frame) { | |||||
switch (write_queue->EnqueueEvent(&event)) { | |||||
case BUFFER_TOO_SMALL: | |||||
HandleEventLoss(&event); | |||||
// Fallthrough on purpose | |||||
case OK: | |||||
event_pending = false; | |||||
return true; | |||||
default: | |||||
// This is here to stop compilers from warning us about not | |||||
// handling enumeration values. | |||||
; | |||||
} | |||||
} | |||||
return false; | |||||
} |
@@ -0,0 +1,170 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackMidiRawInputWriteQueue__ | |||||
#define __JackMidiRawInputWriteQueue__ | |||||
#include "JackMidiAsyncQueue.h" | |||||
#include "JackMidiWriteQueue.h" | |||||
#include "ringbuffer.h" | |||||
namespace Jack { | |||||
/** | |||||
* This queue enqueues raw, unparsed MIDI packets, and outputs complete | |||||
* MIDI messages to a write queue. | |||||
* | |||||
* Use this queue if the MIDI API you're interfacing with gives you raw | |||||
* MIDI bytes that must be parsed. | |||||
*/ | |||||
class SERVER_EXPORT JackMidiRawInputWriteQueue: public JackMidiWriteQueue { | |||||
private: | |||||
jack_midi_event_t event; | |||||
jack_midi_data_t event_byte; | |||||
bool event_pending; | |||||
size_t expected_bytes; | |||||
jack_ringbuffer_t *input_ring; | |||||
jack_midi_event_t *packet; | |||||
JackMidiAsyncQueue *packet_queue; | |||||
jack_midi_data_t status_byte; | |||||
size_t total_bytes; | |||||
size_t unbuffered_bytes; | |||||
JackMidiWriteQueue *write_queue; | |||||
void | |||||
Clear(); | |||||
bool | |||||
PrepareBufferedEvent(jack_nframes_t time); | |||||
bool | |||||
PrepareByteEvent(jack_nframes_t time, jack_midi_data_t byte); | |||||
void | |||||
PrepareEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer); | |||||
bool | |||||
ProcessByte(jack_nframes_t time, jack_midi_data_t byte); | |||||
void | |||||
RecordByte(jack_midi_data_t byte); | |||||
bool | |||||
WriteEvent(jack_nframes_t boundary_frame); | |||||
protected: | |||||
/** | |||||
* Override this method to specify what happens when there isn't enough | |||||
* room in the ringbuffer to contain a parsed event. The default | |||||
* method outputs an error message. | |||||
*/ | |||||
virtual void | |||||
HandleBufferFailure(size_t unbuffered_bytes, size_t total_bytes); | |||||
/** | |||||
* Override this method to specify what happens when a parsed event | |||||
* can't be written to the write queue because the event's size exceeds | |||||
* the total possible space in the write queue. The default method | |||||
* outputs an error message. | |||||
*/ | |||||
virtual void | |||||
HandleEventLoss(jack_midi_event_t *event); | |||||
/** | |||||
* Override this method to specify what happens when an incomplete MIDI | |||||
* message is parsed. The default method outputs an error message. | |||||
*/ | |||||
virtual void | |||||
HandleIncompleteMessage(size_t total_bytes); | |||||
/** | |||||
* Override this method to specify what happens when an invalid MIDI | |||||
* status byte is parsed. The default method outputs an error message. | |||||
*/ | |||||
virtual void | |||||
HandleInvalidStatusByte(jack_midi_data_t byte); | |||||
/** | |||||
* Override this method to specify what happens when a sysex end byte | |||||
* is parsed without first parsing a sysex begin byte. The default | |||||
* method outputs an error message. | |||||
*/ | |||||
virtual void | |||||
HandleUnexpectedSysexEnd(size_t total_bytes); | |||||
public: | |||||
using JackMidiWriteQueue::EnqueueEvent; | |||||
/** | |||||
* Called to create a new raw input write queue. The `write_queue` | |||||
* argument is the queue to write parsed messages to. The optional | |||||
* `max_packets` argument specifies the number of packets that can be | |||||
* enqueued in the internal queue. The optional `max_packet_data` | |||||
* argument specifies the total number of MIDI bytes that can be put in | |||||
* the internal queue, AND the maximum size for an event that can be | |||||
* written to the write queue. | |||||
*/ | |||||
JackMidiRawInputWriteQueue(JackMidiWriteQueue *write_queue, | |||||
size_t max_packet_data=4096, | |||||
size_t max_packets=1024); | |||||
~JackMidiRawInputWriteQueue(); | |||||
EnqueueResult | |||||
EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer); | |||||
/** | |||||
* The `Process()` method should be called each time the | |||||
* `EnqueueEvent()` method returns `OK`. The `Process()` method will | |||||
* return the next frame at which an event should be sent. The return | |||||
* value from `Process()` depends upon the result of writing bytes to | |||||
* the write queue: | |||||
* | |||||
* -If the return value is '0', then all *complete* events have been | |||||
* sent successfully to the write queue. Don't call `Process()` again | |||||
* until another event has been enqueued. | |||||
* | |||||
* -If the return value is a non-zero value, then it specifies the | |||||
* frame that a pending event is scheduled to sent at. If the frame is | |||||
* in the future, then `Process()` should be called again at that time; | |||||
* otherwise, `Process()` should be called as soon as the write queue | |||||
* will accept events again. | |||||
*/ | |||||
jack_nframes_t | |||||
Process(jack_nframes_t boundary_frame); | |||||
}; | |||||
} | |||||
#endif |
@@ -0,0 +1,228 @@ | |||||
/* | |||||
Copyright (C) 2010 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 <memory> | |||||
#include <new> | |||||
#include "JackError.h" | |||||
#include "JackMidiRawOutputWriteQueue.h" | |||||
#include "JackMidiUtil.h" | |||||
using Jack::JackMidiRawOutputWriteQueue; | |||||
#define STILL_TIME(c, b) ((! (b)) || ((c) < (b))) | |||||
JackMidiRawOutputWriteQueue:: | |||||
JackMidiRawOutputWriteQueue(JackMidiSendQueue *send_queue, size_t non_rt_size, | |||||
size_t max_non_rt_messages, size_t max_rt_messages) | |||||
{ | |||||
non_rt_queue = new JackMidiAsyncQueue(non_rt_size, max_non_rt_messages); | |||||
std::auto_ptr<JackMidiAsyncQueue> non_rt_ptr(non_rt_queue); | |||||
rt_queue = new JackMidiAsyncQueue(max_rt_messages, max_rt_messages); | |||||
std::auto_ptr<JackMidiAsyncQueue> rt_ptr(rt_queue); | |||||
non_rt_event = 0; | |||||
rt_event = 0; | |||||
running_status = 0; | |||||
this->send_queue = send_queue; | |||||
rt_ptr.release(); | |||||
non_rt_ptr.release(); | |||||
} | |||||
JackMidiRawOutputWriteQueue::~JackMidiRawOutputWriteQueue() | |||||
{ | |||||
delete non_rt_queue; | |||||
delete rt_queue; | |||||
} | |||||
bool | |||||
JackMidiRawOutputWriteQueue::DequeueNonRealtimeEvent() | |||||
{ | |||||
non_rt_event = non_rt_queue->DequeueEvent(); | |||||
bool result = non_rt_event != 0; | |||||
if (result) { | |||||
non_rt_event_time = non_rt_event->time; | |||||
running_status = ApplyRunningStatus(non_rt_event, running_status); | |||||
} | |||||
return result; | |||||
} | |||||
bool | |||||
JackMidiRawOutputWriteQueue::DequeueRealtimeEvent() | |||||
{ | |||||
rt_event = rt_queue->DequeueEvent(); | |||||
bool result = rt_event != 0; | |||||
if (result) { | |||||
rt_event_time = rt_event->time; | |||||
} | |||||
return result; | |||||
} | |||||
Jack::JackMidiWriteQueue::EnqueueResult | |||||
JackMidiRawOutputWriteQueue::EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer) | |||||
{ | |||||
JackMidiAsyncQueue *queue = (size == 1) && (*buffer >= 0xf8) ? rt_queue : | |||||
non_rt_queue; | |||||
EnqueueResult result = queue->EnqueueEvent(time, size, buffer); | |||||
if (result == OK) { | |||||
last_enqueued_message_time = time; | |||||
} | |||||
return result; | |||||
} | |||||
void | |||||
JackMidiRawOutputWriteQueue::HandleWriteQueueBug(jack_nframes_t time, | |||||
jack_midi_data_t byte) | |||||
{ | |||||
jack_error("JackMidiRawOutputWriteQueue::HandleWriteQueueBug - **BUG** " | |||||
"The write queue told us that it couldn't enqueue a 1-byte " | |||||
"MIDI event scheduled for frame '%d'. This is probably a bug " | |||||
"in the write queue implementation.", time); | |||||
} | |||||
jack_nframes_t | |||||
JackMidiRawOutputWriteQueue::Process(jack_nframes_t boundary_frame) | |||||
{ | |||||
jack_nframes_t current_frame = send_queue->GetNextScheduleFrame(); | |||||
while (STILL_TIME(current_frame, boundary_frame)) { | |||||
if (! non_rt_event) { | |||||
DequeueNonRealtimeEvent(); | |||||
} | |||||
if (! rt_event) { | |||||
DequeueRealtimeEvent(); | |||||
} | |||||
if (! (non_rt_event || rt_event)) { | |||||
return 0; | |||||
} | |||||
if (! WriteRealtimeEvents(boundary_frame)) { | |||||
break; | |||||
} | |||||
jack_nframes_t non_rt_boundary = | |||||
rt_event && STILL_TIME(rt_event_time, boundary_frame) ? | |||||
rt_event_time : boundary_frame; | |||||
if (! WriteNonRealtimeEvents(non_rt_boundary)) { | |||||
break; | |||||
} | |||||
current_frame = send_queue->GetNextScheduleFrame(); | |||||
} | |||||
// If we get here, that means there is some sort of message available, and | |||||
// that either we can't currently write to the write queue or we have | |||||
// reached the boundary frame. Return the earliest time that a message is | |||||
// scheduled to be sent. | |||||
return ! non_rt_event ? rt_event_time : | |||||
non_rt_event->size > 1 ? current_frame : | |||||
! rt_event ? non_rt_event_time : | |||||
non_rt_event_time < rt_event_time ? non_rt_event_time : rt_event_time; | |||||
} | |||||
bool | |||||
JackMidiRawOutputWriteQueue::SendByte(jack_nframes_t time, | |||||
jack_midi_data_t byte) | |||||
{ | |||||
switch (send_queue->EnqueueEvent(time, 1, &byte)) { | |||||
case BUFFER_TOO_SMALL: | |||||
HandleWriteQueueBug(time, byte); | |||||
case OK: | |||||
return true; | |||||
default: | |||||
// This is here to stop compilers from warning us about not handling | |||||
// enumeration values. | |||||
; | |||||
} | |||||
return false; | |||||
} | |||||
bool | |||||
JackMidiRawOutputWriteQueue:: | |||||
WriteNonRealtimeEvents(jack_nframes_t boundary_frame) | |||||
{ | |||||
if (! non_rt_event) { | |||||
if (! DequeueNonRealtimeEvent()) { | |||||
return true; | |||||
} | |||||
} | |||||
jack_nframes_t current_frame = send_queue->GetNextScheduleFrame(); | |||||
do { | |||||
// Send out as much of the non-realtime buffer as we can, save for one | |||||
// byte which we will send out when the message is supposed to arrive. | |||||
for (; non_rt_event->size > 1; | |||||
(non_rt_event->size)--, (non_rt_event->buffer)++) { | |||||
if (! STILL_TIME(current_frame, boundary_frame)) { | |||||
return true; | |||||
} | |||||
if (! SendByte(current_frame, *(non_rt_event->buffer))) { | |||||
return false; | |||||
} | |||||
current_frame = send_queue->GetNextScheduleFrame(); | |||||
} | |||||
if (! (STILL_TIME(current_frame, boundary_frame) && | |||||
STILL_TIME(non_rt_event_time, boundary_frame))) { | |||||
return true; | |||||
} | |||||
// There's still time. Try to send the byte. | |||||
if (! SendByte(non_rt_event_time, *(non_rt_event->buffer))) { | |||||
return false; | |||||
} | |||||
current_frame = send_queue->GetNextScheduleFrame(); | |||||
if (! DequeueNonRealtimeEvent()) { | |||||
break; | |||||
} | |||||
} while (STILL_TIME(current_frame, boundary_frame)); | |||||
return true; | |||||
} | |||||
bool | |||||
JackMidiRawOutputWriteQueue::WriteRealtimeEvents(jack_nframes_t boundary_frame) | |||||
{ | |||||
jack_nframes_t current_frame = send_queue->GetNextScheduleFrame(); | |||||
if (! rt_event) { | |||||
if (! DequeueRealtimeEvent()) { | |||||
return true; | |||||
} | |||||
} | |||||
for (;;) { | |||||
if (! STILL_TIME(current_frame, boundary_frame)) { | |||||
return false; | |||||
} | |||||
// If: | |||||
// -there's still time before we need to send the realtime event | |||||
// -there's a non-realtime event available for sending | |||||
// -non-realtime data can be scheduled before this event | |||||
if ((rt_event_time > current_frame) && non_rt_event && | |||||
((non_rt_event->size > 1) || | |||||
(non_rt_event_time < rt_event_time))) { | |||||
return true; | |||||
} | |||||
if (! SendByte(rt_event_time, *(rt_event->buffer))) { | |||||
return false; | |||||
} | |||||
current_frame = send_queue->GetNextScheduleFrame(); | |||||
if (! DequeueRealtimeEvent()) { | |||||
return true; | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,147 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackMidiRawOutputWriteQueue__ | |||||
#define __JackMidiRawOutputWriteQueue__ | |||||
#include "JackMidiAsyncQueue.h" | |||||
#include "JackMidiSendQueue.h" | |||||
namespace Jack { | |||||
/** | |||||
* This queue enqueues valid MIDI events and modifies them for raw output | |||||
* to a write queue. It has a number of advantages over straight MIDI | |||||
* event copying: | |||||
* | |||||
* -Running status: Status bytes can be omitted when the status byte of the | |||||
* current MIDI message is the same as the status byte of the last sent | |||||
* MIDI message. | |||||
* | |||||
* -Realtime messages: Realtime messages are given priority over | |||||
* non-realtime messages. Realtime bytes are interspersed with | |||||
* non-realtime bytes so that realtime messages can be sent as close as | |||||
* possible to the time they're scheduled for sending. | |||||
* | |||||
* -Time optimization: Bytes in non-realtime messages are sent out early | |||||
* when possible, with the last byte of the message being sent out as close | |||||
* to the specified event time as possible. | |||||
* | |||||
* Use this queue if the MIDI API you're interfacing with allows you to | |||||
* send raw MIDI bytes. | |||||
*/ | |||||
class SERVER_EXPORT JackMidiRawOutputWriteQueue: | |||||
public JackMidiWriteQueue { | |||||
private: | |||||
jack_nframes_t last_enqueued_message_time; | |||||
jack_midi_event_t *non_rt_event; | |||||
jack_nframes_t non_rt_event_time; | |||||
JackMidiAsyncQueue *non_rt_queue; | |||||
jack_midi_event_t *rt_event; | |||||
jack_nframes_t rt_event_time; | |||||
JackMidiAsyncQueue *rt_queue; | |||||
jack_midi_data_t running_status; | |||||
JackMidiSendQueue *send_queue; | |||||
bool | |||||
DequeueNonRealtimeEvent(); | |||||
bool | |||||
DequeueRealtimeEvent(); | |||||
bool | |||||
SendByte(jack_nframes_t time, jack_midi_data_t byte); | |||||
bool | |||||
WriteNonRealtimeEvents(jack_nframes_t boundary_frame); | |||||
bool | |||||
WriteRealtimeEvents(jack_nframes_t boundary_frame); | |||||
protected: | |||||
/** | |||||
* Override this method to specify what happens when the write queue | |||||
* says that a 1-byte event is too large for its buffer. Basically, | |||||
* this should never happen. | |||||
*/ | |||||
virtual void | |||||
HandleWriteQueueBug(jack_nframes_t time, jack_midi_data_t byte); | |||||
public: | |||||
using JackMidiWriteQueue::EnqueueEvent; | |||||
/** | |||||
* Called to create a new raw write queue. The `send_queue` argument | |||||
* is the queue to write raw bytes to. The optional `max_rt_messages` | |||||
* argument specifies the number of messages that can be enqueued in | |||||
* the internal realtime queue. The optional `max_non_rt_messages` | |||||
* argument specifies the number of messages that can be enqueued in | |||||
* the internal non-realtime queue. The optional `non_rt_size` | |||||
* argument specifies the total number of MIDI bytes that can be put in | |||||
* the non-realtime queue. | |||||
*/ | |||||
JackMidiRawOutputWriteQueue(JackMidiSendQueue *send_queue, | |||||
size_t non_rt_size=4096, | |||||
size_t max_non_rt_messages=1024, | |||||
size_t max_rt_messages=128); | |||||
~JackMidiRawOutputWriteQueue(); | |||||
EnqueueResult | |||||
EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer); | |||||
/** | |||||
* The `Process()` method should be called each time the | |||||
* `EnqueueEvent()` method returns 'OK'. The `Process()` method will | |||||
* return the next frame at which an event should be sent. The return | |||||
* value from `Process()` depends upon the result of writing bytes to | |||||
* the write queue: | |||||
* | |||||
* -If the return value is '0', then all events that have been enqueued | |||||
* in this queue have been sent successfully to the write queue. Don't | |||||
* call `Process()` again until another event has been enqueued. | |||||
* | |||||
* -If the return value is an earlier frame or the current frame, it | |||||
* means that the write queue returned 'BUFFER_FULL', 'ERROR', or | |||||
* 'EVENT_EARLY' when this queue attempted to send the next byte, and | |||||
* that the byte should have already been sent, or is scheduled to be | |||||
* sent *now*. `Process()` should be called again when the write queue | |||||
* can enqueue events again successfully. How to determine when this | |||||
* will happen is left up to the caller. | |||||
* | |||||
* -If the return value is in the future, then `Process()` should be | |||||
* called again at that time, or after another event is enqueued. | |||||
*/ | |||||
jack_nframes_t | |||||
Process(jack_nframes_t boundary_frame); | |||||
}; | |||||
} | |||||
#endif |
@@ -0,0 +1,27 @@ | |||||
/* | |||||
Copyright (C) 2010 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 "JackMidiReadQueue.h" | |||||
using Jack::JackMidiReadQueue; | |||||
JackMidiReadQueue::~JackMidiReadQueue() | |||||
{ | |||||
// Empty | |||||
} |
@@ -0,0 +1,55 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackMidiReadQueue__ | |||||
#define __JackMidiReadQueue__ | |||||
#include "JackMidiPort.h" | |||||
namespace Jack { | |||||
/** | |||||
* Interface for objects that MIDI events can be read from. | |||||
*/ | |||||
class SERVER_EXPORT JackMidiReadQueue { | |||||
public: | |||||
virtual | |||||
~JackMidiReadQueue(); | |||||
/** | |||||
* Dequeues an event from the queue. Returns the event, or 0 if no | |||||
* events are available for reading. | |||||
* | |||||
* An event dequeued from the read queue is guaranteed to be valid up | |||||
* until another event is dequeued, at which all bets are off. Make | |||||
* sure that you handle each event you dequeue before dequeueing the | |||||
* next event. | |||||
*/ | |||||
virtual jack_midi_event_t * | |||||
DequeueEvent() = 0; | |||||
}; | |||||
} | |||||
#endif |
@@ -0,0 +1,27 @@ | |||||
/* | |||||
Copyright (C) 2010 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 "JackMidiReceiveQueue.h" | |||||
using Jack::JackMidiReceiveQueue; | |||||
JackMidiReceiveQueue::~JackMidiReceiveQueue() | |||||
{ | |||||
// Empty | |||||
} |
@@ -0,0 +1,42 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackMidiReceiveQueue__ | |||||
#define __JackMidiReceiveQueue__ | |||||
#include "JackMidiReadQueue.h" | |||||
namespace Jack { | |||||
/** | |||||
* Implemented by MIDI input connections. | |||||
*/ | |||||
class SERVER_EXPORT JackMidiReceiveQueue: public JackMidiReadQueue { | |||||
public: | |||||
virtual | |||||
~JackMidiReceiveQueue(); | |||||
}; | |||||
} | |||||
#endif |
@@ -0,0 +1,34 @@ | |||||
/* | |||||
Copyright (C) 2010 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 "JackMidiSendQueue.h" | |||||
#include "JackMidiUtil.h" | |||||
using Jack::JackMidiSendQueue; | |||||
JackMidiSendQueue::~JackMidiSendQueue() | |||||
{ | |||||
// Empty | |||||
} | |||||
jack_nframes_t | |||||
JackMidiSendQueue::GetNextScheduleFrame() | |||||
{ | |||||
return GetCurrentFrame(); | |||||
} |
@@ -0,0 +1,52 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackMidiSendQueue__ | |||||
#define __JackMidiSendQueue__ | |||||
#include "JackMidiWriteQueue.h" | |||||
namespace Jack { | |||||
/** | |||||
* Implemented by MIDI output connections. | |||||
*/ | |||||
class SERVER_EXPORT JackMidiSendQueue: public JackMidiWriteQueue { | |||||
public: | |||||
using JackMidiWriteQueue::EnqueueEvent; | |||||
virtual | |||||
~JackMidiSendQueue(); | |||||
/** | |||||
* Returns the next frame that a MIDI message can be sent at. The | |||||
* default method returns the current frame. | |||||
*/ | |||||
virtual jack_nframes_t | |||||
GetNextScheduleFrame(); | |||||
}; | |||||
} | |||||
#endif |
@@ -0,0 +1,120 @@ | |||||
/* | |||||
Copyright (C) 2010 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 "JackEngineControl.h" | |||||
#include "JackFrameTimer.h" | |||||
#include "JackGlobals.h" | |||||
#include "JackMidiUtil.h" | |||||
#include "JackTime.h" | |||||
jack_midi_data_t | |||||
Jack::ApplyRunningStatus(size_t *size, jack_midi_data_t **buffer, | |||||
jack_midi_data_t running_status) | |||||
{ | |||||
// Stolen and modified from alsa/midi_pack.h | |||||
jack_midi_data_t status = **buffer; | |||||
if ((status >= 0x80) && (status < 0xf0)) { | |||||
if (status == running_status) { | |||||
(*buffer)++; | |||||
(*size)--; | |||||
} else { | |||||
running_status = status; | |||||
} | |||||
} else if (status < 0xf8) { | |||||
running_status = 0; | |||||
} | |||||
return running_status; | |||||
} | |||||
jack_midi_data_t | |||||
Jack::ApplyRunningStatus(jack_midi_event_t *event, | |||||
jack_midi_data_t running_status) | |||||
{ | |||||
return ApplyRunningStatus(&(event->size), &(event->buffer), | |||||
running_status); | |||||
} | |||||
jack_nframes_t | |||||
Jack::GetCurrentFrame() | |||||
{ | |||||
JackEngineControl *control = GetEngineControl(); | |||||
JackTimer timer; | |||||
control->ReadFrameTime(&timer); | |||||
return timer.Time2Frames(GetMicroSeconds(), control->fBufferSize); | |||||
} | |||||
jack_nframes_t | |||||
Jack::GetFramesFromTime(jack_time_t time) | |||||
{ | |||||
JackEngineControl* control = GetEngineControl(); | |||||
JackTimer timer; | |||||
control->ReadFrameTime(&timer); | |||||
return timer.Time2Frames(time, control->fBufferSize); | |||||
} | |||||
jack_nframes_t | |||||
Jack::GetLastFrame() | |||||
{ | |||||
return GetEngineControl()->fFrameTimer.ReadCurrentState()->CurFrame(); | |||||
} | |||||
int | |||||
Jack::GetMessageLength(jack_midi_data_t status_byte) | |||||
{ | |||||
switch (status_byte & 0xf0) { | |||||
case 0x80: | |||||
case 0x90: | |||||
case 0xa0: | |||||
case 0xb0: | |||||
case 0xe0: | |||||
return 3; | |||||
case 0xc0: | |||||
case 0xd0: | |||||
return 2; | |||||
case 0xf0: | |||||
switch (status_byte) { | |||||
case 0xf0: | |||||
return 0; | |||||
case 0xf1: | |||||
case 0xf3: | |||||
return 2; | |||||
case 0xf2: | |||||
return 3; | |||||
case 0xf4: | |||||
case 0xf5: | |||||
case 0xf7: | |||||
case 0xfd: | |||||
break; | |||||
default: | |||||
return 1; | |||||
} | |||||
} | |||||
return -1; | |||||
} | |||||
jack_time_t | |||||
Jack::GetTimeFromFrames(jack_nframes_t frames) | |||||
{ | |||||
JackEngineControl* control = GetEngineControl(); | |||||
JackTimer timer; | |||||
control->ReadFrameTime(&timer); | |||||
return timer.Frames2Time(frames, control->fBufferSize); | |||||
} |
@@ -0,0 +1,102 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackMidiUtil__ | |||||
#define __JackMidiUtil__ | |||||
#include "JackMidiPort.h" | |||||
namespace Jack { | |||||
/** | |||||
* Use this function to optimize MIDI output by omitting unnecessary status | |||||
* bytes. This can't be used with all MIDI APIs, so before using this | |||||
* function, make sure that your MIDI API doesn't require complete MIDI | |||||
* messages to be sent. | |||||
* | |||||
* To start using this function, call this method with pointers to the | |||||
* `size` and `buffer` arguments of the MIDI message you want to send, and | |||||
* set the `running_status` argument to '0'. For each subsequent MIDI | |||||
* message, call this method with pointers to its `size` and `buffer` | |||||
* arguments, and set the `running_status` argument to the return value of | |||||
* the previous call to this function. | |||||
* | |||||
* Note: This function will alter the `size` and `buffer` of your MIDI | |||||
* message for each message that can be optimized. | |||||
*/ | |||||
SERVER_EXPORT jack_midi_data_t | |||||
ApplyRunningStatus(size_t *size, jack_midi_data_t **buffer, | |||||
jack_midi_data_t running_status=0); | |||||
/** | |||||
* A wrapper function for the above `ApplyRunningStatus` function. | |||||
*/ | |||||
SERVER_EXPORT jack_midi_data_t | |||||
ApplyRunningStatus(jack_midi_event_t *event, | |||||
jack_midi_data_t running_status); | |||||
/** | |||||
* Gets the estimated current time in frames. This function has the same | |||||
* functionality as the JACK client API function `jack_frame_time`. | |||||
*/ | |||||
SERVER_EXPORT jack_nframes_t | |||||
GetCurrentFrame(); | |||||
/** | |||||
* Gets the estimated frame that will be occurring at the given time. This | |||||
* function has the same functionality as the JACK client API function | |||||
* `jack_time_to_frames`. | |||||
*/ | |||||
SERVER_EXPORT jack_nframes_t | |||||
GetFramesFromTime(jack_time_t time); | |||||
/** | |||||
* Gets the precise time at the start of the current process cycle. This | |||||
* function has the same functionality as the JACK client API function | |||||
* `jack_last_frame_time`. | |||||
*/ | |||||
SERVER_EXPORT jack_nframes_t | |||||
GetLastFrame(); | |||||
/** | |||||
* Returns the expected message length for the status byte. Returns 0 if | |||||
* the status byte is a system exclusive status byte, or -1 if the status | |||||
* byte is invalid. | |||||
*/ | |||||
SERVER_EXPORT int | |||||
GetMessageLength(jack_midi_data_t status_byte); | |||||
/** | |||||
* Gets the estimated time at which the given frame will occur. This | |||||
* function has the same functionality as the JACK client API function | |||||
* `jack_frames_to_time`. | |||||
*/ | |||||
SERVER_EXPORT jack_time_t | |||||
GetTimeFromFrames(jack_nframes_t frames); | |||||
}; | |||||
#endif |
@@ -0,0 +1,27 @@ | |||||
/* | |||||
Copyright (C) 2010 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 "JackMidiWriteQueue.h" | |||||
using Jack::JackMidiWriteQueue; | |||||
JackMidiWriteQueue::~JackMidiWriteQueue() | |||||
{ | |||||
// Empty | |||||
} |
@@ -0,0 +1,79 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackMidiWriteQueue__ | |||||
#define __JackMidiWriteQueue__ | |||||
#include "JackMidiPort.h" | |||||
namespace Jack { | |||||
/** | |||||
* Interface for classes that act as write queues for MIDI messages. Write | |||||
* queues are used by processors to transfer data to the next processor. | |||||
*/ | |||||
class SERVER_EXPORT JackMidiWriteQueue { | |||||
public: | |||||
enum EnqueueResult { | |||||
BUFFER_FULL, | |||||
BUFFER_TOO_SMALL, | |||||
EVENT_EARLY, | |||||
ERROR, | |||||
OK | |||||
}; | |||||
virtual ~JackMidiWriteQueue(); | |||||
/** | |||||
* Enqueues a data packet in the write queue of `size` bytes contained | |||||
* in `buffer` that will be sent the absolute time specified by `time`. | |||||
* This method should not block unless 1.) this write queue represents | |||||
* the actual outbound MIDI connection, 2.) the MIDI event is being | |||||
* sent *now*, meaning that `time` is less than or equal to *now*, and | |||||
* 3.) the method is *not* being called in the process thread. The | |||||
* method should return `OK` if the event was enqueued, `BUFFER_FULL` | |||||
* if the write queue isn't able to accept the event right now, | |||||
* `BUFFER_TOO_SMALL` if this write queue will never be able to accept | |||||
* the event because the event is too large, `EVENT_EARLY` if this | |||||
* queue cannot schedule events ahead of time, and `ERROR` if an error | |||||
* occurs that cannot be specified by another return code. | |||||
*/ | |||||
virtual EnqueueResult | |||||
EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer) = 0; | |||||
/** | |||||
* A wrapper method for the `EnqueueEvent` method above. | |||||
*/ | |||||
inline EnqueueResult | |||||
EnqueueEvent(jack_midi_event_t *event) | |||||
{ | |||||
return EnqueueEvent(event->time, event->size, event->buffer); | |||||
} | |||||
}; | |||||
} | |||||
#endif |
@@ -1,287 +0,0 @@ | |||||
/* | |||||
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); | |||||
} | |||||
} | |||||
} |
@@ -1,146 +0,0 @@ | |||||
/* | |||||
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); | |||||
virtual ~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 |
@@ -1,320 +0,0 @@ | |||||
/* | |||||
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 < 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; | |||||
} | |||||
} |
@@ -1,118 +0,0 @@ | |||||
/* | |||||
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); | |||||
virtual ~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 |
@@ -130,8 +130,21 @@ def build(bld): | |||||
'JackNetTool.cpp', | 'JackNetTool.cpp', | ||||
'JackNetInterface.cpp', | 'JackNetInterface.cpp', | ||||
'JackArgParser.cpp', | 'JackArgParser.cpp', | ||||
'JackPhysicalMidiInput.cpp', | |||||
'JackPhysicalMidiOutput.cpp', | |||||
#'JackPhysicalMidiInput.cpp', | |||||
#'JackPhysicalMidiOutput.cpp', | |||||
'JackMidiAsyncQueue.cpp', | |||||
'JackMidiAsyncWaitQueue.cpp', | |||||
'JackMidiBufferReadQueue.cpp', | |||||
'JackMidiBufferWriteQueue.cpp', | |||||
'JackMidiRawInputWriteQueue.cpp', | |||||
'JackMidiRawOutputWriteQueue.cpp', | |||||
'JackMidiReadQueue.cpp', | |||||
'JackMidiReceiveQueue.cpp', | |||||
'JackMidiSendQueue.cpp', | |||||
'JackMidiUtil.cpp', | |||||
'JackMidiWriteQueue.cpp' | |||||
] | ] | ||||
if bld.env['IS_LINUX']: | if bld.env['IS_LINUX']: | ||||
@@ -0,0 +1,588 @@ | |||||
/* | |||||
Copyright (C) 2010 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. | |||||
*/ | |||||
/* | |||||
* This program is used to measure MIDI latency and jitter. It writes MIDI | |||||
* messages to one port and calculates how long it takes before it reads the | |||||
* same MIDI message over another port. It was written to calculate the | |||||
* latency and jitter of hardware and JACK hardware drivers, but might have | |||||
* other practical applications. | |||||
* | |||||
* The latency results of the program include the latency introduced by the | |||||
* JACK system. Because JACK has sample accurate MIDI, the same latency | |||||
* imposed on audio is also imposed on MIDI going through the system. Make | |||||
* sure you take this into account before complaining to me or (*especially*) | |||||
* other JACK developers about reported MIDI latency. | |||||
* | |||||
* The jitter results are a little more interesting. The program attempts to | |||||
* calculate 'average jitter' and 'peak jitter', as defined here: | |||||
* | |||||
* http://openmuse.org/transport/fidelity.html | |||||
* | |||||
* It also outputs a jitter plot, which gives you a more specific idea about | |||||
* the MIDI jitter for the ports you're testing. This is useful for catching | |||||
* extreme jitter values, and for analyzing the amount of truth in the | |||||
* technical specifications for your MIDI interface(s). :) | |||||
* | |||||
* This program is loosely based on 'alsa-midi-latency-test' in the ALSA test | |||||
* suite. | |||||
* | |||||
* To port this program to non-POSIX platforms, you'll have to include | |||||
* implementations for mutexes, semaphores, and command-line argument handling. | |||||
*/ | |||||
#include <errno.h> | |||||
#include <stdio.h> | |||||
#include <stdlib.h> | |||||
#include <string.h> | |||||
#include <getopt.h> | |||||
#include <pthread.h> | |||||
#include <semaphore.h> | |||||
#include <jack/jack.h> | |||||
#include <jack/midiport.h> | |||||
#define ABS(x) (((x) >= 0) ? (x) : (-(x))) | |||||
const char *ERROR_UNEXPECTED = "in port received unexpected MIDI message"; | |||||
const char *ERROR_UNEXPECTED_EXTRA = "received more than one MIDI message"; | |||||
const char *ERROR_RESERVE = "could not reserve MIDI event on port buffer"; | |||||
const char *ERROR_TIMEOUT = "timed out while waiting for MIDI message"; | |||||
const char *SOURCE_EVENT_RESERVE = "jack_midi_event_reserve"; | |||||
const char *SOURCE_PROCESS = "handle_process"; | |||||
const char *SOURCE_TRYLOCK = "pthread_mutex_trylock"; | |||||
const char *SOURCE_UNLOCK = "pthread_mutex_unlock"; | |||||
jack_client_t *client; | |||||
const char *error_message; | |||||
const char *error_source; | |||||
jack_nframes_t highest_latency; | |||||
jack_time_t highest_latency_time; | |||||
jack_latency_range_t in_latency_range; | |||||
jack_port_t *in_port; | |||||
jack_nframes_t last_activity; | |||||
jack_time_t last_activity_time; | |||||
jack_time_t *latency_time_values; | |||||
jack_nframes_t *latency_values; | |||||
jack_nframes_t lowest_latency; | |||||
jack_time_t lowest_latency_time; | |||||
jack_midi_data_t *message_1; | |||||
jack_midi_data_t *message_2; | |||||
size_t messages_processed; | |||||
size_t message_size; | |||||
jack_latency_range_t out_latency_range; | |||||
jack_port_t *out_port; | |||||
int process_state; | |||||
char *program_name; | |||||
jack_port_t *remote_in_port; | |||||
jack_port_t *remote_out_port; | |||||
size_t samples; | |||||
sem_t semaphore; | |||||
pthread_mutex_t start_mutex; | |||||
int timeout; | |||||
jack_nframes_t total_latency; | |||||
jack_time_t total_latency_time; | |||||
size_t unexpected_messages; | |||||
static void | |||||
output_error(const char *source, const char *message); | |||||
static void | |||||
output_usage(); | |||||
static void | |||||
set_process_error(const char *source, const char *message); | |||||
static void | |||||
die(char *source, char *error_message) | |||||
{ | |||||
output_error(source, error_message); | |||||
output_usage(); | |||||
exit(EXIT_FAILURE); | |||||
} | |||||
static void | |||||
handle_info(const char *message) | |||||
{ | |||||
/* Suppress info */ | |||||
} | |||||
static int | |||||
handle_process(jack_nframes_t frames, void *arg) | |||||
{ | |||||
jack_midi_data_t *buffer; | |||||
int code; | |||||
jack_midi_event_t event; | |||||
jack_nframes_t event_count; | |||||
jack_nframes_t event_time; | |||||
jack_nframes_t frame; | |||||
size_t i; | |||||
jack_nframes_t last_frame_time; | |||||
jack_midi_data_t *message; | |||||
void *port_buffer; | |||||
jack_time_t time; | |||||
switch (process_state) { | |||||
case 0: | |||||
/* State: initializing */ | |||||
code = pthread_mutex_trylock(&start_mutex); | |||||
if (code) { | |||||
if (code != EBUSY) { | |||||
set_process_error(SOURCE_TRYLOCK, strerror(code)); | |||||
} | |||||
break; | |||||
} | |||||
code = pthread_mutex_unlock(&start_mutex); | |||||
if (code) { | |||||
set_process_error(SOURCE_UNLOCK, strerror(code)); | |||||
break; | |||||
} | |||||
highest_latency = 0; | |||||
lowest_latency = 0; | |||||
messages_processed = 0; | |||||
process_state = 1; | |||||
total_latency = 0; | |||||
total_latency_time = 0; | |||||
unexpected_messages = 0; | |||||
jack_port_get_latency_range(remote_in_port, JackCaptureLatency, | |||||
&in_latency_range); | |||||
jack_port_get_latency_range(remote_out_port, JackPlaybackLatency, | |||||
&out_latency_range); | |||||
goto send_message; | |||||
case 1: | |||||
/* State: processing */ | |||||
jack_midi_clear_buffer(jack_port_get_buffer(out_port, frames)); | |||||
port_buffer = jack_port_get_buffer(in_port, frames); | |||||
event_count = jack_midi_get_event_count(port_buffer); | |||||
last_frame_time = jack_last_frame_time(client); | |||||
for (i = 0; i < event_count; i++) { | |||||
jack_midi_event_get(&event, port_buffer, i); | |||||
message = (messages_processed % 2) ? message_2 : message_1; | |||||
if ((event.size == message_size) && | |||||
(! memcmp(message, event.buffer, | |||||
message_size * sizeof(jack_midi_data_t)))) { | |||||
goto found_message; | |||||
} | |||||
unexpected_messages++; | |||||
} | |||||
jack_time_t microseconds = | |||||
jack_frames_to_time(client, last_frame_time) - last_activity_time; | |||||
if ((microseconds / 1000000) >= timeout) { | |||||
set_process_error(SOURCE_PROCESS, ERROR_TIMEOUT); | |||||
} | |||||
break; | |||||
found_message: | |||||
event_time = last_frame_time + event.time; | |||||
frame = event_time - last_activity; | |||||
time = jack_frames_to_time(client, event_time) - last_activity_time; | |||||
if ((! highest_latency) || (frame > highest_latency)) { | |||||
highest_latency = frame; | |||||
highest_latency_time = time; | |||||
} | |||||
if ((! lowest_latency) || (frame < lowest_latency)) { | |||||
lowest_latency = frame; | |||||
lowest_latency_time = time; | |||||
} | |||||
latency_time_values[messages_processed] = time; | |||||
latency_values[messages_processed] = frame; | |||||
total_latency += frame; | |||||
total_latency_time += time; | |||||
messages_processed++; | |||||
if (messages_processed == samples) { | |||||
process_state = 2; | |||||
sem_post(&semaphore); | |||||
break; | |||||
} | |||||
send_message: | |||||
frame = (jack_nframes_t) ((((double) rand()) / RAND_MAX) * frames); | |||||
if (frame == frames) { | |||||
frame--; | |||||
} | |||||
port_buffer = jack_port_get_buffer(out_port, frames); | |||||
jack_midi_clear_buffer(port_buffer); | |||||
buffer = jack_midi_event_reserve(port_buffer, frame, message_size); | |||||
if (buffer == NULL) { | |||||
set_process_error(SOURCE_EVENT_RESERVE, ERROR_RESERVE); | |||||
break; | |||||
} | |||||
message = (messages_processed % 2) ? message_2 : message_1; | |||||
memcpy(buffer, message, message_size * sizeof(jack_midi_data_t)); | |||||
last_activity = jack_last_frame_time(client) + frame; | |||||
last_activity_time = jack_frames_to_time(client, last_activity); | |||||
case 2: | |||||
/* State: finished - do nothing */ | |||||
case -1: | |||||
/* State: error - do nothing */ | |||||
; | |||||
} | |||||
return 0; | |||||
} | |||||
static void | |||||
handle_shutdown(void *arg) | |||||
{ | |||||
set_process_error("handle_shutdown", "The JACK server has been shutdown"); | |||||
} | |||||
static void | |||||
output_error(const char *source, const char *message) | |||||
{ | |||||
fprintf(stderr, "%s: %s: %s\n", program_name, source, message); | |||||
} | |||||
static void | |||||
output_usage() | |||||
{ | |||||
fprintf(stderr, "Usage: %s [options] out-port-name in-port-name\n\n" | |||||
"\t-h, --help print program usage\n" | |||||
"\t-m, --message-size=size set size of MIDI messages to send\n" | |||||
"\t-s, --samples=n number of MIDI messages to send\n" | |||||
"\t-t, --timeout=seconds wait time before giving up on message\n" | |||||
"\n", program_name); | |||||
} | |||||
static unsigned long | |||||
parse_positive_number_arg(char *s, char *name) | |||||
{ | |||||
char *end_ptr; | |||||
unsigned long result; | |||||
errno = 0; | |||||
result = strtoul(s, &end_ptr, 10); | |||||
if (errno) { | |||||
die(name, strerror(errno)); | |||||
} | |||||
if (*s == '\0') { | |||||
die(name, "argument value cannot be empty"); | |||||
} | |||||
if (*end_ptr != '\0') { | |||||
die(name, "invalid value"); | |||||
} | |||||
if (! result) { | |||||
die(name, "must be a positive number"); | |||||
} | |||||
return result; | |||||
} | |||||
static void | |||||
set_process_error(const char *source, const char *message) | |||||
{ | |||||
error_source = source; | |||||
error_message = message; | |||||
process_state = -1; | |||||
sem_post(&semaphore); | |||||
} | |||||
int | |||||
main(int argc, char **argv) | |||||
{ | |||||
int code; | |||||
size_t jitter_plot[101]; | |||||
int long_index = 0; | |||||
struct option long_options[] = { | |||||
{"help", 0, NULL, 'h'}, | |||||
{"message-size", 1, NULL, 'm'}, | |||||
{"samples", 1, NULL, 's'}, | |||||
{"timeout", 1, NULL, 't'} | |||||
}; | |||||
char *option_string = "hm:s:t:"; | |||||
int show_usage = 0; | |||||
error_message = NULL; | |||||
message_size = 3; | |||||
program_name = argv[0]; | |||||
samples = 1024; | |||||
timeout = 5; | |||||
for (;;) { | |||||
char c = getopt_long(argc, argv, option_string, long_options, | |||||
&long_index); | |||||
switch (c) { | |||||
case 'h': | |||||
show_usage = 1; | |||||
break; | |||||
case 'm': | |||||
message_size = parse_positive_number_arg(optarg, "message-size"); | |||||
break; | |||||
case 's': | |||||
samples = parse_positive_number_arg(optarg, "samples"); | |||||
break; | |||||
case 't': | |||||
timeout = parse_positive_number_arg(optarg, "timeout"); | |||||
break; | |||||
default: | |||||
{ | |||||
char *s = "'- '"; | |||||
s[2] = c; | |||||
die(s, "invalid switch"); | |||||
} | |||||
case -1: | |||||
if (show_usage) { | |||||
output_usage(); | |||||
exit(EXIT_SUCCESS); | |||||
} | |||||
goto parse_port_names; | |||||
case 1: | |||||
/* end of switch :) */ | |||||
; | |||||
} | |||||
} | |||||
parse_port_names: | |||||
if ((argc - optind) != 2) { | |||||
output_usage(); | |||||
return EXIT_FAILURE; | |||||
} | |||||
latency_values = malloc(sizeof(jack_nframes_t) * samples); | |||||
if (latency_values == NULL) { | |||||
error_message = strerror(errno); | |||||
error_source = "malloc"; | |||||
goto show_error; | |||||
} | |||||
latency_time_values = malloc(sizeof(jack_time_t) * samples); | |||||
if (latency_time_values == NULL) { | |||||
error_message = strerror(errno); | |||||
error_source = "malloc"; | |||||
goto free_latency_values; | |||||
} | |||||
message_1 = malloc(message_size * sizeof(jack_midi_data_t)); | |||||
if (message_1 == NULL) { | |||||
error_message = strerror(errno); | |||||
error_source = "malloc"; | |||||
goto free_latency_time_values; | |||||
} | |||||
message_2 = malloc(message_size * sizeof(jack_midi_data_t)); | |||||
if (message_2 == NULL) { | |||||
error_message = strerror(errno); | |||||
error_source = "malloc"; | |||||
goto free_message_1; | |||||
} | |||||
switch (message_size) { | |||||
case 1: | |||||
message_1[0] = 0xf6; | |||||
message_2[0] = 0xf8; | |||||
break; | |||||
case 2: | |||||
message_1[0] = 0xc0; | |||||
message_1[1] = 0x00; | |||||
message_2[0] = 0xd0; | |||||
message_2[1] = 0x7f; | |||||
break; | |||||
case 3: | |||||
message_1[0] = 0x80; | |||||
message_1[1] = 0x00; | |||||
message_1[2] = 0x00; | |||||
message_2[0] = 0x90; | |||||
message_2[1] = 0x7f; | |||||
message_2[2] = 0x7f; | |||||
break; | |||||
default: | |||||
message_1[0] = 0xf0; | |||||
memset(message_1 + 1, 0, | |||||
(message_size - 2) * sizeof(jack_midi_data_t)); | |||||
message_1[message_size - 1] = 0xf7; | |||||
message_2[0] = 0xf0; | |||||
memset(message_2 + 1, 0x7f, | |||||
(message_size - 2) * sizeof(jack_midi_data_t)); | |||||
message_2[message_size - 1] = 0xf7; | |||||
} | |||||
client = jack_client_open(program_name, JackNullOption, NULL); | |||||
if (client == NULL) { | |||||
error_message = "failed to open JACK client"; | |||||
error_source = "jack_client_open"; | |||||
goto free_message_2; | |||||
} | |||||
remote_in_port = jack_port_by_name(client, argv[optind + 1]); | |||||
if (remote_in_port == NULL) { | |||||
error_message = "invalid port name"; | |||||
error_source = argv[optind + 1]; | |||||
goto close_client; | |||||
} | |||||
remote_out_port = jack_port_by_name(client, argv[optind]); | |||||
if (remote_out_port == NULL) { | |||||
error_message = "invalid port name"; | |||||
error_source = argv[optind]; | |||||
goto close_client; | |||||
} | |||||
in_port = jack_port_register(client, "in", JACK_DEFAULT_MIDI_TYPE, | |||||
JackPortIsInput, 0); | |||||
if (in_port == NULL) { | |||||
error_message = "failed to register MIDI-in port"; | |||||
error_source = "jack_port_register"; | |||||
goto close_client; | |||||
} | |||||
out_port = jack_port_register(client, "out", JACK_DEFAULT_MIDI_TYPE, | |||||
JackPortIsOutput, 0); | |||||
if (out_port == NULL) { | |||||
error_message = "failed to register MIDI-out port"; | |||||
error_source = "jack_port_register"; | |||||
goto unregister_in_port; | |||||
} | |||||
if (jack_set_process_callback(client, handle_process, NULL)) { | |||||
error_message = "failed to set process callback"; | |||||
error_source = "jack_set_process_callback"; | |||||
goto unregister_out_port; | |||||
} | |||||
jack_on_shutdown(client, handle_shutdown, NULL); | |||||
jack_set_info_function(handle_info); | |||||
process_state = 0; | |||||
if (sem_init(&semaphore, 0, 0)) { | |||||
error_message = strerror(errno); | |||||
error_source = "sem_init"; | |||||
goto unregister_out_port; | |||||
} | |||||
code = pthread_mutex_init(&start_mutex, NULL); | |||||
if (code) { | |||||
error_message = strerror(errno); | |||||
error_source = "pthread_mutex_init"; | |||||
goto destroy_semaphore; | |||||
} | |||||
code = pthread_mutex_trylock(&start_mutex); | |||||
if (code) { | |||||
error_message = strerror(code); | |||||
error_source = "pthread_mutex_trylock"; | |||||
goto destroy_mutex; | |||||
} | |||||
if (jack_activate(client)) { | |||||
error_message = "could not activate client"; | |||||
error_source = "jack_activate"; | |||||
goto destroy_mutex; | |||||
} | |||||
if (jack_connect(client, jack_port_name(out_port), | |||||
jack_port_name(remote_out_port))) { | |||||
error_message = "could not connect MIDI out port"; | |||||
error_source = "jack_connect"; | |||||
goto deactivate_client; | |||||
} | |||||
if (jack_connect(client, jack_port_name(remote_in_port), | |||||
jack_port_name(in_port))) { | |||||
error_message = "could not connect MIDI in port"; | |||||
error_source = "jack_connect"; | |||||
goto deactivate_client; | |||||
} | |||||
jack_port_get_latency_range(remote_in_port, JackCaptureLatency, | |||||
&in_latency_range); | |||||
jack_port_get_latency_range(remote_out_port, JackPlaybackLatency, | |||||
&out_latency_range); | |||||
code = pthread_mutex_unlock(&start_mutex); | |||||
if (code) { | |||||
error_message = strerror(code); | |||||
error_source = "pthread_mutex_unlock"; | |||||
goto deactivate_client; | |||||
} | |||||
if (sem_wait(&semaphore)) { | |||||
error_message = strerror(errno); | |||||
error_source = "sem_wait"; | |||||
goto deactivate_client; | |||||
} | |||||
if (process_state == 2) { | |||||
double average_latency = ((double) total_latency) / samples; | |||||
double average_latency_time = total_latency_time / samples; | |||||
double high_jitter = highest_latency - average_latency; | |||||
size_t i; | |||||
double low_jitter = average_latency - lowest_latency; | |||||
double peak_jitter; | |||||
double peak_jitter_time; | |||||
double sample_rate = (double) jack_get_sample_rate(client); | |||||
jack_nframes_t total_jitter = 0; | |||||
jack_time_t total_jitter_time = 0; | |||||
if (high_jitter > low_jitter) { | |||||
peak_jitter = high_jitter; | |||||
peak_jitter_time = highest_latency_time - average_latency_time; | |||||
} else { | |||||
peak_jitter = low_jitter; | |||||
peak_jitter_time = average_latency_time - lowest_latency_time; | |||||
} | |||||
for (i = 0; i <= 100; i++) { | |||||
jitter_plot[i] = 0; | |||||
} | |||||
for (i = 0; i < samples; i++) { | |||||
double jitter_time = ABS(average_latency_time - | |||||
((double) latency_time_values[i])); | |||||
if (jitter_time >= 10000.0) { | |||||
(jitter_plot[100])++; | |||||
} else { | |||||
(jitter_plot[(int) (jitter_time / 100.0)])++; | |||||
} | |||||
total_jitter += ABS(average_latency - | |||||
((double) latency_values[i])); | |||||
total_jitter_time += jitter_time; | |||||
} | |||||
printf("Reported out-port latency: %.2f-%.2f ms (%u-%u frames)\n" | |||||
"Reported in-port latency: %.2f-%.2f ms (%u-%u frames)\n" | |||||
"Average latency: %.2f ms (%.2f frames)\n" | |||||
"Best latency: %.2f ms (%u frames)\n" | |||||
"Worst latency: %.2f ms (%u frames)\n" | |||||
"Peak MIDI jitter: %.2f ms (%.2f frames)\n" | |||||
"Average MIDI jitter: %.2f ms (%.2f frames)\n", | |||||
(out_latency_range.min / sample_rate) * 1000.0, | |||||
(out_latency_range.max / sample_rate) * 1000.0, | |||||
out_latency_range.min, out_latency_range.max, | |||||
(in_latency_range.min / sample_rate) * 1000.0, | |||||
(in_latency_range.max / sample_rate) * 1000.0, | |||||
in_latency_range.min, in_latency_range.max, | |||||
average_latency_time / 1000.0, average_latency, | |||||
lowest_latency_time / 1000.0, lowest_latency, | |||||
highest_latency_time / 1000.0, highest_latency, | |||||
peak_jitter_time / 1000.0, peak_jitter, | |||||
(total_jitter_time / 1000.0) / samples, | |||||
((double) total_jitter) / samples); | |||||
printf("\nJitter Plot:\n"); | |||||
for (i = 0; i < 100; i++) { | |||||
if (jitter_plot[i]) { | |||||
printf("%.1f - %.1f ms: %u\n", ((float) i) / 10.0, | |||||
((float) (i + 1)) / 10.0, jitter_plot[i]); | |||||
} | |||||
} | |||||
if (jitter_plot[100]) { | |||||
printf(" > 10 ms: %u\n", jitter_plot[100]); | |||||
} | |||||
if (unexpected_messages) { | |||||
printf("\nUnexpected messages received: %d\n", | |||||
unexpected_messages); | |||||
} | |||||
} | |||||
deactivate_client: | |||||
jack_deactivate(client); | |||||
destroy_mutex: | |||||
pthread_mutex_destroy(&start_mutex); | |||||
destroy_semaphore: | |||||
sem_destroy(&semaphore); | |||||
unregister_out_port: | |||||
jack_port_unregister(client, out_port); | |||||
unregister_in_port: | |||||
jack_port_unregister(client, in_port); | |||||
close_client: | |||||
jack_client_close(client); | |||||
free_message_2: | |||||
free(message_2); | |||||
free_message_1: | |||||
free(message_1); | |||||
free_latency_time_values: | |||||
free(latency_time_values); | |||||
free_latency_values: | |||||
free(latency_values); | |||||
if (error_message != NULL) { | |||||
show_error: | |||||
output_error(error_source, error_message); | |||||
exit(EXIT_FAILURE); | |||||
} | |||||
return EXIT_SUCCESS; | |||||
} |
@@ -27,6 +27,7 @@ example_programs = { | |||||
'jack_server_control' : 'server_control.cpp', | 'jack_server_control' : 'server_control.cpp', | ||||
'jack_latent_client' : 'latent_client.c', | 'jack_latent_client' : 'latent_client.c', | ||||
'jack_midi_dump' : 'midi_dump.c', | 'jack_midi_dump' : 'midi_dump.c', | ||||
'jack_midi_latency_test' : 'midi_latency_test.c' | |||||
} | } | ||||
example_libs = { | example_libs = { | ||||
@@ -36,8 +36,8 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||||
#include <string.h> | #include <string.h> | ||||
#include "JackFFADODriver.h" | #include "JackFFADODriver.h" | ||||
#include "JackFFADOMidiInput.h" | |||||
#include "JackFFADOMidiOutput.h" | |||||
#include "JackFFADOMidiInputPort.h" | |||||
#include "JackFFADOMidiOutputPort.h" | |||||
#include "JackEngineControl.h" | #include "JackEngineControl.h" | ||||
#include "JackClientControl.h" | #include "JackClientControl.h" | ||||
#include "JackPort.h" | #include "JackPort.h" | ||||
@@ -94,14 +94,9 @@ JackFFADODriver::ffado_driver_read (ffado_driver_t * driver, jack_nframes_t nfra | |||||
/* process the midi data */ | /* process the midi data */ | ||||
for (chn = 0; chn < driver->capture_nchannels; chn++) { | for (chn = 0; chn < driver->capture_nchannels; chn++) { | ||||
if (driver->capture_channels[chn].stream_type == ffado_stream_type_midi) { | if (driver->capture_channels[chn].stream_type == ffado_stream_type_midi) { | ||||
JackFFADOMidiInput *midi_input = (JackFFADOMidiInput *) driver->capture_channels[chn].midi_input; | |||||
JackFFADOMidiInputPort *midi_input = (JackFFADOMidiInputPort *) driver->capture_channels[chn].midi_input; | |||||
JackMidiBuffer *buffer = (JackMidiBuffer *) fGraphManager->GetBuffer(fCapturePortList[chn], nframes); | 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); | |||||
midi_input->Process(buffer, driver->capture_channels[chn].midi_buffer, nframes); | |||||
} | } | ||||
} | } | ||||
@@ -138,16 +133,9 @@ JackFFADODriver::ffado_driver_write (ffado_driver_t * driver, jack_nframes_t nfr | |||||
memset(midi_buffer, 0, nframes * sizeof(uint32_t)); | memset(midi_buffer, 0, nframes * sizeof(uint32_t)); | ||||
buf = (jack_default_audio_sample_t *) fGraphManager->GetBuffer(fPlaybackPortList[chn], nframes); | buf = (jack_default_audio_sample_t *) fGraphManager->GetBuffer(fPlaybackPortList[chn], nframes); | ||||
ffado_streaming_set_playback_stream_buffer(driver->dev, chn, (char *)(midi_buffer)); | 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); | |||||
JackFFADOMidiOutput *midi_output = (JackFFADOMidiOutput *) driver->playback_channels[chn].midi_output; | |||||
midi_output->SetPortBuffer((JackMidiBuffer *) buf); | |||||
midi_output->SetOutputBuffer(midi_buffer); | |||||
midi_output->Process(nframes); | |||||
ffado_streaming_playback_stream_onoff(driver->dev, chn, buf ? 1 : 0); | |||||
JackFFADOMidiOutputPort *midi_output = (JackFFADOMidiOutputPort *) driver->playback_channels[chn].midi_output; | |||||
midi_output->Process((JackMidiBuffer *) buf, midi_buffer, nframes); | |||||
} else { // always have a valid buffer | } else { // always have a valid buffer | ||||
ffado_streaming_set_playback_stream_buffer(driver->dev, chn, (char *)(driver->nullbuffer)); | ffado_streaming_set_playback_stream_buffer(driver->dev, chn, (char *)(driver->nullbuffer)); | ||||
@@ -155,9 +143,7 @@ JackFFADODriver::ffado_driver_write (ffado_driver_t * driver, jack_nframes_t nfr | |||||
} | } | ||||
} | } | ||||
} | } | ||||
ffado_streaming_transfer_playback_buffers(driver->dev); | ffado_streaming_transfer_playback_buffers(driver->dev); | ||||
printExit(); | printExit(); | ||||
return 0; | return 0; | ||||
} | } | ||||
@@ -476,7 +462,7 @@ int JackFFADODriver::Attach() | |||||
printError(" cannot enable port %s", buf); | printError(" cannot enable port %s", buf); | ||||
} | } | ||||
driver->capture_channels[chn].midi_input = new JackFFADOMidiInput(); | |||||
driver->capture_channels[chn].midi_input = new JackFFADOMidiInputPort(); | |||||
// setup the midi buffer | // setup the midi buffer | ||||
driver->capture_channels[chn].midi_buffer = (uint32_t *)calloc(driver->period_size, sizeof(uint32_t)); | driver->capture_channels[chn].midi_buffer = (uint32_t *)calloc(driver->period_size, sizeof(uint32_t)); | ||||
@@ -557,12 +543,12 @@ int JackFFADODriver::Attach() | |||||
// This constructor optionally accepts arguments for the | // This constructor optionally accepts arguments for the | ||||
// non-realtime buffer size and the realtime buffer size. Ideally, | // non-realtime buffer size and the realtime buffer size. Ideally, | ||||
// these would become command-line options for the FFADO driver. | // these would become command-line options for the FFADO driver. | ||||
driver->playback_channels[chn].midi_output = new JackFFADOMidiOutput(); | |||||
driver->playback_channels[chn].midi_output = new JackFFADOMidiOutputPort(); | |||||
driver->playback_channels[chn].midi_buffer = (uint32_t *)calloc(driver->period_size, sizeof(uint32_t)); | driver->playback_channels[chn].midi_buffer = (uint32_t *)calloc(driver->period_size, sizeof(uint32_t)); | ||||
port = fGraphManager->GetPort(port_index); | port = fGraphManager->GetPort(port_index); | ||||
range.min = range.max = (driver->period_size * (driver->device_options.nb_buffers - 1)) + driver->playback_frame_latency; | |||||
range.min = range.max = (driver->period_size * (driver->device_options.nb_buffers - 1)) + ((fEngineControl->fSyncMode) ? 0 : fEngineControl->fBufferSize) + driver->playback_frame_latency; | |||||
port->SetLatencyRange(JackPlaybackLatency, &range); | port->SetLatencyRange(JackPlaybackLatency, &range); | ||||
fPlaybackPortList[chn] = port_index; | fPlaybackPortList[chn] = port_index; | ||||
jack_log("JackFFADODriver::Attach fPlaybackPortList[i] %ld ", port_index); | jack_log("JackFFADODriver::Attach fPlaybackPortList[i] %ld ", port_index); | ||||
@@ -600,7 +586,7 @@ int JackFFADODriver::Detach() | |||||
if (driver->capture_channels[chn].midi_buffer) | if (driver->capture_channels[chn].midi_buffer) | ||||
free(driver->capture_channels[chn].midi_buffer); | free(driver->capture_channels[chn].midi_buffer); | ||||
if (driver->capture_channels[chn].midi_input) | if (driver->capture_channels[chn].midi_input) | ||||
delete ((JackFFADOMidiInput *) (driver->capture_channels[chn].midi_input)); | |||||
delete ((JackFFADOMidiInputPort *) (driver->capture_channels[chn].midi_input)); | |||||
} | } | ||||
free(driver->capture_channels); | free(driver->capture_channels); | ||||
@@ -608,7 +594,7 @@ int JackFFADODriver::Detach() | |||||
if (driver->playback_channels[chn].midi_buffer) | if (driver->playback_channels[chn].midi_buffer) | ||||
free(driver->playback_channels[chn].midi_buffer); | free(driver->playback_channels[chn].midi_buffer); | ||||
if (driver->playback_channels[chn].midi_output) | if (driver->playback_channels[chn].midi_output) | ||||
delete ((JackFFADOMidiOutput *) (driver->playback_channels[chn].midi_output)); | |||||
delete ((JackFFADOMidiOutputPort *) (driver->playback_channels[chn].midi_output)); | |||||
} | } | ||||
free(driver->playback_channels); | free(driver->playback_channels); | ||||
@@ -1,59 +0,0 @@ | |||||
/* | |||||
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,94 @@ | |||||
/* | |||||
Copyright (C) 2010 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 <memory> | |||||
#include "JackFFADOMidiInputPort.h" | |||||
#include "JackMidiUtil.h" | |||||
using Jack::JackFFADOMidiInputPort; | |||||
JackFFADOMidiInputPort::JackFFADOMidiInputPort(size_t max_bytes) | |||||
{ | |||||
event = 0; | |||||
receive_queue = new JackFFADOMidiReceiveQueue(); | |||||
std::auto_ptr<JackFFADOMidiReceiveQueue> receive_queue_ptr(receive_queue); | |||||
write_queue = new JackMidiBufferWriteQueue(); | |||||
std::auto_ptr<JackMidiBufferWriteQueue> write_queue_ptr(write_queue); | |||||
raw_queue = new JackMidiRawInputWriteQueue(write_queue, max_bytes, | |||||
max_bytes); | |||||
write_queue_ptr.release(); | |||||
receive_queue_ptr.release(); | |||||
} | |||||
JackFFADOMidiInputPort::~JackFFADOMidiInputPort() | |||||
{ | |||||
delete raw_queue; | |||||
delete receive_queue; | |||||
delete write_queue; | |||||
} | |||||
void | |||||
JackFFADOMidiInputPort::Process(JackMidiBuffer *port_buffer, | |||||
uint32_t *input_buffer, jack_nframes_t frames) | |||||
{ | |||||
receive_queue->ResetInputBuffer(input_buffer, frames); | |||||
write_queue->ResetMidiBuffer(port_buffer, frames); | |||||
jack_nframes_t boundary_frame = GetLastFrame() + frames; | |||||
if (! event) { | |||||
event = receive_queue->DequeueEvent(); | |||||
} | |||||
for (; event; event = receive_queue->DequeueEvent()) { | |||||
switch (raw_queue->EnqueueEvent(event)) { | |||||
case JackMidiWriteQueue::BUFFER_FULL: | |||||
// Processing events early might free up some space in the raw | |||||
// input queue. | |||||
raw_queue->Process(boundary_frame); | |||||
switch (raw_queue->EnqueueEvent(event)) { | |||||
case JackMidiWriteQueue::BUFFER_TOO_SMALL: | |||||
// This shouldn't really happen. It indicates a bug if it | |||||
// does. | |||||
jack_error("JackFFADOMidiInputPort::Process - **BUG** " | |||||
"JackMidiRawInputWriteQueue::EnqueueEvent returned " | |||||
"`BUFFER_FULL`, and then returned " | |||||
"`BUFFER_TOO_SMALL` after a `Process()` call."); | |||||
// Fallthrough on purpose | |||||
case JackMidiWriteQueue::OK: | |||||
continue; | |||||
default: | |||||
return; | |||||
} | |||||
case JackMidiWriteQueue::BUFFER_TOO_SMALL: | |||||
jack_error("JackFFADOMidiInputPort::Process - The write queue " | |||||
"couldn't enqueue a %d-byte event. Dropping event.", | |||||
event->size); | |||||
// Fallthrough on purpose | |||||
case JackMidiWriteQueue::OK: | |||||
continue; | |||||
default: | |||||
// This is here to stop compliers from warning us about not | |||||
// handling enumeration values. | |||||
; | |||||
} | |||||
break; | |||||
} | |||||
raw_queue->Process(boundary_frame); | |||||
} |
@@ -0,0 +1,51 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackFFADOMidiInputPort__ | |||||
#define __JackFFADOMidiInputPort__ | |||||
#include "JackFFADOMidiReceiveQueue.h" | |||||
#include "JackMidiBufferWriteQueue.h" | |||||
#include "JackMidiRawInputWriteQueue.h" | |||||
namespace Jack { | |||||
class JackFFADOMidiInputPort { | |||||
private: | |||||
jack_midi_event_t *event; | |||||
JackMidiRawInputWriteQueue *raw_queue; | |||||
JackFFADOMidiReceiveQueue *receive_queue; | |||||
JackMidiBufferWriteQueue *write_queue; | |||||
public: | |||||
JackFFADOMidiInputPort(size_t max_bytes=4096); | |||||
~JackFFADOMidiInputPort(); | |||||
void | |||||
Process(JackMidiBuffer *port_buffer, uint32_t *input_buffer, | |||||
jack_nframes_t frames); | |||||
}; | |||||
} | |||||
#endif |
@@ -1,60 +0,0 @@ | |||||
/* | |||||
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,98 @@ | |||||
/* | |||||
Copyright (C) 2010 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 <memory> | |||||
#include "JackFFADOMidiOutputPort.h" | |||||
#include "JackMidiUtil.h" | |||||
using Jack::JackFFADOMidiOutputPort; | |||||
JackFFADOMidiOutputPort::JackFFADOMidiOutputPort(size_t non_rt_size, | |||||
size_t max_non_rt_messages, | |||||
size_t max_rt_messages) | |||||
{ | |||||
event = 0; | |||||
read_queue = new JackMidiBufferReadQueue(); | |||||
std::auto_ptr<JackMidiBufferReadQueue> read_queue_ptr(read_queue); | |||||
send_queue = new JackFFADOMidiSendQueue(); | |||||
std::auto_ptr<JackFFADOMidiSendQueue> send_queue_ptr(send_queue); | |||||
raw_queue = new JackMidiRawOutputWriteQueue(send_queue, max_rt_messages, | |||||
max_non_rt_messages, | |||||
non_rt_size); | |||||
send_queue_ptr.release(); | |||||
read_queue_ptr.release(); | |||||
} | |||||
JackFFADOMidiOutputPort::~JackFFADOMidiOutputPort() | |||||
{ | |||||
delete raw_queue; | |||||
delete read_queue; | |||||
delete send_queue; | |||||
} | |||||
void | |||||
JackFFADOMidiOutputPort::Process(JackMidiBuffer *port_buffer, | |||||
uint32_t *output_buffer, | |||||
jack_nframes_t frames) | |||||
{ | |||||
read_queue->ResetMidiBuffer(port_buffer); | |||||
send_queue->ResetOutputBuffer(output_buffer, frames); | |||||
jack_nframes_t boundary_frame = GetLastFrame() + frames; | |||||
if (! event) { | |||||
event = read_queue->DequeueEvent(); | |||||
} | |||||
for (; event; event = read_queue->DequeueEvent()) { | |||||
switch (raw_queue->EnqueueEvent(event)) { | |||||
case JackMidiWriteQueue::BUFFER_FULL: | |||||
// Processing events early might free up some space in the raw | |||||
// output queue. | |||||
raw_queue->Process(boundary_frame); | |||||
switch (raw_queue->EnqueueEvent(event)) { | |||||
case JackMidiWriteQueue::BUFFER_TOO_SMALL: | |||||
// This shouldn't really happen. It indicates a bug if it | |||||
// does. | |||||
jack_error("JackFFADOMidiOutputPort::Process - **BUG** " | |||||
"JackMidiRawOutputWriteQueue::EnqueueEvent " | |||||
"returned `BUFFER_FULL`, and then returned " | |||||
"`BUFFER_TOO_SMALL` after a `Process()` call."); | |||||
// Fallthrough on purpose | |||||
case JackMidiWriteQueue::OK: | |||||
continue; | |||||
default: | |||||
return; | |||||
} | |||||
case JackMidiWriteQueue::BUFFER_TOO_SMALL: | |||||
jack_error("JackFFADOMidiOutputPort::Process - The write queue " | |||||
"couldn't enqueue a %d-byte event. Dropping event.", | |||||
event->size); | |||||
// Fallthrough on purpose | |||||
case JackMidiWriteQueue::OK: | |||||
continue; | |||||
default: | |||||
// This is here to stop compliers from warning us about not | |||||
// handling enumeration values. | |||||
; | |||||
} | |||||
break; | |||||
} | |||||
raw_queue->Process(boundary_frame); | |||||
} |
@@ -0,0 +1,53 @@ | |||||
/* | |||||
Copyright (C) 2010 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 __JackFFADOMidiOutputPort__ | |||||
#define __JackFFADOMidiOutputPort__ | |||||
#include "JackFFADOMidiSendQueue.h" | |||||
#include "JackMidiBufferReadQueue.h" | |||||
#include "JackMidiRawOutputWriteQueue.h" | |||||
namespace Jack { | |||||
class JackFFADOMidiOutputPort { | |||||
private: | |||||
jack_midi_event_t *event; | |||||
JackMidiRawOutputWriteQueue *raw_queue; | |||||
JackMidiBufferReadQueue *read_queue; | |||||
JackFFADOMidiSendQueue *send_queue; | |||||
public: | |||||
JackFFADOMidiOutputPort(size_t non_rt_size=4096, | |||||
size_t max_non_rt_messages=1024, | |||||
size_t max_rt_messages=128); | |||||
~JackFFADOMidiOutputPort(); | |||||
void | |||||
Process(JackMidiBuffer *port_buffer, uint32_t *output_buffer, | |||||
jack_nframes_t frames); | |||||
}; | |||||
} | |||||
#endif |
@@ -0,0 +1,55 @@ | |||||
/* | |||||
Copyright (C) 2010 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 "JackFFADOMidiReceiveQueue.h" | |||||
#include "JackMidiUtil.h" | |||||
using Jack::JackFFADOMidiReceiveQueue; | |||||
JackFFADOMidiReceiveQueue::JackFFADOMidiReceiveQueue() | |||||
{ | |||||
// Empty | |||||
} | |||||
jack_midi_event_t * | |||||
JackFFADOMidiReceiveQueue::DequeueEvent() | |||||
{ | |||||
for (; index < length; index += 8) { | |||||
uint32_t data = input_buffer[index]; | |||||
if (data & 0xff000000) { | |||||
byte = (jack_midi_data_t) (data & 0xff); | |||||
event.buffer = &byte; | |||||
event.size = 1; | |||||
event.time = last_frame + index; | |||||
index += 8; | |||||
return &event; | |||||
} | |||||
} | |||||
return 0; | |||||
} | |||||
void | |||||
JackFFADOMidiReceiveQueue::ResetInputBuffer(uint32_t *input_buffer, | |||||
jack_nframes_t length) | |||||
{ | |||||
this->input_buffer = input_buffer; | |||||
index = 0; | |||||
last_frame = GetLastFrame(); | |||||
this->length = length; | |||||
} |
@@ -1,5 +1,5 @@ | |||||
/* | /* | ||||
Copyright (C) 2009 Devin Anderson | |||||
Copyright (C) 2010 Devin Anderson | |||||
This program is free software; you can redistribute it and/or modify | 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 | it under the terms of the GNU Lesser General Public License as published by | ||||
@@ -17,35 +17,33 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |||||
*/ | */ | ||||
#ifndef __JackFFADOMidiInput__ | |||||
#define __JackFFADOMidiInput__ | |||||
#ifndef __JackFFADOMidiReceiveQueue__ | |||||
#define __JackFFADOMidiReceiveQueue__ | |||||
#include "JackPhysicalMidiInput.h" | |||||
#include "JackMidiReceiveQueue.h" | |||||
namespace Jack { | namespace Jack { | ||||
class JackFFADOMidiInput: public JackPhysicalMidiInput { | |||||
class JackFFADOMidiReceiveQueue: public JackMidiReceiveQueue { | |||||
private: | private: | ||||
jack_midi_data_t byte; | |||||
jack_midi_event_t event; | |||||
jack_nframes_t index; | |||||
uint32_t *input_buffer; | uint32_t *input_buffer; | ||||
bool new_period; | |||||
protected: | |||||
jack_nframes_t | |||||
Receive(jack_midi_data_t *, jack_nframes_t, jack_nframes_t); | |||||
jack_nframes_t last_frame; | |||||
jack_nframes_t length; | |||||
public: | public: | ||||
JackFFADOMidiInput(size_t buffer_size=1024); | |||||
~JackFFADOMidiInput(); | |||||
JackFFADOMidiReceiveQueue(); | |||||
jack_midi_event_t * | |||||
DequeueEvent(); | |||||
inline void | |||||
SetInputBuffer(uint32_t *input_buffer) | |||||
{ | |||||
this->input_buffer = input_buffer; | |||||
} | |||||
void | |||||
ResetInputBuffer(uint32_t *input_buffer, jack_nframes_t length); | |||||
}; | }; | ||||
@@ -0,0 +1,64 @@ | |||||
/* | |||||
Copyright (C) 2010 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 "JackFFADOMidiSendQueue.h" | |||||
#include "JackMidiUtil.h" | |||||
using Jack::JackFFADOMidiSendQueue; | |||||
JackFFADOMidiSendQueue::JackFFADOMidiSendQueue() | |||||
{ | |||||
// Empty | |||||
} | |||||
Jack::JackMidiWriteQueue::EnqueueResult | |||||
JackFFADOMidiSendQueue::EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer) | |||||
{ | |||||
assert(size == 1); | |||||
jack_nframes_t relative_time = (time < last_frame) ? 0 : time - last_frame; | |||||
if (index < relative_time) { | |||||
index = (relative_time % 8) ? | |||||
(relative_time & (~ ((jack_nframes_t) 7))) + 8 : relative_time; | |||||
} | |||||
if (index >= length) { | |||||
return BUFFER_FULL; | |||||
} | |||||
output_buffer[index] = 0x01000000 | ((uint32_t) *buffer); | |||||
index += 8; | |||||
return OK; | |||||
} | |||||
jack_nframes_t | |||||
JackFFADOMidiSendQueue::GetNextScheduleFrame() | |||||
{ | |||||
return last_frame + index; | |||||
} | |||||
void | |||||
JackFFADOMidiSendQueue::ResetOutputBuffer(uint32_t *output_buffer, | |||||
jack_nframes_t length) | |||||
{ | |||||
index = 0; | |||||
last_frame = GetLastFrame(); | |||||
this->length = length; | |||||
this->output_buffer = output_buffer; | |||||
} |
@@ -1,5 +1,5 @@ | |||||
/* | /* | ||||
Copyright (C) 2009 Devin Anderson | |||||
Copyright (C) 2010 Devin Anderson | |||||
This program is free software; you can redistribute it and/or modify | 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 | it under the terms of the GNU Lesser General Public License as published by | ||||
@@ -17,38 +17,35 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |||||
*/ | */ | ||||
#ifndef __JackFFADOMidiOutput__ | |||||
#define __JackFFADOMidiOutput__ | |||||
#ifndef __JackFFADOMidiSendQueue__ | |||||
#define __JackFFADOMidiSendQueue__ | |||||
#include "JackPhysicalMidiOutput.h" | |||||
#include "JackMidiSendQueue.h" | |||||
namespace Jack { | namespace Jack { | ||||
class JackFFADOMidiOutput: public JackPhysicalMidiOutput { | |||||
class JackFFADOMidiSendQueue: public JackMidiSendQueue { | |||||
private: | private: | ||||
jack_nframes_t index; | |||||
jack_nframes_t last_frame; | |||||
jack_nframes_t length; | |||||
uint32_t *output_buffer; | uint32_t *output_buffer; | ||||
protected: | |||||
jack_nframes_t | |||||
Advance(jack_nframes_t); | |||||
public: | |||||
jack_nframes_t | |||||
Send(jack_nframes_t, jack_midi_data_t); | |||||
JackFFADOMidiSendQueue(); | |||||
public: | |||||
EnqueueResult | |||||
EnqueueEvent(jack_nframes_t time, size_t size, | |||||
jack_midi_data_t *buffer); | |||||
JackFFADOMidiOutput(size_t non_rt_buffer_size=1024, | |||||
size_t rt_buffer_size=64); | |||||
~JackFFADOMidiOutput(); | |||||
jack_nframes_t | |||||
GetNextScheduleFrame(); | |||||
inline void | |||||
SetOutputBuffer(uint32_t *output_buffer) | |||||
{ | |||||
this->output_buffer = output_buffer; | |||||
} | |||||
void | |||||
ResetOutputBuffer(uint32_t *output_buffer, jack_nframes_t length); | |||||
}; | }; | ||||
@@ -58,10 +58,10 @@ def build(bld): | |||||
] | ] | ||||
ffado_driver_src = ['firewire/JackFFADODriver.cpp', | ffado_driver_src = ['firewire/JackFFADODriver.cpp', | ||||
'firewire/JackFFADOMidiInput.cpp', | |||||
'firewire/JackFFADOMidiOutput.cpp', | |||||
'../common/JackPhysicalMidiInput.cpp', | |||||
'../common/JackPhysicalMidiOutput.cpp' | |||||
'firewire/JackFFADOMidiInputPort.cpp', | |||||
'firewire/JackFFADOMidiOutputPort.cpp', | |||||
'firewire/JackFFADOMidiReceiveQueue.cpp', | |||||
'firewire/JackFFADOMidiSendQueue.cpp' | |||||
] | ] | ||||
if bld.env['BUILD_DRIVER_ALSA'] == True: | if bld.env['BUILD_DRIVER_ALSA'] == True: | ||||