/* Copyright (C) 2009 Devin Anderson This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include #include "JackError.h" #include "JackPhysicalMidiOutput.h" namespace Jack { JackPhysicalMidiOutput::JackPhysicalMidiOutput(size_t non_rt_buffer_size, size_t rt_buffer_size) { size_t datum_size = sizeof(jack_midi_data_t); assert(non_rt_buffer_size > 0); assert(rt_buffer_size > 0); output_ring = jack_ringbuffer_create((non_rt_buffer_size + 1) * datum_size); if (! output_ring) { throw std::bad_alloc(); } rt_output_ring = jack_ringbuffer_create((rt_buffer_size + 1) * datum_size); if (! rt_output_ring) { jack_ringbuffer_free(output_ring); throw std::bad_alloc(); } jack_ringbuffer_mlock(output_ring); jack_ringbuffer_mlock(rt_output_ring); running_status = 0; } JackPhysicalMidiOutput::~JackPhysicalMidiOutput() { jack_ringbuffer_free(output_ring); jack_ringbuffer_free(rt_output_ring); } jack_nframes_t JackPhysicalMidiOutput::Advance(jack_nframes_t frame) { return frame; } inline jack_midi_data_t JackPhysicalMidiOutput::ApplyRunningStatus(jack_midi_data_t **buffer, size_t *size) { // Stolen and modified from alsa/midi_pack.h jack_midi_data_t status = (*buffer)[0]; if ((status >= 0x80) && (status < 0xf0)) { if (status == running_status) { (*buffer)++; (*size)--; } else { running_status = status; } } else if (status < 0xf8) { running_status = 0; } return status; } void JackPhysicalMidiOutput::HandleEventLoss(JackMidiEvent *event) { jack_error("%d byte MIDI event lost", event->size); } void JackPhysicalMidiOutput::Process(jack_nframes_t frames) { assert(port_buffer); jack_nframes_t current_frame = Advance(0); jack_nframes_t current_midi_event = 0; jack_midi_data_t datum; size_t datum_size = sizeof(jack_midi_data_t); JackMidiEvent *midi_event; jack_midi_data_t *midi_event_buffer; size_t midi_event_size; jack_nframes_t midi_events = port_buffer->event_count; // First, send any realtime MIDI data that's left from last cycle. if ((current_frame < frames) && jack_ringbuffer_read_space(rt_output_ring)) { jack_log("JackPhysicalMidiOutput::Process (%d) - Sending buffered " "realtime data from last period.", current_frame); current_frame = SendBufferedData(rt_output_ring, current_frame, frames); jack_log("JackPhysicalMidiOutput::Process (%d) - Sent", current_frame); } // Iterate through the events in this cycle. for (; (current_midi_event < midi_events) && (current_frame < frames); current_midi_event++) { // Once we're inside this loop, we know that the realtime buffer // is empty. As long as we don't find a realtime message, we can // concentrate on sending non-realtime data. midi_event = &(port_buffer->events[current_midi_event]); jack_nframes_t midi_event_time = midi_event->time; midi_event_buffer = midi_event->GetData(port_buffer); midi_event_size = midi_event->size; datum = ApplyRunningStatus(&midi_event_buffer, &midi_event_size); if (current_frame < midi_event_time) { // We have time before this event is scheduled to be sent. // Send data in the non-realtime buffer. if (jack_ringbuffer_read_space(output_ring)) { jack_log("JackPhysicalMidiOutput::Process (%d) - Sending " "buffered non-realtime data from last period.", current_frame); current_frame = SendBufferedData(output_ring, current_frame, midi_event_time); jack_log("JackPhysicalMidiOutput::Process (%d) - Sent", current_frame); } if (current_frame < midi_event_time) { // We _still_ have time before this event is scheduled to // be sent. Let's send as much of this event as we can // (save for one byte, which will need to be sent at or // after its scheduled time). First though, we need to // make sure that we can buffer this data if we need to. // Otherwise, we might start sending a message that we // can't finish. if (midi_event_size > 1) { if (jack_ringbuffer_write_space(output_ring) < ((midi_event_size - 1) * datum_size)) { HandleEventLoss(midi_event); continue; } // Send as much of the event as possible (save for one // byte). do { jack_log("JackPhysicalMidiOutput::Process (%d) - " "Sending unbuffered event byte early.", current_frame); current_frame = Send(current_frame, *midi_event_buffer); jack_log("JackPhysicalMidiOutput::Process (%d) - " "Sent.", current_frame); midi_event_buffer++; midi_event_size--; if (current_frame >= midi_event_time) { // The event we're processing must be a // non-realtime event. It has more than one // byte. goto buffer_non_realtime_data; } } while (midi_event_size > 1); } jack_log("JackPhysicalMidiOutput::Process (%d) - Advancing to " ">= %d", current_frame, midi_event_time); current_frame = Advance(midi_event_time); jack_log("JackPhysicalMidiOutput::Process (%d) - Advanced.", current_frame); } } // If the event is realtime, then we'll send the event now. // Otherwise, we attempt to put the rest of the event bytes in the // non-realtime buffer. if (datum >= 0xf8) { jack_log("JackPhysicalMidiOutput::Process (%d) - Sending " "unbuffered realtime event.", current_frame); current_frame = Send(current_frame, datum); jack_log("JackPhysicalMidiOutput::Process (%d) - Sent.", current_frame); } else if (jack_ringbuffer_write_space(output_ring) >= (midi_event_size * datum_size)) { buffer_non_realtime_data: jack_log("JackPhysicalMidiOutput::Process (%d) - Buffering %d " "byte(s) of non-realtime data.", current_frame, midi_event_size); jack_ringbuffer_write(output_ring, (const char *) midi_event_buffer, midi_event_size); } else { HandleEventLoss(midi_event); } } if (current_frame < frames) { // If we have time left to send data, then we know that all of the // data in the realtime buffer has been sent, and that all of the // non-realtime messages have either been sent, or buffered. We // use whatever time is left to send data in the non-realtime // buffer. if (jack_ringbuffer_read_space(output_ring)) { jack_log("JackPhysicalMidiOutput::Process (%d) - All events " "processed. Sending buffered non-realtime data.", current_frame); current_frame = SendBufferedData(output_ring, current_frame, frames); jack_log("JackPhysicalMidiOutput::Process (%d) - Sent.", current_frame); } } else { // Since we have no time left, we need to put all remaining midi // events in their appropriate buffers, and send them next period. for (; current_midi_event < 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; } }