Subversion Repositories eduke32

Rev

Rev 8757 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
8215 terminx 1
/*
2
 Copyright (C) 2009 Jonathon Fowler <jf@jonof.id.au>
8757 terminx 3
 Copyright (C) EDuke32 developers and contributors
4
 
8215 terminx 5
 This program is free software; you can redistribute it and/or
6
 modify it under the terms of the GNU General Public License
7
 as published by the Free Software Foundation; either version 2
8
 of the License, or (at your option) any later version.
9
 
10
 This program is distributed in the hope that it will be useful,
11
 but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13
 
14
 See the GNU General Public License for more details.
15
 
16
 You should have received a copy of the GNU General Public License
17
 along with this program; if not, write to the Free Software
18
 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
 
20
 */
21
 
22
/**
23
 * WinMM MIDI output driver
24
 */
25
 
26
#include "driver_winmm.h"
27
 
28
#include "compat.h"
29
#include "ll.h"
8381 terminx 30
#include "midi.h"
8215 terminx 31
#include "midifuncs.h"
32
#include "multivoc.h"
33
 
34
#include <mmsystem.h>
35
 
36
#ifdef _MSC_VER
37
#define inline _inline
38
#endif
39
 
8384 terminx 40
UINT WinMM_DeviceID = MIDI_MAPPER;
41
 
8215 terminx 42
static int ErrorCode = WinMMErr_Ok;
43
 
8381 terminx 44
static BOOL      midiInstalled;
8215 terminx 45
static HMIDISTRM midiStream;
8381 terminx 46
static void    (*midiThreadService)(void);
8225 terminx 47
static uint32_t midiThreadTimer;
48
static uint32_t midiLastEventTime;
49
static uint32_t midiThreadQueueTimer;
50
static uint32_t midiThreadQueueTicks;
8381 terminx 51
static HANDLE   midiThread;
52
static HANDLE   midiThreadQuitEvent;
53
static HANDLE   midiMutex;
54
static BOOL     midiStreamRunning;
55
static int      midiLastDivision;
8215 terminx 56
 
8759 terminx 57
#define MME_THREAD_QUEUE_INTERVAL 10       // 1/10 sec
58
#define MME_MIDI_BUFFER_SPACE (12 * 128u)  // 128 note-on events
8381 terminx 59
 
8759 terminx 60
typedef struct MidiBuffer
61
{
8215 terminx 62
    struct MidiBuffer *next;
63
    struct MidiBuffer *prev;
64
 
65
    BOOL prepared;
66
    MIDIHDR hdr;
67
} MidiBuffer;
68
 
8381 terminx 69
static MidiBuffer activeMidiBuffers;
70
static MidiBuffer spareMidiBuffers;
8215 terminx 71
static MidiBuffer *currentMidiBuffer;
72
 
8759 terminx 73
int WinMMDrv_GetError(void) { return ErrorCode; }
8215 terminx 74
 
8757 terminx 75
const char *WinMMDrv_ErrorString(int ErrorNumber)
8215 terminx 76
{
8757 terminx 77
    switch (ErrorNumber)
8215 terminx 78
    {
8757 terminx 79
        case WinMMErr_Error:             return WinMMDrv_ErrorString(ErrorCode);
8759 terminx 80
        case WinMMErr_Ok:                return "MME ok.";
8757 terminx 81
        case WinMMErr_MIDIStreamOpen:    return "MIDI error: failed opening stream.";
82
        case WinMMErr_MIDIStreamRestart: return "MIDI error: failed starting stream.";
83
        case WinMMErr_MIDICreateEvent:   return "MIDI error: failed creating play thread quit event.";
84
        case WinMMErr_MIDIPlayThread:    return "MIDI error: failed creating play thread.";
85
        case WinMMErr_MIDICreateMutex:   return "MIDI error: failed creating play mutex.";
8759 terminx 86
        default:                         return "Unknown MME error code.";
8215 terminx 87
    }
88
}
89
 
90
 
