Jack2 1.9.12

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