Replace Util.pprint by Logs.pprint
[jack2.git] / windows / winmme / JackWinMMEOutputPort.cpp
blob21200044e8039d0e9496f357cfa8604e54cc8b40
1 /*
2 Copyright (C) 2011 Devin Anderson
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.
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.
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.
20 #include <memory>
21 #include <stdexcept>
23 #include "JackMidiUtil.h"
24 #include "JackTime.h"
25 #include "JackWinMMEOutputPort.h"
26 #include "JackGlobals.h"
27 #include "JackEngineControl.h"
29 using Jack::JackWinMMEOutputPort;
31 ///////////////////////////////////////////////////////////////////////////////
32 // Static callbacks
33 ///////////////////////////////////////////////////////////////////////////////
35 void CALLBACK
36 JackWinMMEOutputPort::HandleMessageEvent(HMIDIOUT handle, UINT message,
37 DWORD_PTR port, DWORD_PTR param1,
38 DWORD_PTR param2)
40 ((JackWinMMEOutputPort *) port)->HandleMessage(message, param1, param2);
43 ///////////////////////////////////////////////////////////////////////////////
44 // Class
45 ///////////////////////////////////////////////////////////////////////////////
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)
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;
67 thread_queue_semaphore = CreateSemaphore(NULL, 0, max_messages, NULL);
68 if (thread_queue_semaphore == NULL) {
69 GetOSErrorString(error_message);
70 goto close_handle;
72 sysex_semaphore = CreateSemaphore(NULL, 0, 1, NULL);
73 if (sysex_semaphore == NULL) {
74 GetOSErrorString(error_message);
75 goto destroy_thread_queue_semaphore;
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;
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;
95 destroy_thread_queue_semaphore:
96 if (! CloseHandle(thread_queue_semaphore)) {
97 WriteOSError("JackWinMMEOutputPort [constructor]", "CloseHandle");
99 close_handle:
100 result = midiOutClose(handle);
101 if (result != MMSYSERR_NOERROR) {
102 WriteOutError("JackWinMMEOutputPort [constructor]", "midiOutClose",
103 result);
105 raise_exception:
106 throw std::runtime_error(error_message);
109 JackWinMMEOutputPort::~JackWinMMEOutputPort()
111 MMRESULT result = midiOutReset(handle);
112 if (result != MMSYSERR_NOERROR) {
113 WriteOutError("JackWinMMEOutputPort [destructor]", "midiOutReset",
114 result);
116 result = midiOutClose(handle);
117 if (result != MMSYSERR_NOERROR) {
118 WriteOutError("JackWinMMEOutputPort [destructor]", "midiOutClose",
119 result);
121 if (! CloseHandle(sysex_semaphore)) {
122 WriteOSError("JackWinMMEOutputPort [destructor]", "CloseHandle");
124 if (! CloseHandle(thread_queue_semaphore)) {
125 WriteOSError("JackWinMMEOutputPort [destructor]", "CloseHandle");
127 delete read_queue;
128 delete thread_queue;
129 delete thread;
132 bool
133 JackWinMMEOutputPort::Execute()
135 for (;;) {
136 if (! Wait(thread_queue_semaphore)) {
137 jack_log("JackWinMMEOutputPort::Execute BREAK");
139 break;
141 jack_midi_event_t *event = thread_queue->DequeueEvent();
142 if (! event) {
143 break;
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;
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;
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
168 if (! Wait(timer)) {
169 break;
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);
185 // End debugging code
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);
206 continue;
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;
218 result = midiOutLongMsg(handle, &header, sizeof(MIDIHDR));
219 if (result != MMSYSERR_NOERROR) {
220 WriteOutError("JackWinMMEOutputPort::Execute", "midiOutLongMsg",
221 result);
222 continue;
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;
232 result = midiOutUnprepareHeader(handle, &header, sizeof(MIDIHDR));
233 if (result != MMSYSERR_NOERROR) {
234 WriteOutError("JackWinMMEOutputPort::Execute",
235 "midiOutUnprepareHeader", result);
236 break;
239 return false;
242 void
243 JackWinMMEOutputPort::GetOutErrorString(MMRESULT error, LPTSTR text)
245 MMRESULT result = midiOutGetErrorText(error, text, MAXERRORLENGTH);
246 if (result != MMSYSERR_NOERROR) {
247 snprintf(text, MAXERRORLENGTH, "Unknown MM error code '%d'", error);
251 void
252 JackWinMMEOutputPort::HandleMessage(UINT message, DWORD_PTR param1,
253 DWORD_PTR param2)
255 set_threaded_log_function();
256 switch (message) {
257 case MOM_CLOSE:
258 jack_info("JackWinMMEOutputPort::HandleMessage - MIDI device closed.");
259 break;
260 case MOM_DONE:
261 Signal(sysex_semaphore);
262 break;
263 case MOM_OPEN:
264 jack_info("JackWinMMEOutputPort::HandleMessage - MIDI device opened.");
265 break;
266 case MOM_POSITIONCB:
267 LPMIDIHDR header = (LPMIDIHDR) param1;
268 jack_info("JackWinMMEOutputPort::HandleMessage - %d bytes out of %d "
269 "bytes of the current sysex message have been sent.",
270 header->dwOffset, header->dwBytesRecorded);
274 bool
275 JackWinMMEOutputPort::Init()
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.");
284 return true;
287 void
288 JackWinMMEOutputPort::ProcessJack(JackMidiBuffer *port_buffer,
289 jack_nframes_t frames)
291 read_queue->ResetMidiBuffer(port_buffer);
292 for (jack_midi_event_t *event = read_queue->DequeueEvent(); event;
293 event = read_queue->DequeueEvent()) {
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);
311 bool
312 JackWinMMEOutputPort::Signal(HANDLE semaphore)
314 bool result = (bool) ReleaseSemaphore(semaphore, 1, NULL);
315 if (! result) {
316 WriteOSError("JackWinMMEOutputPort::Signal", "ReleaseSemaphore");
318 return result;
321 bool
322 JackWinMMEOutputPort::Start()
324 if (thread->GetStatus() != JackThread::kIdle) {
325 return true;
327 timer = CreateWaitableTimer(NULL, FALSE, NULL);
328 if (! timer) {
329 WriteOSError("JackWinMMEOutputPort::Start", "CreateWaitableTimer");
330 return false;
332 if (! thread->StartSync()) {
333 return true;
335 jack_error("JackWinMMEOutputPort::Start - failed to start MIDI processing "
336 "thread.");
337 if (! CloseHandle(timer)) {
338 WriteOSError("JackWinMMEOutputPort::Start", "CloseHandle");
340 return false;
343 bool
344 JackWinMMEOutputPort::Stop()
346 jack_info("JackWinMMEOutputPort::Stop - stopping MIDI output port "
347 "processing thread.");
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;
365 if (result) {
366 jack_error("JackWinMMEOutputPort::Stop - could not %s MIDI processing "
367 "thread.", verb);
369 if (! CloseHandle(timer)) {
370 WriteOSError("JackWinMMEOutputPort::Stop", "CloseHandle");
371 result = -1;
373 return ! result;
376 bool
377 JackWinMMEOutputPort::Wait(HANDLE semaphore)
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'.");
390 return false;
393 void
394 JackWinMMEOutputPort::WriteOutError(const char *jack_func, const char *mm_func,
395 MMRESULT result)
397 char error_message[MAXERRORLENGTH];
398 GetOutErrorString(result, error_message);
399 jack_error("%s - %s: %s", jack_func, mm_func, error_message);