91
// will append "err nnn (ssss)\n" to the end of the string it emits
92
static void midi_error(MMRESULT rv, const char * fmt, ...)
93
{
94
    va_list va;
95
    const char * errtxt = "?";
96
 
8757 terminx 97
    switch (rv)
98
    {
99
        case MMSYSERR_NOERROR:      errtxt = "MMSYSERR_NOERROR";      break;
100
        case MMSYSERR_BADDEVICEID:  errtxt = "MMSYSERR_BADDEVICEID";  break;
101
        case MMSYSERR_NOTENABLED:   errtxt = "MMSYSERR_NOTENABLED";   break;
102
        case MMSYSERR_ALLOCATED:    errtxt = "MMSYSERR_ALLOCATED";    break;
103
        case MMSYSERR_INVALHANDLE:  errtxt = "MMSYSERR_INVALHANDLE";  break;
104
        case MMSYSERR_NODRIVER:     errtxt = "MMSYSERR_NODRIVER";     break;
105
        case MMSYSERR_NOMEM:        errtxt = "MMSYSERR_NOMEM";        break;
8215 terminx 106
        case MMSYSERR_NOTSUPPORTED: errtxt = "MMSYSERR_NOTSUPPORTED"; break;
8757 terminx 107
        case MMSYSERR_BADERRNUM:    errtxt = "MMSYSERR_BADERRNUM";    break;
108
        case MMSYSERR_INVALFLAG:    errtxt = "MMSYSERR_INVALFLAG";    break;
109
        case MMSYSERR_INVALPARAM:   errtxt = "MMSYSERR_INVALPARAM";   break;
110
        case MMSYSERR_HANDLEBUSY:   errtxt = "MMSYSERR_HANDLEBUSY";   break;
8215 terminx 111
        case MMSYSERR_INVALIDALIAS: errtxt = "MMSYSERR_INVALIDALIAS"; break;
8757 terminx 112
        case MMSYSERR_BADDB:        errtxt = "MMSYSERR_BADDB";        break;
113
        case MMSYSERR_KEYNOTFOUND:  errtxt = "MMSYSERR_KEYNOTFOUND";  break;
114
        case MMSYSERR_READERROR:    errtxt = "MMSYSERR_READERROR";    break;
115
        case MMSYSERR_WRITEERROR:   errtxt = "MMSYSERR_WRITEERROR";   break;
116
        case MMSYSERR_DELETEERROR:  errtxt = "MMSYSERR_DELETEERROR";  break;
117
        case MMSYSERR_VALNOTFOUND:  errtxt = "MMSYSERR_VALNOTFOUND";  break;
118
        case MMSYSERR_NODRIVERCB:   errtxt = "MMSYSERR_NODRIVERCB";   break;
119
        default:                                                      break;
8215 terminx 120
    }
121
 
122
    va_start(va, fmt);
123
    MV_Printf(fmt, va);
124
    va_end(va);
125
 
126
    MV_Printf(" err %d (%s)\n", (int)rv, errtxt);
127
}
128
 
8759 terminx 129
static void midi_dispose_buffer(MidiBuffer *node, const char *caller)
8215 terminx 130
{
8759 terminx 131
    if (node->prepared)
132
    {
133
        auto rv = midiOutUnprepareHeader((HMIDIOUT)midiStream, &node->hdr, sizeof(MIDIHDR));
134
        if (rv != MMSYSERR_NOERROR)
135
            midi_error(rv, "MME %s/midi_dispose_buffer midiOutUnprepareHeader", caller);
8215 terminx 136
        node->prepared = FALSE;
137
    }
138
 
8759 terminx 139
    if (midiThread)
140
    {
8215 terminx 141
        // remove the node from the activeMidiBuffers list
8759 terminx 142
        LL_Remove(node, next, prev);
143
 
8215 terminx 144
        // when playing, we keep the buffers
8759 terminx 145
        LL_Add((MidiBuffer *)&spareMidiBuffers, node, next, prev);
146
        //MV_Printf("MME %s/midi_dispose_buffer recycling buffer %p\n", caller, node);
147
    }
148
    else
149
    {
8215 terminx 150
        // when not, we throw them away
8515 hendricks2 151
        Xfree(node);
8759 terminx 152
        //MV_Printf("MME %s/midi_dispose_buffer freeing buffer %p\n", caller, node);
8215 terminx 153
    }
154
}
155
 
156
static void midi_gc_buffers(void)
157
{
8759 terminx 158
    for (auto node = activeMidiBuffers.next, next = node->next; node != &activeMidiBuffers; node = next, next = node->next)
159
    {
160
        if (node->hdr.dwFlags & MHDR_DONE)
8215 terminx 161
            midi_dispose_buffer(node, "midi_gc_buffers");
162
    }
163
}
164
 
165
static void midi_free_buffers(void)
166
{
167
    //MV_Printf("waiting for active buffers to return\n");
8759 terminx 168
    while (!LL_ListEmpty(&activeMidiBuffers, next, prev))
169
    {
8215 terminx 170
        // wait for Windows to finish with all the buffers queued
171
        midi_gc_buffers();
172
        //MV_Printf("waiting...\n");
173
        Sleep(10);
174
    }
175
    //MV_Printf("waiting over\n");
176
 
8759 terminx 177
    for (auto node = spareMidiBuffers.next, next = node->next; node != &spareMidiBuffers; node = next, next = node->next)
178
    {
179
        LL_Remove(node, next, prev);
8515 hendricks2 180
        Xfree(node);
8759 terminx 181
        //MV_Printf("MME midi_free_buffers freeing buffer %p\n", node);
8215 terminx 182
    }
8759 terminx 183
 
184
    Bassert(currentMidiBuffer == 0);
8215 terminx 185
}
186
 
