Jack2  1.9.10
JackCoreMidiInputPort.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 <memory>
22 
23 #include "JackCoreMidiInputPort.h"
24 #include "JackMidiUtil.h"
25 #include "JackError.h"
26 
28 
34 inline static int _expectedEventSize(const unsigned char& byte) {
35  if (byte < 0x80) return -1; // not a valid status byte
36  if (byte < 0xC0) return 3; // note on/off, note pressure, control change
37  if (byte < 0xE0) return 2; // program change, channel pressure
38  if (byte < 0xF0) return 3; // pitch wheel
39  if (byte == 0xF0) return -1; // sysex message (variable size)
40  if (byte == 0xF1) return 2; // time code per quarter frame
41  if (byte == 0xF2) return 3; // sys. common song position pointer
42  if (byte == 0xF3) return 2; // sys. common song select
43  if (byte == 0xF4) return -1; // sys. common undefined / reserved
44  if (byte == 0xF5) return -1; // sys. common undefined / reserved
45  return 1; // tune request, end of SysEx, system real-time events
46 }
47 
48 JackCoreMidiInputPort::JackCoreMidiInputPort(double time_ratio,
49  size_t max_bytes,
50  size_t max_messages):
51  JackCoreMidiPort(time_ratio)
52 {
53  thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages);
54  std::auto_ptr<JackMidiAsyncQueue> thread_queue_ptr(thread_queue);
55  write_queue = new JackMidiBufferWriteQueue();
56  std::auto_ptr<JackMidiBufferWriteQueue> write_queue_ptr(write_queue);
57  sysex_buffer = new jack_midi_data_t[max_bytes];
58  write_queue_ptr.release();
59  thread_queue_ptr.release();
60  jack_event = 0;
61  running_status_buf[0] = 0;
62 }
63 
64 JackCoreMidiInputPort::~JackCoreMidiInputPort()
65 {
66  delete thread_queue;
67  delete write_queue;
68  delete[] sysex_buffer;
69 }
70 
71 jack_nframes_t
72 JackCoreMidiInputPort::GetFramesFromTimeStamp(MIDITimeStamp timestamp)
73 {
74  return GetFramesFromTime((jack_time_t) (timestamp * time_ratio));
75 }
76 
77 void
78 JackCoreMidiInputPort::Initialize(const char *alias_name,
79  const char *client_name,
80  const char *driver_name, int index,
81  MIDIEndpointRef endpoint)
82 {
83  JackCoreMidiPort::Initialize(alias_name, client_name, driver_name, index, endpoint, false);
84 }
85 
86 void
87 JackCoreMidiInputPort::ProcessCoreMidi(const MIDIPacketList *packet_list)
88 {
89  set_threaded_log_function();
90 
91  // TODO: maybe parsing should be done by JackMidiRawInputWriteQueue instead
92 
93  unsigned int packet_count = packet_list->numPackets;
94  assert(packet_count);
95  MIDIPacket *packet = (MIDIPacket *) packet_list->packet;
96  for (unsigned int i = 0; i < packet_count; i++) {
97  jack_midi_data_t *data = packet->data;
98  size_t size = packet->length;
99  assert(size);
100  jack_midi_event_t event;
101  // In a MIDIPacket there can be more than one (non SysEx) MIDI event.
102  // However if the packet contains a SysEx event, it is guaranteed that
103  // there are no other events in the same MIDIPacket.
104  int k = 0; // index of the current MIDI event within current MIDIPacket
105  int eventSize = 0; // theoretical size of the current MIDI event
106  int chunkSize = 0; // actual size of the current MIDI event data consumed
107 
108  // XX: There might be dragons in my spaghetti. This code is begging
109  // for a rewrite.
110 
111  if (sysex_bytes_sent) {
112  if (data[0] & 0x80) {
113  jack_error("JackCoreMidiInputPort::ProcessCoreMidi - System "
114  "exclusive message aborted.");
115  sysex_bytes_sent = 0;
116  goto parse_event;
117  }
118  buffer_sysex_bytes:
119  if ((sysex_bytes_sent + size) <= sizeof(sysex_buffer)) {
120  memcpy(sysex_buffer + sysex_bytes_sent, packet,
121  size * sizeof(jack_midi_data_t));
122  }
123  sysex_bytes_sent += size;
124  if (data[size - 1] == 0xf7) {
125  if (sysex_bytes_sent > sizeof(sysex_buffer)) {
126  jack_error("JackCoreMidiInputPort::ProcessCoreMidi - "
127  "Could not buffer a %d-byte system exclusive "
128  "message. Discarding message.",
129  sysex_bytes_sent);
130  sysex_bytes_sent = 0;
131  goto get_next_packet;
132  }
133  event.buffer = sysex_buffer;
134  event.size = sysex_bytes_sent;
135  sysex_bytes_sent = 0;
136  k = size; // don't loop in a MIDIPacket if its a SysEx
137  goto send_event;
138  }
139  goto get_next_packet;
140  }
141 
142  parse_event:
143  if (data[k+0] == 0xf0) {
144  // Must actually never happen, since CoreMIDI guarantees a SysEx
145  // message to be alone in one MIDIPaket, but safety first. The SysEx
146  // buffer code is not written to handle this case, so skip packet.
147  if (k != 0) {
148  jack_error("JackCoreMidiInputPort::ProcessCoreMidi - Non "
149  "isolated SysEx message in one packet, discarding.");
150  goto get_next_packet;
151  }
152 
153  if (data[size - 1] != 0xf7) {
154  goto buffer_sysex_bytes;
155  }
156  }
157 
158  // not a regular status byte ?
159  if (!(data[k+0] & 0x80) && running_status_buf[0]) { // "running status" mode ...
160  eventSize = _expectedEventSize(running_status_buf[0]);
161  chunkSize = (eventSize < 0) ? size - k : eventSize - 1;
162  if (chunkSize <= 0) goto get_next_packet;
163  if (chunkSize + 1 <= sizeof(running_status_buf)) {
164  memcpy(&running_status_buf[1], &data[k], chunkSize);
165  event.buffer = running_status_buf;
166  event.size = chunkSize + 1;
167  k += chunkSize;
168  goto send_event;
169  }
170  }
171 
172  // valid status byte (or invalid "running status") ...
173 
174  eventSize = _expectedEventSize(data[k+0]);
175  if (eventSize < 0) eventSize = size - k;
176  if (eventSize <= 0) goto get_next_packet;
177  event.buffer = &data[k];
178  event.size = eventSize;
179  // store status byte for eventual "running status" in next event
180  if (data[k+0] & 0x80) {
181  if (data[k+0] < 0xf0) {
182  // "running status" is only allowed for channel messages
183  running_status_buf[0] = data[k+0];
184  } else if (data[k+0] < 0xf8) {
185  // "system common" messages (0xf0..0xf7) shall reset any running
186  // status, however "realtime" messages (0xf8..0xff) shall be
187  // ignored here
188  running_status_buf[0] = 0;
189  }
190  }
191  k += eventSize;
192 
193  send_event:
194  event.time = GetFramesFromTimeStamp(packet->timeStamp);
195  switch (thread_queue->EnqueueEvent(&event)) {
196  case JackMidiWriteQueue::BUFFER_FULL:
197  jack_error("JackCoreMidiInputPort::ProcessCoreMidi - The thread "
198  "queue buffer is full. Dropping event.");
199  break;
200  case JackMidiWriteQueue::BUFFER_TOO_SMALL:
201  jack_error("JackCoreMidiInputPort::ProcessCoreMidi - The thread "
202  "queue couldn't enqueue a %d-byte packet. Dropping "
203  "event.", event.size);
204  break;
205  default:
206  ;
207  }
208  if (k < size) goto parse_event;
209 
210  get_next_packet:
211  packet = MIDIPacketNext(packet);
212  assert(packet);
213  }
214 }
215 
216 void
217 JackCoreMidiInputPort::ProcessJack(JackMidiBuffer *port_buffer,
218  jack_nframes_t frames)
219 {
220  write_queue->ResetMidiBuffer(port_buffer, frames);
221  if (! jack_event) {
222  jack_event = thread_queue->DequeueEvent();
223  }
224 
225  for (; jack_event; jack_event = thread_queue->DequeueEvent()) {
226  // Add 'frames' to MIDI events to align with audio.
227  switch (write_queue->EnqueueEvent(jack_event, frames)) {
228  case JackMidiWriteQueue::BUFFER_TOO_SMALL:
229  jack_error("JackCoreMidiInputPort::ProcessJack - The write queue "
230  "couldn't enqueue a %d-byte event. Dropping event.",
231  jack_event->size);
232  // Fallthrough on purpose
233  case JackMidiWriteQueue::OK:
234  continue;
235  default:
236  ;
237  }
238  break;
239  }
240 }
241 
242 bool
243 JackCoreMidiInputPort::Start()
244 {
245  // Hack: Get rid of any messages that might have come in before starting
246  // the engine.
247  while (thread_queue->DequeueEvent());
248  sysex_bytes_sent = 0;
249  running_status_buf[0] = 0;
250  return true;
251 }
252 
253 bool
254 JackCoreMidiInputPort::Stop()
255 {
256  return true;
257 }