Jack2  1.9.13
JackWinMMEOutputPort.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 <memory>
21 #include <stdexcept>
22 
23 #include "JackMidiUtil.h"
24 #include "JackTime.h"
25 #include "JackWinMMEOutputPort.h"
26 #include "JackGlobals.h"
27 #include "JackEngineControl.h"
28 
30 
32 // Static callbacks
34 
35 void CALLBACK
36 JackWinMMEOutputPort::HandleMessageEvent(HMIDIOUT handle, UINT message,
37  DWORD_PTR port, DWORD_PTR param1,
38  DWORD_PTR param2)
39 {
40  ((JackWinMMEOutputPort *) port)->HandleMessage(message, param1, param2);
41 }
42 
44 // Class
46 
47 JackWinMMEOutputPort::JackWinMMEOutputPort(const char *alias_name,
48  const char *client_name,
49  const char *driver_name,
50  UINT index,
51  size_t max_bytes,
52  size_t max_messages)
53 {
54  read_queue = new JackMidiBufferReadQueue();
55  std::unique_ptr<JackMidiBufferReadQueue> read_queue_ptr(read_queue);
56  thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages);
57  std::unique_ptr<JackMidiAsyncQueue> thread_queue_ptr(thread_queue);
58  thread = new JackThread(this);
59  std::unique_ptr<JackThread> thread_ptr(thread);
60  char error_message[MAXERRORLENGTH];
61  MMRESULT result = midiOutOpen(&handle, index, (DWORD_PTR)HandleMessageEvent,
62  (DWORD_PTR)this, CALLBACK_FUNCTION);
63  if (result != MMSYSERR_NOERROR) {
64  GetOutErrorString(result, error_message);
65  goto raise_exception;
66  }
67  thread_queue_semaphore = CreateSemaphore(NULL, 0, max_messages, NULL);
68  if (thread_queue_semaphore == NULL) {
69  GetOSErrorString(error_message);
70  goto close_handle;
71  }
72  sysex_semaphore = CreateSemaphore(NULL, 0, 1, NULL);
73  if (sysex_semaphore == NULL) {
74  GetOSErrorString(error_message);
75  goto destroy_thread_queue_semaphore;
76  }
77  MIDIOUTCAPS capabilities;
78  char *name_tmp;
79  result = midiOutGetDevCaps(index, &capabilities, sizeof(capabilities));
80  if (result != MMSYSERR_NOERROR) {
81  WriteOutError("JackWinMMEOutputPort [constructor]", "midiOutGetDevCaps",
82  result);
83  name_tmp = (char*)driver_name;
84  } else {
85  name_tmp = capabilities.szPname;
86  }
87  snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", alias_name, name_tmp,
88  index + 1);
89  snprintf(name, sizeof(name) - 1, "%s:playback_%d", client_name, index + 1);
90  strncpy(device_name, name_tmp, sizeof(device_name) - 1);
91  read_queue_ptr.release();
92  thread_queue_ptr.release();
93  thread_ptr.release();
94  return;
95 
96  destroy_thread_queue_semaphore:
97  if (! CloseHandle(thread_queue_semaphore)) {
98  WriteOSError("JackWinMMEOutputPort [constructor]", "CloseHandle");
99  }
100  close_handle:
101  result = midiOutClose(handle);
102  if (result != MMSYSERR_NOERROR) {
103  WriteOutError("JackWinMMEOutputPort [constructor]", "midiOutClose",
104  result);
105  }
106  raise_exception:
107  throw std::runtime_error(error_message);
108 }
109 
110 JackWinMMEOutputPort::~JackWinMMEOutputPort()
111 {
112  MMRESULT result = midiOutReset(handle);
113  if (result != MMSYSERR_NOERROR) {
114  WriteOutError("JackWinMMEOutputPort [destructor]", "midiOutReset",
115  result);
116  }
117  result = midiOutClose(handle);
118  if (result != MMSYSERR_NOERROR) {
119  WriteOutError("JackWinMMEOutputPort [destructor]", "midiOutClose",
120  result);
121  }
122  if (! CloseHandle(sysex_semaphore)) {
123  WriteOSError("JackWinMMEOutputPort [destructor]", "CloseHandle");
124  }
125  if (! CloseHandle(thread_queue_semaphore)) {
126  WriteOSError("JackWinMMEOutputPort [destructor]", "CloseHandle");
127  }
128  delete read_queue;
129  delete thread_queue;
130  delete thread;
131 }
132 
133 bool
134 JackWinMMEOutputPort::Execute()
135 {
136  for (;;) {
137  if (! Wait(thread_queue_semaphore)) {
138  jack_log("JackWinMMEOutputPort::Execute BREAK");
139 
140  break;
141  }
142  jack_midi_event_t *event = thread_queue->DequeueEvent();
143  if (! event) {
144  break;
145  }
146  jack_time_t frame_time = GetTimeFromFrames(event->time);
147  jack_time_t current_time = GetMicroSeconds();
148  if (frame_time > current_time) {
149  LARGE_INTEGER due_time;
150 
151  // 100 ns resolution
152  due_time.QuadPart =
153  -((LONGLONG) ((frame_time - current_time) * 10));
154  if (! SetWaitableTimer(timer, &due_time, 0, NULL, NULL, 0)) {
155  WriteOSError("JackWinMMEOutputPort::Execute",
156  "SetWaitableTimer");
157  break;
158  }
159 
160  // Debugging code
161  jack_log("JackWinMMEOutputPort::Execute - waiting at %f for %f "
162  "milliseconds before sending message (current frame: %d, "
163  "send frame: %d)",
164  ((double) current_time) / 1000.0,
165  ((double) (frame_time - current_time)) / 1000.0,
166  GetFramesFromTime(current_time), event->time);
167  // End debugging code
168 
169  if (! Wait(timer)) {
170  break;
171  }
172 
173  // Debugging code
174  jack_time_t wakeup_time = GetMicroSeconds();
175  jack_log("JackWinMMEOutputPort::Execute - woke up at %f.",
176  ((double) wakeup_time) / 1000.0);
177  if (wakeup_time > frame_time) {
178  jack_log("JackWinMMEOutputPort::Execute - overslept by %f "
179  "milliseconds.",
180  ((double) (wakeup_time - frame_time)) / 1000.0);
181  } else if (wakeup_time < frame_time) {
182  jack_log("JackWinMMEOutputPort::Execute - woke up %f "
183  "milliseconds too early.",
184  ((double) (frame_time - wakeup_time)) / 1000.0);
185  }
186  // End debugging code
187 
188  }
189  jack_midi_data_t *data = event->buffer;
190  DWORD message = 0;
191  MMRESULT result;
192  size_t size = event->size;
193  switch (size) {
194  case 3:
195  message |= (((DWORD) data[2]) << 16);
196  // Fallthrough on purpose.
197  case 2:
198  message |= (((DWORD) data[1]) << 8);
199  // Fallthrough on purpose.
200  case 1:
201  message |= (DWORD) data[0];
202  result = midiOutShortMsg(handle, message);
203  if (result != MMSYSERR_NOERROR) {
204  WriteOutError("JackWinMMEOutputPort::Execute",
205  "midiOutShortMsg", result);
206  }
207  continue;
208  }
209  MIDIHDR header;
210  header.dwBufferLength = size;
211  header.dwFlags = 0;
212  header.lpData = (LPSTR) data;
213  result = midiOutPrepareHeader(handle, &header, sizeof(MIDIHDR));
214  if (result != MMSYSERR_NOERROR) {
215  WriteOutError("JackWinMMEOutputPort::Execute",
216  "midiOutPrepareHeader", result);
217  continue;
218  }
219  result = midiOutLongMsg(handle, &header, sizeof(MIDIHDR));
220  if (result != MMSYSERR_NOERROR) {
221  WriteOutError("JackWinMMEOutputPort::Execute", "midiOutLongMsg",
222  result);
223  continue;
224  }
225 
226  // System exclusive messages may be sent synchronously or
227  // asynchronously. The choice is up to the WinMME driver. So, we wait
228  // until the message is sent, regardless of the driver's choice.
229  if (! Wait(sysex_semaphore)) {
230  break;
231  }
232 
233  result = midiOutUnprepareHeader(handle, &header, sizeof(MIDIHDR));
234  if (result != MMSYSERR_NOERROR) {
235  WriteOutError("JackWinMMEOutputPort::Execute",
236  "midiOutUnprepareHeader", result);
237  break;
238  }
239  }
240  return false;
241 }
242 
243 void
244 JackWinMMEOutputPort::GetOutErrorString(MMRESULT error, LPTSTR text)
245 {
246  MMRESULT result = midiOutGetErrorText(error, text, MAXERRORLENGTH);
247  if (result != MMSYSERR_NOERROR) {
248  snprintf(text, MAXERRORLENGTH, "Unknown MM error code '%d'", error);
249  }
250 }
251 
252 void
253 JackWinMMEOutputPort::HandleMessage(UINT message, DWORD_PTR param1,
254  DWORD_PTR param2)
255 {
256  set_threaded_log_function();
257  switch (message) {
258  case MOM_CLOSE:
259  jack_log("JackWinMMEOutputPort::HandleMessage - MIDI device closed.");
260  break;
261  case MOM_DONE:
262  Signal(sysex_semaphore);
263  break;
264  case MOM_OPEN:
265  jack_log("JackWinMMEOutputPort::HandleMessage - MIDI device opened.");
266  break;
267  case MOM_POSITIONCB:
268  LPMIDIHDR header = (LPMIDIHDR) param1;
269  jack_log("JackWinMMEOutputPort::HandleMessage - %d bytes out of %d "
270  "bytes of the current sysex message have been sent.",
271  header->dwOffset, header->dwBytesRecorded);
272  }
273 }
274 
275 bool
277 {
278  set_threaded_log_function();
279  // XX: Can more be done? Ideally, this thread should have the JACK server
280  // thread priority + 1.
281  if (thread->AcquireSelfRealTime(GetEngineControl()->fServerPriority)) {
282  jack_error("JackWinMMEOutputPort::Init - could not acquire realtime "
283  "scheduling. Continuing anyway.");
284  }
285  return true;
286 }
287 
288 void
289 JackWinMMEOutputPort::ProcessJack(JackMidiBuffer *port_buffer,
290  jack_nframes_t frames)
291 {
292  read_queue->ResetMidiBuffer(port_buffer);
293  for (jack_midi_event_t *event = read_queue->DequeueEvent(); event;
294  event = read_queue->DequeueEvent()) {
295 
296  switch (thread_queue->EnqueueEvent(event, frames)) {
297  case JackMidiWriteQueue::BUFFER_FULL:
298  jack_error("JackWinMMEOutputPort::ProcessJack - The thread queue "
299  "buffer is full. Dropping event.");
300  break;
301  case JackMidiWriteQueue::BUFFER_TOO_SMALL:
302  jack_error("JackWinMMEOutputPort::ProcessJack - The thread queue "
303  "couldn't enqueue a %d-byte event. Dropping event.",
304  event->size);
305  break;
306  default:
307  Signal(thread_queue_semaphore);
308  }
309  }
310 }
311 
312 bool
313 JackWinMMEOutputPort::Signal(HANDLE semaphore)
314 {
315  bool result = (bool) ReleaseSemaphore(semaphore, 1, NULL);
316  if (! result) {
317  WriteOSError("JackWinMMEOutputPort::Signal", "ReleaseSemaphore");
318  }
319  return result;
320 }
321 
322 bool
323 JackWinMMEOutputPort::Start()
324 {
325  if (thread->GetStatus() != JackThread::kIdle) {
326  return true;
327  }
328  timer = CreateWaitableTimer(NULL, FALSE, NULL);
329  if (! timer) {
330  WriteOSError("JackWinMMEOutputPort::Start", "CreateWaitableTimer");
331  return false;
332  }
333  if (! thread->StartSync()) {
334  return true;
335  }
336  jack_error("JackWinMMEOutputPort::Start - failed to start MIDI processing "
337  "thread.");
338  if (! CloseHandle(timer)) {
339  WriteOSError("JackWinMMEOutputPort::Start", "CloseHandle");
340  }
341  return false;
342 }
343 
344 bool
345 JackWinMMEOutputPort::Stop()
346 {
347  jack_log("JackWinMMEOutputPort::Stop - stopping MIDI output port "
348  "processing thread.");
349 
350  int result;
351  const char *verb;
352  switch (thread->GetStatus()) {
353  case JackThread::kIniting:
354  case JackThread::kStarting:
355  result = thread->Kill();
356  verb = "kill";
357  break;
358  case JackThread::kRunning:
359  Signal(thread_queue_semaphore);
360  result = thread->Stop();
361  verb = "stop";
362  break;
363  default:
364  return true;
365  }
366  if (result) {
367  jack_error("JackWinMMEOutputPort::Stop - could not %s MIDI processing "
368  "thread.", verb);
369  }
370  if (! CloseHandle(timer)) {
371  WriteOSError("JackWinMMEOutputPort::Stop", "CloseHandle");
372  result = -1;
373  }
374  return ! result;
375 }
376 
377 bool
378 JackWinMMEOutputPort::Wait(HANDLE semaphore)
379 {
380  DWORD result = WaitForSingleObject(semaphore, INFINITE);
381  switch (result) {
382  case WAIT_FAILED:
383  WriteOSError("JackWinMMEOutputPort::Wait", "WaitForSingleObject");
384  break;
385  case WAIT_OBJECT_0:
386  return true;
387  default:
388  jack_error("JackWinMMEOutputPort::Wait - unexpected result from "
389  "'WaitForSingleObject'.");
390  }
391  return false;
392 }
393 
394 void
395 JackWinMMEOutputPort::WriteOutError(const char *jack_func, const char *mm_func,
396  MMRESULT result)
397 {
398  char error_message[MAXERRORLENGTH];
399  GetOutErrorString(result, error_message);
400  jack_error("%s - %s: %s", jack_func, mm_func, error_message);
401 }
void ResetMidiBuffer(JackMidiBuffer *buffer)
SERVER_EXPORT void jack_error(const char *fmt,...)
Definition: JackError.cpp:92
virtual jack_midi_event_t * DequeueEvent()
SERVER_EXPORT void jack_log(const char *fmt,...)
Definition: JackError.cpp:108
virtual EnqueueResult EnqueueEvent(jack_nframes_t time, size_t size, jack_midi_data_t *buffer)