187
static void midi_flush_current_buffer(void)
188
{
189
    BOOL needsPrepare = FALSE;
190
 
8759 terminx 191
    if (!currentMidiBuffer)
8215 terminx 192
        return;
193
 
8759 terminx 194
    auto evt = (MIDIEVENT *)currentMidiBuffer->hdr.lpData;
8215 terminx 195
 
8759 terminx 196
    if (!midiThread)
197
    {
8215 terminx 198
        // immediate messages don't use a MIDIEVENT header so strip it off and
199
        // make some adjustments
200
 
8759 terminx 201
        currentMidiBuffer->hdr.dwBufferLength  = currentMidiBuffer->hdr.dwBytesRecorded - 12;
8215 terminx 202
        currentMidiBuffer->hdr.dwBytesRecorded = 0;
8759 terminx 203
        currentMidiBuffer->hdr.lpData          = (LPSTR)&evt->dwParms[0];
204
 
205
        if (currentMidiBuffer->hdr.dwBufferLength > 0)
8215 terminx 206
            needsPrepare = TRUE;
8759 terminx 207
    }
208
    else
8215 terminx 209
        needsPrepare = TRUE;
8759 terminx 210
 
211
    if (needsPrepare)
212
    {
8215 terminx 213
        // playing a file, or sending a sysex when not playing means
214
        // we need to prepare the buffer
8759 terminx 215
        auto rv = midiOutPrepareHeader((HMIDIOUT)midiStream, &currentMidiBuffer->hdr, sizeof(MIDIHDR));
216
        if (rv != MMSYSERR_NOERROR)
217
        {
218
            midi_error(rv, "MME midi_flush_current_buffer midiOutPrepareHeader");
8215 terminx 219
            return;
220
        }
221
 
222
        currentMidiBuffer->prepared = TRUE;
223
    }
224
 
8759 terminx 225
    if (midiThread)
226
    {
8215 terminx 227
        // midi file playing, so send events to the stream
228
 
8759 terminx 229
        LL_Add((MidiBuffer *)&activeMidiBuffers, currentMidiBuffer, next, prev);
8215 terminx 230
 
8759 terminx 231
        auto rv = midiStreamOut(midiStream, &currentMidiBuffer->hdr, sizeof(MIDIHDR));
232
        if (rv != MMSYSERR_NOERROR)
233
        {
234
            midi_error(rv, "MME midi_flush_current_buffer midiStreamOut");
8215 terminx 235
            midi_dispose_buffer(currentMidiBuffer, "midi_flush_current_buffer");
236
            return;
237
        }
238
 
8759 terminx 239
        //MV_Printf("MME midi_flush_current_buffer queued buffer %p\n", currentMidiBuffer);
240
    }
241
    else
242
    {
8215 terminx 243
        // midi file not playing, so send immediately
8759 terminx 244
 
245
        if (currentMidiBuffer->hdr.dwBufferLength > 0)
246
        {
247
            auto rv = midiOutLongMsg((HMIDIOUT)midiStream, &currentMidiBuffer->hdr, sizeof(MIDIHDR));
248
            if (rv == MMSYSERR_NOERROR)
249
            {
8215 terminx 250
                // busy-wait for Windows to be done with it
8759 terminx 251
                while (!(currentMidiBuffer->hdr.dwFlags & MHDR_DONE));
252
 
253
                //MV_Printf("MME midi_flush_current_buffer sent immediate long\n");
8215 terminx 254
            }
8759 terminx 255
            else
256
                midi_error(rv, "MME midi_flush_current_buffer midiOutLongMsg");
8215 terminx 257
        }
8759 terminx 258
        else
259
        {
260
            auto rv = midiOutShortMsg((HMIDIOUT)midiStream, evt->dwEvent);
261
            if (rv != MMSYSERR_NOERROR)
262
                midi_error(rv, "MME midi_flush_current_buffer midiOutShortMsg");
263
        }
8215 terminx 264
 
265
        midi_dispose_buffer(currentMidiBuffer, "midi_flush_current_buffer");
266
    }
8759 terminx 267
 
8215 terminx 268
    currentMidiBuffer = 0;
269
}
270
 
