JackWinMMEOutputPort.cpp 12.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*
Copyright (C) 2011 Devin Anderson

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include <memory>
#include <stdexcept>

#include "JackMidiUtil.h"
#include "JackTime.h"
#include "JackWinMMEOutputPort.h"

using Jack::JackWinMMEOutputPort;

///////////////////////////////////////////////////////////////////////////////
// Static callbacks
///////////////////////////////////////////////////////////////////////////////

void CALLBACK
JackWinMMEOutputPort::HandleMessageEvent(HMIDIOUT handle, UINT message,
                                         DWORD_PTR port, DWORD_PTR param1,
                                         DWORD_PTR param2)
{
    ((JackWinMMEOutputPort *) port)->HandleMessage(message, param1, param2);
}

///////////////////////////////////////////////////////////////////////////////
// Class
///////////////////////////////////////////////////////////////////////////////

JackWinMMEOutputPort::JackWinMMEOutputPort(const char *alias_name,
                                           const char *client_name,
                                           const char *driver_name, UINT index,
                                           size_t max_bytes,
                                           size_t max_messages)
{
    read_queue = new JackMidiBufferReadQueue();
sletz's avatar
sletz committed
52
    std::auto_ptr<JackMidiBufferReadQueue> read_queue_ptr(read_queue);
53
    thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages);
sletz's avatar
sletz committed
54
    std::auto_ptr<JackMidiAsyncQueue> thread_queue_ptr(thread_queue);
55
    thread = new JackThread(this);
sletz's avatar
sletz committed
56
    std::auto_ptr<JackThread> thread_ptr(thread);
57
    char error_message[MAXERRORLENGTH];
58
59
    MMRESULT result = midiOutOpen(&handle, index, (DWORD)HandleMessageEvent,
                                  (DWORD)this, CALLBACK_FUNCTION);
60
    if (result != MMSYSERR_NOERROR) {
61
        GetOutErrorString(result, error_message);
62
63
64
65
66
67
68
69
70
71
72
73
        goto raise_exception;
    }
    thread_queue_semaphore = CreateSemaphore(NULL, 0, max_messages, NULL);
    if (thread_queue_semaphore == NULL) {
        GetOSErrorString(error_message);
        goto close_handle;
    }
    sysex_semaphore = CreateSemaphore(NULL, 0, 1, NULL);
    if (sysex_semaphore == NULL) {
        GetOSErrorString(error_message);
        goto destroy_thread_queue_semaphore;
    }
Stephane Letz's avatar
Stephane Letz committed
74
75
    MIDIOUTCAPS capabilities;
    char *name_tmp;
76
77
    result = midiOutGetDevCaps(index, &capabilities, sizeof(capabilities));
    if (result != MMSYSERR_NOERROR) {
78
        WriteOutError("JackWinMMEOutputPort [constructor]", "midiOutGetDevCaps",
79
                     result);
Stephane Letz's avatar
Stephane Letz committed
80
        name_tmp = (char*)driver_name;
81
    } else {
Stephane Letz's avatar
Stephane Letz committed
82
        name_tmp = capabilities.szPname;
83
    }
84
    snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", alias_name, name_tmp,
85
86
             index + 1);
    snprintf(name, sizeof(name) - 1, "%s:playback_%d", client_name, index + 1);
sletz's avatar
sletz committed
87
    read_queue_ptr.release();
sletz's avatar
sletz committed
88
    thread_queue_ptr.release();
sletz's avatar
sletz committed
89
    thread_ptr.release();
90
91
92
93
94
95
96
97
98
    return;

 destroy_thread_queue_semaphore:
    if (! CloseHandle(thread_queue_semaphore)) {
        WriteOSError("JackWinMMEOutputPort [constructor]", "CloseHandle");
    }
 close_handle:
    result = midiOutClose(handle);
    if (result != MMSYSERR_NOERROR) {
99
        WriteOutError("JackWinMMEOutputPort [constructor]", "midiOutClose",
100
101
                     result);
    }
sletz's avatar
sletz committed
102
 raise_exception:
103
104
105
106
107
108
109
    throw std::runtime_error(error_message);
}

JackWinMMEOutputPort::~JackWinMMEOutputPort()
{
    MMRESULT result = midiOutReset(handle);
    if (result != MMSYSERR_NOERROR) {
110
        WriteOutError("JackWinMMEOutputPort [destructor]", "midiOutReset",
111
112
113
114
                     result);
    }
    result = midiOutClose(handle);
    if (result != MMSYSERR_NOERROR) {
115
        WriteOutError("JackWinMMEOutputPort [destructor]", "midiOutClose",
116
117
118
119
120
121
122
123
124
                     result);
    }
    if (! CloseHandle(sysex_semaphore)) {
        WriteOSError("JackWinMMEOutputPort [destructor]", "CloseHandle");
    }
    if (! CloseHandle(thread_queue_semaphore)) {
        WriteOSError("JackWinMMEOutputPort [destructor]", "CloseHandle");
    }
    delete read_queue;
sletz's avatar
sletz committed
125
    delete thread_queue;
sletz's avatar
sletz committed
126
    delete thread;
127
128
129
130
131
132
}

