Jack2  1.9.8
JackCoreMidiOutputPort.cpp
1 /*
2 Copyright (C) 2011 Devin Anderson
3 
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13 
14 You should have received a copy of the GNU General Public License
15 along with this program; if not, write to the Free Software
16 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 
18 */
19 
20 #include <cassert>
21 #include <cerrno>
22 #include <cstring>
23 #include <new>
24 #include <stdexcept>
25 
26 #include "JackCoreMidiOutputPort.h"
27 #include "JackMidiUtil.h"
28 #include "JackTime.h"
29 
31 
32 JackCoreMidiOutputPort::JackCoreMidiOutputPort(double time_ratio,
33  size_t max_bytes,
34  size_t max_messages):
35  JackCoreMidiPort(time_ratio)
36 {
37  read_queue = new JackMidiBufferReadQueue();
38  std::auto_ptr<JackMidiBufferReadQueue> read_queue_ptr(read_queue);
39  thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages);
40  std::auto_ptr<JackMidiAsyncQueue> thread_queue_ptr(thread_queue);
41  thread = new JackThread(this);
42  std::auto_ptr<JackThread> thread_ptr(thread);
43  snprintf(semaphore_name, sizeof(semaphore_name), "coremidi_%p", this);
44  thread_queue_semaphore = sem_open(semaphore_name, O_CREAT, 0777, 0);
45  if (thread_queue_semaphore == (sem_t *) SEM_FAILED) {
46  throw std::runtime_error(strerror(errno));
47  }
48  advance_schedule_time = 0;
49  thread_ptr.release();
50  thread_queue_ptr.release();
51  read_queue_ptr.release();
52 }
53 
54 JackCoreMidiOutputPort::~JackCoreMidiOutputPort()
55 {
56  delete thread;
57  sem_close(thread_queue_semaphore);
58  sem_unlink(semaphore_name);
59  delete read_queue;
60  delete thread_queue;
61 }
62 
63 bool
64 JackCoreMidiOutputPort::Execute()
65 {
66  jack_midi_event_t *event = 0;
67  MIDIPacketList *packet_list = (MIDIPacketList *) packet_buffer;
68  for (;;) {
69  MIDIPacket *packet = MIDIPacketListInit(packet_list);
70  assert(packet);
71  if (! event) {
72  event = GetCoreMidiEvent(true);
73  }
74  jack_midi_data_t *data = event->buffer;
75  jack_nframes_t send_frame = event->time;
76  jack_time_t send_time =
77  GetTimeFromFrames(send_frame) - advance_schedule_time;
78  size_t size = event->size;
79  MIDITimeStamp timestamp = GetTimeStampFromFrames(send_frame);
80  packet = MIDIPacketListAdd(packet_list, PACKET_BUFFER_SIZE, packet,
81  timestamp, size, data);
82  if (packet) {
83  while (GetMicroSeconds() < send_time) {
84  event = GetCoreMidiEvent(false);
85  if (! event) {
86  break;
87  }
88  packet = MIDIPacketListAdd(packet_list, sizeof(packet_buffer),
89  packet,
90  GetTimeStampFromFrames(event->time),
91  event->size, event->buffer);
92  if (! packet) {
93  break;
94  }
95  }
96  SendPacketList(packet_list);
97  } else {
98 
99  // We have a large system exclusive event. We'll have to send it
100  // out in multiple packets.
101  size_t bytes_sent = 0;
102  do {
103  packet = MIDIPacketListInit(packet_list);
104  assert(packet);
105  size_t num_bytes = 0;
106  for (; bytes_sent < size; bytes_sent += num_bytes) {
107  size_t num_bytes = size - bytes_sent;
108 
109  // We use 256 because the MIDIPacket struct defines the
110  // size of the 'data' member to be 256 bytes. I believe
111  // this prevents packets from being dynamically allocated
112  // by 'MIDIPacketListAdd', but I might be wrong.
113  if (num_bytes > 256) {
114  num_bytes = 256;
115  }
116  packet = MIDIPacketListAdd(packet_list,
117  sizeof(packet_buffer), packet,
118  timestamp, num_bytes,
119  data + bytes_sent);
120  if (! packet) {
121  break;
122  }
123  }
124  if (! SendPacketList(packet_list)) {
125  // An error occurred. The error message has already been
126  // output. We lick our wounds and move along.
127  break;
128  }
129  } while (bytes_sent < size);
130  event = 0;
131  }
132  }
133  return false;
134 }
135 
137 JackCoreMidiOutputPort::GetCoreMidiEvent(bool block)
138 {
139  if (! block) {
140  if (sem_trywait(thread_queue_semaphore)) {
141  if (errno != EAGAIN) {
142  jack_error("JackCoreMidiOutputPort::Execute - sem_trywait: %s",
143  strerror(errno));
144  }
145  return 0;
146  }
147  } else {
148  while (sem_wait(thread_queue_semaphore)) {
149  if (errno != EINTR) {
150  jack_error("JackCoreMidiOutputPort::Execute - sem_wait: %s",
151  strerror(errno));
152  return 0;
153  }
154  }
155  }
156  return thread_queue->DequeueEvent();
157 }
158 
159 MIDITimeStamp
160 JackCoreMidiOutputPort::GetTimeStampFromFrames(jack_nframes_t frames)
161 {
162  return GetTimeFromFrames(frames) / time_ratio;
163 }
164 
165 bool
167 {
168  set_threaded_log_function();
169 
170  // OSX only, values read in RT CoreMIDI thread
171  UInt64 period = 0;
172  UInt64 computation = 250 * 1000;
173  UInt64 constraint = 500 * 1000;
174  thread->SetParams(period, computation, constraint);
175 
176  if (thread->AcquireSelfRealTime()) {
177  jack_error("JackCoreMidiOutputPort::Init - could not acquire realtime "
178  "scheduling. Continuing anyway.");
179  }
180  return true;
181 }
182 
183 void
184 JackCoreMidiOutputPort::Initialize(const char *alias_name,
185  const char *client_name,
186  const char *driver_name, int index,
187  MIDIEndpointRef endpoint,
188  SInt32 advance_schedule_time)
189 {
190  JackCoreMidiPort::Initialize(alias_name, client_name, driver_name, index,
191  endpoint, true);
192  assert(advance_schedule_time >= 0);
193  this->advance_schedule_time = advance_schedule_time;
194 }
195 
196 void
197 JackCoreMidiOutputPort::ProcessJack(JackMidiBuffer *port_buffer,
198  jack_nframes_t frames)
199 {
200  read_queue->ResetMidiBuffer(port_buffer);
201  for (jack_midi_event_t *event = read_queue->DequeueEvent(); event;
202  event = read_queue->DequeueEvent()) {
203  switch (thread_queue->EnqueueEvent(event, frames)) {
204  case JackMidiWriteQueue::BUFFER_FULL:
205  jack_error("JackCoreMidiOutputPort::ProcessJack - The thread "
206  "queue buffer is full. Dropping event.");
207  break;
208  case JackMidiWriteQueue::BUFFER_TOO_SMALL:
209  jack_error("JackCoreMidiOutputPort::ProcessJack - The thread "
210  "queue couldn't enqueue a %d-byte event. Dropping "
211  "event.", event->size);
212  break;
213  default:
214  if (sem_post(thread_queue_semaphore)) {
215  jack_error("JackCoreMidiOutputPort::ProcessJack - unexpected "
216  "error while posting to thread queue semaphore: %s",
217  strerror(errno));
218  }
219  }
220  }
221 }
222 
223 bool
224 JackCoreMidiOutputPort::Start()
225 {
226  bool result = thread->GetStatus() != JackThread::kIdle;
227  if (! result) {
228  result = ! thread->StartSync();
229  if (! result) {
230  jack_error("JackCoreMidiOutputPort::Start - failed to start MIDI "
231  "processing thread.");
232  }
233  }
234  return result;
235 }
236 
237 bool
238 JackCoreMidiOutputPort::Stop()
239 {
240  bool result = thread->GetStatus() == JackThread::kIdle;
241  if (! result) {
242  result = ! thread->Kill();
243  if (! result) {
244  jack_error("JackCoreMidiOutputPort::Stop - failed to stop MIDI "
245  "processing thread.");
246  }
247  }
248  return result;
249 }