8759 terminx 271
static void midi_setup_event(int length, unsigned char **data)
8215 terminx 272
{
8759 terminx 273
    auto evt = (MIDIEVENT *)((intptr_t)currentMidiBuffer->hdr.lpData + currentMidiBuffer->hdr.dwBytesRecorded);
8215 terminx 274
 
275
    evt->dwDeltaTime = midiThread ? (midiThreadTimer - midiLastEventTime) : 0;
8759 terminx 276
    evt->dwStreamID  = 0;
8215 terminx 277
 
8759 terminx 278
    if (length <= 3)
279
    {
8215 terminx 280
        evt->dwEvent = (DWORD)MEVT_SHORTMSG << 24;
8759 terminx 281
        *data        = (unsigned char *)&evt->dwEvent;
282
    }
283
    else
284
    {
8215 terminx 285
        evt->dwEvent = ((DWORD)MEVT_LONGMSG << 24) | (length & 0x00ffffff);
8759 terminx 286
        *data        = (unsigned char *)&evt->dwParms[0];
8215 terminx 287
    }
288
}
289
 
290
/* Gets space in the buffer presently being filled.
291
   If insufficient space can be found in the buffer,
292
   what is there is flushed to the stream and a new
293
   buffer large enough is allocated.
294
 
295
   Returns a pointer to starting writing at in 'data'.
296
 */
8759 terminx 297
static BOOL midi_get_buffer(int length, unsigned char **data)
8215 terminx 298
{
8759 terminx 299
    uint32_t    datalen;
300
 
8215 terminx 301
    // determine the space to alloc.
302
    // the size of a MIDIEVENT is 3*sizeof(DWORD) = 12.
303
    // short messages need only that amount of space.
304
    // long messages need additional space equal to the length of
305
    //    the message, padded to 4 bytes
8759 terminx 306
 
307
    if (length <= 3)
8215 terminx 308
        datalen = 12;
8759 terminx 309
    else
310
    {
8215 terminx 311
        datalen = 12 + length;
8759 terminx 312
        if ((datalen & 3) > 0)
8215 terminx 313
            datalen += 4 - (datalen & 3);
314
    }
8759 terminx 315
 
316
    if (!midiThread)
317
        Bassert(currentMidiBuffer == 0);
318
 
319
    if (currentMidiBuffer && (currentMidiBuffer->hdr.dwBufferLength - currentMidiBuffer->hdr.dwBytesRecorded) >= datalen)
320
    {
8215 terminx 321
        // there was enough space in the current buffer, so hand that back
322
        midi_setup_event(length, data);
8759 terminx 323
 
8215 terminx 324
        currentMidiBuffer->hdr.dwBytesRecorded += datalen;
8759 terminx 325
 
8215 terminx 326
        return TRUE;
327
    }
8759 terminx 328
 
329
    if (currentMidiBuffer)
330
    {
8215 terminx 331
        // not enough space in the current buffer to accommodate the
332
        // new data, so flush it to the stream
333
        midi_flush_current_buffer();
334
        currentMidiBuffer = 0;
335
    }
8759 terminx 336
 
8215 terminx 337
    // check if there's a spare buffer big enough to hold the message
8759 terminx 338
    if (midiThread)
339
    {
340
        for (auto node = spareMidiBuffers.next; node != &spareMidiBuffers; node = node->next)
341
        {
342
            if (node->hdr.dwBufferLength >= datalen)
343
            {
8215 terminx 344
                // yes!
8759 terminx 345
                LL_Remove(node, next, prev);
346
 
8215 terminx 347
                node->hdr.dwBytesRecorded = 0;
8759 terminx 348
                Bmemset(node->hdr.lpData, 0, node->hdr.dwBufferLength);
349
 
8215 terminx 350
                currentMidiBuffer = node;
8759 terminx 351
 
352
                //MV_Printf("MME midi_get_buffer fetched buffer %p\n", node);
8215 terminx 353
                break;
354
            }
355
        }
356
    }
357
 
8759 terminx 358
    if (!currentMidiBuffer)
359
    {
360
        // there were no spare buffers, or none were big enough, so allocate a new one
361
        int const size = midiThread ? max(MME_MIDI_BUFFER_SPACE, datalen) : datalen;
362
        auto      node = (MidiBuffer *)Xmalloc(sizeof(MidiBuffer) + size);
363
 
364
        Bmemset(node, 0, sizeof(MidiBuffer) + datalen);
365
 
366
        node->hdr.dwUser = (DWORD_PTR)node;
367
        node->hdr.lpData = (LPSTR)((intptr_t)node + sizeof(MidiBuffer));
368
 
369
        node->hdr.dwBufferLength  = size;
8215 terminx 370
        node->hdr.dwBytesRecorded = 0;
8759 terminx 371
 
8215 terminx 372
        currentMidiBuffer = node;
8759 terminx 373
 
374
        //MV_Printf("MME midi_get_buffer allocated buffer %p\n", node);
8215 terminx 375
    }
376
 
377
    midi_setup_event(length, data);
378
 
379
    currentMidiBuffer->hdr.dwBytesRecorded += datalen;
8759 terminx 380
 
8215 terminx 381
    return TRUE;
382
}
383
 