bool
JackWinMMEOutputPort::Execute()
{
    for (;;) {
sletz's avatar
sletz committed
133
         if (! Wait(thread_queue_semaphore)) {
134
135
            jack_log("JackWinMMEOutputPort::Execute BREAK");

136
            break;
sletz's avatar
sletz committed
137
        }
sletz's avatar
sletz committed
138

139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
        jack_midi_event_t *event = thread_queue->DequeueEvent();
        if (! event) {
            break;
        }
        jack_time_t frame_time = GetTimeFromFrames(event->time);
        for (jack_time_t current_time = GetMicroSeconds();
             frame_time > current_time; current_time = GetMicroSeconds()) {
            jack_time_t sleep_time = frame_time - current_time;

            // Windows has a millisecond sleep resolution for its Sleep calls.
            // This is unfortunate, as MIDI timing often requires a higher
            // resolution.  For now, we attempt to compensate by letting an
            // event be sent if we're less than 500 microseconds from sending
            // the event.  We assume that it's better to let an event go out
            // 499 microseconds early than let an event go out 501 microseconds
            // late.  Of course, that's assuming optimal sleep times, which is
            // a whole different Windows issue ...
            if (sleep_time < 500) {
                break;
            }

            if (sleep_time < 1000) {
                sleep_time = 1000;
            }
            JackSleep(sleep_time);
        }
        jack_midi_data_t *data = event->buffer;
        DWORD message = 0;
        MMRESULT result;
        size_t size = event->size;
        switch (size) {
        case 3:
            message |= (((DWORD) data[2]) << 16);
            // Fallthrough on purpose.
        case 2:
            message |= (((DWORD) data[1]) << 8);
            // Fallthrough on purpose.
        case 1:
            message |= (DWORD) data[0];
            result = midiOutShortMsg(handle, message);
            if (result != MMSYSERR_NOERROR) {
180
                WriteOutError("JackWinMMEOutputPort::Execute",
181
182
183
184
185
                             "midiOutShortMsg", result);
            }
            continue;
        }
        MIDIHDR header;
186
187
        header.dwBufferLength = size;
        header.dwBytesRecorded = size;
188
        header.dwFlags = 0;
189
190
        header.dwOffset = 0;
        header.dwUser = 0;
Stephane Letz's avatar
Stephane Letz committed
191
        header.lpData = (LPSTR)data;
192
193
        result = midiOutPrepareHeader(handle, &header, sizeof(MIDIHDR));
        if (result != MMSYSERR_NOERROR) {
194
            WriteOutError("JackWinMMEOutputPort::Execute",
195
196
197
198
199
                         "midiOutPrepareHeader", result);
            continue;
        }
        result = midiOutLongMsg(handle, &header, sizeof(MIDIHDR));
        if (result != MMSYSERR_NOERROR) {
200
            WriteOutError("JackWinMMEOutputPort::Execute", "midiOutLongMsg",
201
202
203
204
205
206
207
208
209
210
211
212
213
                         result);
            continue;
        }

        // System exclusive messages may be sent synchronously or
        // asynchronously.  The choice is up to the WinMME driver.  So, we wait
        // until the message is sent, regardless of the driver's choice.
        if (! Wait(sysex_semaphore)) {
            break;
        }

        result = midiOutUnprepareHeader(handle, &header, sizeof(MIDIHDR));
        if (result != MMSYSERR_NOERROR) {
214
            WriteOutError("JackWinMMEOutputPort::Execute",
215
216
217
                         "midiOutUnprepareHeader", result);
            break;
        }
sletz's avatar
sletz committed
218

219
220
221
222
223
224
225
226
227
    }
 stop_execution:
    return false;
}

void
JackWinMMEOutputPort::HandleMessage(UINT message, DWORD_PTR param1,
                                    DWORD_PTR param2)
{
228
    set_threaded_log_function();
229
230
231
232
233
    switch (message) {
    case MOM_CLOSE:
        jack_info("JackWinMMEOutputPort::HandleMessage - MIDI device closed.");
        break;
    case MOM_DONE:
234
        Signal(sysex_semaphore);
235
236
237
        break;
    case MOM_OPEN:
        jack_info("JackWinMMEOutputPort::HandleMessage - MIDI device opened.");
238
239
240
241
242
243
        break;
    case MOM_POSITIONCB:
        LPMIDIHDR header = (LPMIDIHDR) param2;
        jack_info("JackWinMMEOutputPort::HandleMessage - %d bytes out of %d "
                  "bytes of the current sysex message have been sent.",
                  header->dwOffset, header->dwBytesRecorded);
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
    }
}

