/* Copyright (C) 2011 Devin Anderson This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include #include #include #include "JackCoreMidiOutputPort.h" #include "JackMidiUtil.h" #include "JackTime.h" #include "JackError.h" using Jack::JackCoreMidiOutputPort; JackCoreMidiOutputPort::JackCoreMidiOutputPort(double time_ratio, size_t max_bytes, size_t max_messages): JackCoreMidiPort(time_ratio) { read_queue = new JackMidiBufferReadQueue(); std::unique_ptr read_queue_ptr(read_queue); thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages); std::unique_ptr thread_queue_ptr(thread_queue); thread = new JackThread(this); std::unique_ptr thread_ptr(thread); snprintf(semaphore_name, sizeof(semaphore_name), "coremidi_%p", this); thread_queue_semaphore = sem_open(semaphore_name, O_CREAT, 0777, 0); if (thread_queue_semaphore == (sem_t *) SEM_FAILED) { throw std::runtime_error(strerror(errno)); } advance_schedule_time = 0; thread_ptr.release(); thread_queue_ptr.release(); read_queue_ptr.release(); } JackCoreMidiOutputPort::~JackCoreMidiOutputPort() { delete thread; sem_close(thread_queue_semaphore); sem_unlink(semaphore_name); delete read_queue; delete thread_queue; } bool JackCoreMidiOutputPort::Execute() { jack_midi_event_t *event = 0; MIDIPacketList *packet_list = (MIDIPacketList *) packet_buffer; for (;;) { MIDIPacket *packet = MIDIPacketListInit(packet_list); assert(packet); if (! event) { event = GetCoreMidiEvent(true); } jack_midi_data_t *data = event->buffer; jack_nframes_t send_frame = event->time; jack_time_t send_time = GetTimeFromFrames(send_frame) - advance_schedule_time; size_t size = event->size; MIDITimeStamp timestamp = GetTimeStampFromFrames(send_frame); packet = MIDIPacketListAdd(packet_list, PACKET_BUFFER_SIZE, packet, timestamp, size, data); if (packet) { do { if (GetMicroSeconds() >= send_time) { event = 0; break; } event = GetCoreMidiEvent(false); if (! event) { break; } packet = MIDIPacketListAdd(packet_list, sizeof(packet_buffer), packet, GetTimeStampFromFrames(event->time), event->size, event->buffer); } while (packet); SendPacketList(packet_list); } else { // We have a large system exclusive event. We'll have to send it // out in multiple packets. size_t bytes_sent = 0; do { packet = MIDIPacketListInit(packet_list); assert(packet); size_t num_bytes = 0; for (; bytes_sent < size; bytes_sent += num_bytes) { size_t num_bytes = size - bytes_sent; // We use 256 because the MIDIPacket struct defines the // size of the 'data' member to be 256 bytes. I believe // this prevents packets from being dynamically allocated // by 'MIDIPacketListAdd', but I might be wrong. if (num_bytes > 256) { num_bytes = 256; } packet = MIDIPacketListAdd(packet_list, sizeof(packet_buffer), packet, timestamp, num_bytes, data + bytes_sent); if (! packet) { break; } } if (! SendPacketList(packet_list)) { // An error occurred. The error message has already been // output. We lick our wounds and move along. break; } } while (bytes_sent < size); event = 0; } } return false; } jack_midi_event_t * JackCoreMidiOutputPort::GetCoreMidiEvent(bool block) { if (! block) { if (sem_trywait(thread_queue_semaphore)) { if (errno != EAGAIN) { jack_error("JackCoreMidiOutputPort::Execute - sem_trywait: %s", strerror(errno)); } return 0; } } else { while (sem_wait(thread_queue_semaphore)) { if (errno != EINTR) { jack_error("JackCoreMidiOutputPort::Execute - sem_wait: %s", strerror(errno)); return 0; } } } return thread_queue->DequeueEvent(); } MIDITimeStamp JackCoreMidiOutputPort::GetTimeStampFromFrames(jack_nframes_t frames) { return GetTimeFromFrames(frames) / time_ratio; } bool JackCoreMidiOutputPort::Init() { set_threaded_log_function(); // OSX only, values read in RT CoreMIDI thread UInt64 period = 0; UInt64 computation = 250 * 1000; UInt64 constraint = 500 * 1000; thread->SetParams(period, computation, constraint); if (thread->AcquireSelfRealTime()) { jack_error("JackCoreMidiOutputPort::Init - could not acquire realtime " "scheduling. Continuing anyway."); } return true; } void JackCoreMidiOutputPort::Initialize(const char *alias_name, const char *client_name, const char *driver_name, int index, MIDIEndpointRef endpoint, SInt32 advance_schedule_time) { JackCoreMidiPort::Initialize(alias_name, client_name, driver_name, index, endpoint, true); assert(advance_schedule_time >= 0); this->advance_schedule_time = advance_schedule_time; } void JackCoreMidiOutputPort::ProcessJack(JackMidiBuffer *port_buffer, jack_nframes_t frames) { read_queue->ResetMidiBuffer(port_buffer); for (jack_midi_event_t *event = read_queue->DequeueEvent(); event; event = read_queue->DequeueEvent()) { switch (thread_queue->EnqueueEvent(event, frames)) { case JackMidiWriteQueue::BUFFER_FULL: jack_error("JackCoreMidiOutputPort::ProcessJack - The thread " "queue buffer is full. Dropping event."); break; case JackMidiWriteQueue::BUFFER_TOO_SMALL: jack_error("JackCoreMidiOutputPort::ProcessJack - The thread " "queue couldn't enqueue a %d-byte event. Dropping " "event.", event->size); break; default: if (sem_post(thread_queue_semaphore)) { jack_error("JackCoreMidiOutputPort::ProcessJack - unexpected " "error while posting to thread queue semaphore: %s", strerror(errno)); } } } } bool JackCoreMidiOutputPort::Start() { bool result = thread->GetStatus() != JackThread::kIdle; if (! result) { result = ! thread->StartSync(); if (! result) { jack_error("JackCoreMidiOutputPort::Start - failed to start MIDI " "processing thread."); } } return result; } bool JackCoreMidiOutputPort::Stop() { bool result = thread->GetStatus() == JackThread::kIdle; if (! result) { result = ! thread->Kill(); if (! result) { jack_error("JackCoreMidiOutputPort::Stop - failed to stop MIDI " "processing thread."); } } return result; }