384
static inline void midi_sequence_event(void)
385
{
8759 terminx 386
    if (!midiThread)
387
    {
8215 terminx 388
        // a midi event being sent out of playback (streaming) mode
389
        midi_flush_current_buffer();
390
        return;
391
    }
392
 
8759 terminx 393
    //MV_Printf("MME midi_sequence_event buffered\n");
394
 
8215 terminx 395
    midiLastEventTime = midiThreadTimer;
396
}
397
 
8759 terminx 398
static void MME_NoteOff(int channel, int key, int velocity)
8215 terminx 399
{
8759 terminx 400
    unsigned char *data;
8215 terminx 401
 
8759 terminx 402
    if (midi_get_buffer(3, &data))
403
    {
8381 terminx 404
        data[0] = WINMM_NOTE_OFF | channel;
8215 terminx 405
        data[1] = key;
406
        data[2] = velocity;
407
        midi_sequence_event();
8759 terminx 408
    }
409
    else
410
        MV_Printf("MME_NoteOff error\n");
8215 terminx 411
}
412
 
8759 terminx 413
static void MME_NoteOn(int channel, int key, int velocity)
8215 terminx 414
{
8759 terminx 415
    unsigned char *data;
8215 terminx 416
 
8759 terminx 417
    if (midi_get_buffer(3, &data))
418
    {
8381 terminx 419
        data[0] = WINMM_NOTE_ON | channel;
8215 terminx 420
        data[1] = key;
421
        data[2] = velocity;
422
        midi_sequence_event();
8759 terminx 423
    }
424
    else
425
        MV_Printf("MME_NoteOn error\n");
8215 terminx 426
}
427
 
8759 terminx 428
static void MME_PolyAftertouch(int channel, int key, int pressure)
8215 terminx 429
{
8759 terminx 430
    unsigned char *data;
8215 terminx 431
 
8759 terminx 432
    if (midi_get_buffer(3, &data))
433
    {
8381 terminx 434
        data[0] = WINMM_POLY_AFTER_TCH | channel;
8215 terminx 435
        data[1] = key;
436
        data[2] = pressure;
437
        midi_sequence_event();
8759 terminx 438
    }
439
    else
440
        MV_Printf("MME_PolyAftertouch error\n");
8215 terminx 441
}
442
 
8759 terminx 443
static void MME_ControlChange(int channel, int number, int value)
8215 terminx 444
{
8759 terminx 445
    unsigned char *data;
8215 terminx 446
 
8759 terminx 447
    if (midi_get_buffer(3, &data))
448
    {
8381 terminx 449
        data[0] = WINMM_CONTROL_CHANGE | channel;
8215 terminx 450
        data[1] = number;
451
        data[2] = value;
452
        midi_sequence_event();
8759 terminx 453
    }
454
    else
455
        MV_Printf("MME_ControlChange error\n");
8215 terminx 456
}
457
 
8759 terminx 458
static void MME_ProgramChange(int channel, int program)
8215 terminx 459
{
8759 terminx 460
    unsigned char *data;
8215 terminx 461
 
8759 terminx 462
    if (midi_get_buffer(2, &data))
463
    {
8381 terminx 464
        data[0] = WINMM_PROGRAM_CHANGE | channel;
8215 terminx 465
        data[1] = program;
466
        midi_sequence_event();
8759 terminx 467
    }
468
    else
469
        MV_Printf("MME_ProgramChange error\n");
8215 terminx 470
}
471
 
8759 terminx 472
static void MME_ChannelAftertouch(int channel, int pressure)
8215 terminx 473
{
8759 terminx 474
    unsigned char *data;
8215 terminx 475
 
8759 terminx 476
    if (midi_get_buffer(2, &data))
477
    {
8381 terminx 478
        data[0] = WINMM_AFTER_TOUCH | channel;
8215 terminx 479
        data[1] = pressure;
480
        midi_sequence_event();
8759 terminx 481
    }
482
    else
483
        MV_Printf("MME_ChannelAftertouch error\n");
8215 terminx 484
}
485
 