bool
JackWinMMEOutputPort::Init()
{
    set_threaded_log_function();
    // XX: Can more be done?  Ideally, this thread should have the JACK server
    // thread priority + 1.
    if (thread->AcquireSelfRealTime()) {
        jack_error("JackWinMMEOutputPort::Init - could not acquire realtime "
                   "scheduling. Continuing anyway.");
    }
    return true;
}

void
JackWinMMEOutputPort::ProcessJack(JackMidiBuffer *port_buffer,
                                  jack_nframes_t frames)
sletz's avatar
sletz committed
263
264
{
    read_queue->ResetMidiBuffer(port_buffer);
sletz's avatar
sletz committed
265

266
    for (jack_midi_event_t *event = read_queue->DequeueEvent(); event;
sletz's avatar
sletz committed
267
        event = read_queue->DequeueEvent()) {
sletz's avatar
sletz committed
268

269
270
271
272
273
274
275
276
277
278
279
        switch (thread_queue->EnqueueEvent(event, frames)) {
        case JackMidiWriteQueue::BUFFER_FULL:
            jack_error("JackWinMMEOutputPort::ProcessJack - The thread queue "
                       "buffer is full.  Dropping event.");
            break;
        case JackMidiWriteQueue::BUFFER_TOO_SMALL:
            jack_error("JackWinMMEOutputPort::ProcessJack - The thread queue "
                       "couldn't enqueue a %d-byte event.  Dropping event.",
                       event->size);
            break;
        default:
280
            Signal(thread_queue_semaphore);
281
282
283
284
        }
    }
}

285
bool
Stephane Letz's avatar
Stephane Letz committed
286
JackWinMMEOutputPort::Signal(HANDLE semaphore)
287
288
289
290
291
292
293
294
{
    bool result = (bool) ReleaseSemaphore(semaphore, 1, NULL);
    if (! result) {
        WriteOSError("JackWinMMEOutputPort::Signal", "ReleaseSemaphore");
    }
    return result;
}

295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
bool
JackWinMMEOutputPort::Start()
{
    bool result = thread->GetStatus() != JackThread::kIdle;
    if (! result) {
        result = ! thread->StartSync();
        if (! result) {
            jack_error("JackWinMMEOutputPort::Start - failed to start MIDI "
                       "processing thread.");
        }
    }
    return result;
}

bool
JackWinMMEOutputPort::Stop()
{
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
    jack_info("JackWinMMEOutputPort::Stop - stopping MIDI output port "
              "processing thread.");

    int result;
    const char *verb;
    switch (thread->GetStatus()) {
    case JackThread::kIniting:
    case JackThread::kStarting:
        result = thread->Kill();
        verb = "kill";
        break;
    case JackThread::kRunning:
        Signal(thread_queue_semaphore);
        result = thread->Stop();
        verb = "stop";
        break;
    default:
        return true;
330
    }
331
332
333
334
335
    if (result) {
        jack_error("JackWinMMEOutputPort::Stop - could not %s MIDI processing "
                   "thread.", verb);
    }
    return ! result;
336
337
338
}

bool
Stephane Letz's avatar
Stephane Letz committed
339
JackWinMMEOutputPort::Wait(HANDLE semaphore)
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
{
    DWORD result = WaitForSingleObject(semaphore, INFINITE);
    switch (result) {
    case WAIT_FAILED:
        WriteOSError("JackWinMMEOutputPort::Wait", "WaitForSingleObject");
        break;
    case WAIT_OBJECT_0:
        return true;
    default:
        jack_error("JackWinMMEOutputPort::Wait - unexpected result from "
                   "'WaitForSingleObject'.");
    }
    return false;
}

void
356
JackWinMMEOutputPort::GetOutErrorString(MMRESULT error, LPTSTR text)
357
{
358
359
360
361
    MMRESULT result = midiOutGetErrorText(error, text, MAXERRORLENGTH);
    if (result != MMSYSERR_NOERROR) {
        snprintf(text, MAXERRORLENGTH, "Unknown MM error code '%d'", error);
    }
362
363
364
}

void
365
366
JackWinMMEOutputPort::WriteOutError(const char *jack_func, const char *mm_func,
                                   MMRESULT result)
367
{
Stephane Letz's avatar
Stephane Letz committed
368
    char error_message[MAXERRORLENGTH];
Stephane Letz's avatar
Stephane Letz committed
369
    GetOutErrorString(result, error_message);
370
    jack_error("%s - %s: %s", jack_func, mm_func, error_message);
371
}
sletz's avatar
sletz committed
372