8759 terminx 486
static void MME_PitchBend(int channel, int lsb, int msb)
8215 terminx 487
{
8759 terminx 488
    unsigned char *data;
8215 terminx 489
 
8759 terminx 490
    if (midi_get_buffer(3, &data))
491
    {
8381 terminx 492
        data[0] = WINMM_PITCH_BEND | channel;
8215 terminx 493
        data[1] = lsb;
494
        data[2] = msb;
495
        midi_sequence_event();
8759 terminx 496
    }
497
    else
498
        MV_Printf("MME_PitchBend error\n");
8215 terminx 499
}
500
 
8759 terminx 501
static void MME_SysEx(const unsigned char *data, int length)
8215 terminx 502
{
8759 terminx 503
    unsigned char *wdata;
504
 
505
    if (midi_get_buffer(length, &wdata))
506
    {
507
        Bmemcpy(wdata, data, length);
8215 terminx 508
        midi_sequence_event();
8759 terminx 509
    }
510
    else
511
        MV_Printf("MME_SysEx error\n");
8215 terminx 512
}
513
 
8384 terminx 514
void WinMMDrv_MIDI_PrintDevices(void)
515
{
516
    auto numDevices = (int)midiOutGetNumDevs();
517
    MIDIOUTCAPS midicaps;
518
 
519
    for (int i = -1; i < numDevices; i++)
520
    {
521
        if (!midiOutGetDevCaps(i, &midicaps, sizeof(MIDIOUTCAPS)))
522
            MV_Printf("%d: %s  ", i, midicaps.szPname);
523
    }
8390 terminx 524
 
525
    MV_Printf("\n");
8384 terminx 526
}
527
 
528
int WinMMDrv_MIDI_GetNumDevices(void) { return midiOutGetNumDevs(); }
529
 
8215 terminx 530
int WinMMDrv_MIDI_Init(midifuncs * funcs)
531
{
8759 terminx 532
    if (midiInstalled)
8215 terminx 533
        WinMMDrv_MIDI_Shutdown();
534
 
8759 terminx 535
    Bmemset(funcs, 0, sizeof(midifuncs));
8215 terminx 536
 
8759 terminx 537
    LL_Reset((MidiBuffer *)&activeMidiBuffers, next, prev);
538
    LL_Reset((MidiBuffer *)&spareMidiBuffers, next, prev);
8215 terminx 539
 
8759 terminx 540
    if ((midiMutex = CreateMutex(0, FALSE, 0)) == 0)
541
    {
8215 terminx 542
        ErrorCode = WinMMErr_MIDICreateMutex;
543
        return WinMMErr_Error;
544
    }
545
 
8384 terminx 546
    MIDIOUTCAPS midicaps;
8759 terminx 547
 
8384 terminx 548
    if (WinMM_DeviceID > midiOutGetNumDevs() || midiOutGetDevCaps(WinMM_DeviceID, &midicaps, sizeof(MIDIOUTCAPS)))
8759 terminx 549
        WinMM_DeviceID = MIDI_MAPPER;
8384 terminx 550
 
8759 terminx 551
    if (!midiOutGetDevCaps(WinMM_DeviceID, &midicaps, sizeof(MIDIOUTCAPS)))
552
        MV_Printf(": [%d] %s", WinMM_DeviceID, midicaps.szPname);
553
 
554
    auto rv = midiStreamOpen(&midiStream, &WinMM_DeviceID, 1, (DWORD_PTR)0, (DWORD_PTR)0, CALLBACK_NULL);
555
 
556
    if (rv != MMSYSERR_NOERROR)
557
    {
8215 terminx 558
        CloseHandle(midiMutex);
559
        midiMutex = 0;
560
 
8759 terminx 561
        midi_error(rv, "MME MIDI_Init midiStreamOpen");
8215 terminx 562
        ErrorCode = WinMMErr_MIDIStreamOpen;
563
        return WinMMErr_Error;
564
    }
565
 
8759 terminx 566
    funcs->NoteOff           = MME_NoteOff;
567
    funcs->NoteOn            = MME_NoteOn;
568
    funcs->PolyAftertouch    = MME_PolyAftertouch;
569
    funcs->ControlChange     = MME_ControlChange;
570
    funcs->ProgramChange     = MME_ProgramChange;
571
    funcs->ChannelAftertouch = MME_ChannelAftertouch;
572
    funcs->PitchBend         = MME_PitchBend;
573
    funcs->SysEx             = MME_SysEx;
8215 terminx 574
 
575
    midiInstalled = TRUE;
576
 
577
    return WinMMErr_Ok;
578
}
579
 
580
void WinMMDrv_MIDI_Shutdown(void)
581
{
8759 terminx 582
    if (!midiInstalled)
8215 terminx 583
        return;
584
 
585
    WinMMDrv_MIDI_HaltPlayback();
586
 
8759 terminx 587
    if (midiStream)
588
    {
589
        auto rv = midiStreamClose(midiStream);
590
        if (rv != MMSYSERR_NOERROR)
591
            midi_error(rv, "MME MIDI_Shutdown midiStreamClose");
8215 terminx 592
    }
593
 
8759 terminx 594
    if (midiMutex)
8215 terminx 595
        CloseHandle(midiMutex);
596
 
597
    midiStream = 0;
8759 terminx 598
    midiMutex  = 0;
8215 terminx 599
 
600
    midiInstalled = FALSE;
601
}
602
 
603
static DWORD midi_get_tick(void)
604
{
8759 terminx 605
    MMTIME mmtime = { TIME_TICKS, 0 };
8215 terminx 606
 
8759 terminx 607
    auto rv = midiStreamPosition(midiStream, &mmtime, sizeof(MMTIME));
608
    if (rv != MMSYSERR_NOERROR)
609
    {
610
        midi_error(rv, "MME midi_get_tick midiStreamPosition");
8215 terminx 611
        return 0;
612
    }
613
 
614
    return mmtime.u.ticks;
615
}
616
 
617
static DWORD WINAPI midiDataThread(LPVOID lpParameter)
618
{
8218 terminx 619
    UNREFERENCED_PARAMETER(lpParameter);
620
 
8759 terminx 621
    midiThreadTimer      = midi_get_tick();
622
    midiLastEventTime    = midiThreadTimer;
8215 terminx 623
    midiThreadQueueTimer = midiThreadTimer + midiThreadQueueTicks;
624
 
625
    WinMMDrv_MIDI_Lock();
626
    midi_gc_buffers();
8381 terminx 627
    while (midiThreadTimer < midiThreadQueueTimer)
628
    {
629
        midiThreadService();
8215 terminx 630
        midiThreadTimer++;
631
    }
632
    midi_flush_current_buffer();
633
    WinMMDrv_MIDI_Unlock();
634
 
8759 terminx 635
    DWORD sleepAmount = 100 / MME_THREAD_QUEUE_INTERVAL;
636
 
637
    do
638
    {
639
        auto waitret = WaitForSingleObject(midiThreadQuitEvent, sleepAmount);
640
 
641
        if (waitret == WAIT_OBJECT_0)
8215 terminx 642
            break;
8759 terminx 643
        else if (waitret == WAIT_TIMEOUT)
644
        {
8215 terminx 645
            // queue a tick
8759 terminx 646
            auto sequenceTime = midi_get_tick();
8215 terminx 647
 
8759 terminx 648
            sleepAmount = 100 / MME_THREAD_QUEUE_INTERVAL;
649
            if ((midiThreadTimer - sequenceTime) > midiThreadQueueTicks)
650
            {
8215 terminx 651
                // we're running ahead, so sleep for half the usual
652
                // amount and try again
653
                sleepAmount /= 2;
654
                continue;
655
            }
656
 
657
            midiThreadQueueTimer = sequenceTime + midiThreadQueueTicks;
658
 
659
            WinMMDrv_MIDI_Lock();
660
            midi_gc_buffers();
8381 terminx 661
            while (midiThreadTimer < midiThreadQueueTimer)
662
            {
663
                midiThreadService();
8215 terminx 664
                midiThreadTimer++;
665
            }
666
            midi_flush_current_buffer();
667
            WinMMDrv_MIDI_Unlock();
668
        }
8759 terminx 669
        else
670
            MV_Printf("MME midiDataThread: wfmo err %d\n", (int)waitret);
8215 terminx 671
    } while (1);
672
 
673
    return 0;
674
}
675
 
8381 terminx 676
int WinMMDrv_MIDI_StartPlayback(void)
8215 terminx 677
{
678
    WinMMDrv_MIDI_HaltPlayback();
8381 terminx 679
    midiThreadService = WinMMDrv_MIDI_Service;
8215 terminx 680
 
8219 terminx 681
    midiThreadQuitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
8759 terminx 682
    if (!midiThreadQuitEvent)
683
    {
8215 terminx 684
        ErrorCode = WinMMErr_MIDICreateEvent;
685
        return WinMMErr_Error;
686
    }
687
 
8759 terminx 688
    if (!midiStreamRunning)
689
    {
690
        auto rv = midiStreamRestart(midiStream);
691
        if (rv != MMSYSERR_NOERROR)
692
        {
8215 terminx 693
            midi_error(rv, "MIDI_StartPlayback midiStreamRestart");
694
            WinMMDrv_MIDI_HaltPlayback();
695
            ErrorCode = WinMMErr_MIDIStreamRestart;
696
            return WinMMErr_Error;
697
        }
698
 
699
        midiStreamRunning = TRUE;
700
    }
701
 
8219 terminx 702
    midiThread = CreateThread(nullptr, 0, midiDataThread, 0, 0, 0);
8759 terminx 703
    if (!midiThread)
704
    {
8215 terminx 705
        WinMMDrv_MIDI_HaltPlayback();
706
        ErrorCode = WinMMErr_MIDIPlayThread;
707
        return WinMMErr_Error;
708
    }
709
 
8236 terminx 710
    midiLastDivision = 0;
711
 
8215 terminx 712
    return WinMMErr_Ok;
713
}
714
 
715
void WinMMDrv_MIDI_HaltPlayback(void)
716
{
8759 terminx 717
    if (midiThread)
718
    {
8215 terminx 719
        SetEvent(midiThreadQuitEvent);
720
 
721
        WaitForSingleObject(midiThread, INFINITE);
8759 terminx 722
        // MV_Printf("MME MIDI_HaltPlayback synched\n");
8215 terminx 723
 
724
        CloseHandle(midiThread);
725
    }
726
 
8759 terminx 727
    if (midiThreadQuitEvent)
8215 terminx 728
        CloseHandle(midiThreadQuitEvent);
8759 terminx 729
 
730
    if (midiStreamRunning)
731
    {
8219 terminx 732
        // MV_Printf("stopping stream\n");
8759 terminx 733
        auto rv = midiStreamStop(midiStream);
734
        if (rv != MMSYSERR_NOERROR)
735
            midi_error(rv, "MME MIDI_HaltPlayback midiStreamStop");
8219 terminx 736
        // MV_Printf("stream stopped\n");
8759 terminx 737
 
8215 terminx 738
        midiStreamRunning = FALSE;
739
    }
740
 
741
    midi_free_buffers();
8759 terminx 742
 
743
    midiThread          = 0;
8215 terminx 744
    midiThreadQuitEvent = 0;
745
}
746
 
747
void WinMMDrv_MIDI_SetTempo(int tempo, int division)
748
{
8759 terminx 749
    BOOL const running = midiStreamRunning;
8215 terminx 750
 
751
    //MV_Printf("MIDI_SetTempo %d/%d\n", tempo, division);
8759 terminx 752
    MIDIPROPTEMPO   propTempo   = { sizeof(MIDIPROPTEMPO), (DWORD)(60000000l / tempo) };
753
    MIDIPROPTIMEDIV propTimediv = { sizeof(MIDIPROPTIMEDIV), (DWORD)division };
8215 terminx 754
 
8759 terminx 755
    if (midiLastDivision != division)
756
    {
8215 terminx 757
        // changing the division means halting the stream
758
        WinMMDrv_MIDI_HaltPlayback();
759
 
8759 terminx 760
        auto rv = midiStreamProperty(midiStream, (LPBYTE)&propTimediv, MIDIPROP_SET | MIDIPROP_TIMEDIV);
761
        if (rv != MMSYSERR_NOERROR)
762
            midi_error(rv, "MME MIDI_SetTempo midiStreamProperty timediv");
8215 terminx 763
    }
764
 
8759 terminx 765
    auto rv = midiStreamProperty(midiStream, (LPBYTE)&propTempo, MIDIPROP_SET | MIDIPROP_TEMPO);
766
    if (rv != MMSYSERR_NOERROR)
767
        midi_error(rv, "MME MIDI_SetTempo midiStreamProperty tempo");
8215 terminx 768
 
8759 terminx 769
    if (midiLastDivision != division)
770
    {
771
        if (running && WinMMDrv_MIDI_StartPlayback() != WinMMErr_Ok)
8215 terminx 772
            return;
773
 
774
        midiLastDivision = division;
775
    }
776
 
8759 terminx 777
    midiThreadQueueTicks = (int)ceil((((double)tempo * (double)division) / 60.0) / (double)MME_THREAD_QUEUE_INTERVAL);
778
    if (midiThreadQueueTicks <= 0)
8215 terminx 779
        midiThreadQueueTicks = 1;
780
}
781
 
782
void WinMMDrv_MIDI_Lock(void)
783
{
8759 terminx 784
    DWORD err = WaitForSingleObject(midiMutex, INFINITE);
785
    if (err != WAIT_OBJECT_0)
786
        MV_Printf("MME midiMutex lock: wfso %d\n", (int) err);
8215 terminx 787
}
788
 
8757 terminx 789
void WinMMDrv_MIDI_Unlock(void)  { ReleaseMutex(midiMutex); }
8381 terminx 790
void WinMMDrv_MIDI_Service(void) { MIDI_ServiceRoutine(); }