1/*
2 * Copyright (c) 1999, 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26#define USE_ERROR
27#define USE_TRACE
28
29/* include Java Sound specific headers as C code */
30extern "C" {
31#include "PLATFORM_API_WinOS_Util.h"
32}
33
34#if USE_PLATFORM_MIDI_IN == TRUE
35
36#ifdef USE_ERROR
37#include <stdio.h>
38
39/* Use THIS_FILE when it is available. */
40#ifndef THIS_FILE
41    #define THIS_FILE __FILE__
42#endif
43
44#define MIDIIN_CHECK_ERROR { \
45        if (err != MMSYSERR_NOERROR) \
46            ERROR3("MIDI IN Error in %s:%d : %s\n", THIS_FILE, __LINE__, MIDI_IN_GetErrorStr((INT32) err)); \
47    }
48#else
49#define MIDIIN_CHECK_ERROR
50#endif
51
52/*
53 * Callback from the MIDI device for all messages.
54 */
55//$$fb dwParam1 holds a pointer for long messages. How can that be a DWORD then ???
56void CALLBACK MIDI_IN_PutMessage( HMIDIIN hMidiIn, UINT wMsg, UINT_PTR dwInstance, UINT_PTR dwParam1, UINT_PTR dwParam2 ) {
57
58    MidiDeviceHandle* handle = (MidiDeviceHandle*) dwInstance;
59
60    TRACE3("> MIDI_IN_PutMessage, hMidiIn: %x, wMsg: %x, dwInstance: %x\n", hMidiIn, wMsg, dwInstance);
61    TRACE2("                      dwParam1: %x, dwParam2: %x\n", dwParam1, dwParam2);
62
63    switch(wMsg) {
64
65    case MIM_OPEN:
66        TRACE0("< MIDI_IN_PutMessage: MIM_OPEN\n");
67        break;
68
69    case MIM_CLOSE:
70        TRACE0("< MIDI_IN_PutMessage: MIM_CLOSE\n");
71        break;
72
73    case MIM_MOREDATA:
74    case MIM_DATA:
75        TRACE3("  MIDI_IN_PutMessage: MIM_MOREDATA or MIM_DATA. status=%x  data1=%x  data2=%x\n",
76               dwParam1 & 0xFF, (dwParam1 & 0xFF00)>>8, (dwParam1 & 0xFF0000)>>16);
77        if (handle!=NULL && handle->queue!=NULL && handle->platformData) {
78            MIDI_QueueAddShort(handle->queue,
79                               // queue stores packedMsg in big endian
80                               //(dwParam1 << 24) | ((dwParam1 << 8) & 0xFF0000) | ((dwParam1 >> 8) & 0xFF00),
81                               (UINT32) dwParam1,
82                               // queue uses microseconds
83                               ((INT64) dwParam2)*1000,
84                               // overwrite if queue is full
85                               TRUE);
86            SetEvent((HANDLE) handle->platformData);
87        }
88        TRACE0("< MIDI_IN_PutMessage\n");
89        break;
90
91    case MIM_LONGDATA:
92        TRACE1("  MIDI_IN_PutMessage: MIM_LONGDATA (%d bytes recorded)\n", (int) (((MIDIHDR*) dwParam1)->dwBytesRecorded));
93        if (handle!=NULL && handle->queue!=NULL && handle->platformData) {
94            MIDIHDR* hdr = (MIDIHDR*) dwParam1;
95            TRACE2("  MIDI_IN_PutMessage: Adding to queue: index %d, %d bytes\n", (INT32) hdr->dwUser, hdr->dwBytesRecorded);
96            MIDI_QueueAddLong(handle->queue,
97                              (UBYTE*) hdr->lpData,
98                              (UINT32) hdr->dwBytesRecorded,
99                              // sysex buffer index
100                              (INT32) hdr->dwUser,
101                              // queue uses microseconds
102                              ((INT64) dwParam2)*1000,
103                              // overwrite if queue is full
104                              TRUE);
105            SetEvent((HANDLE) handle->platformData);
106        }
107        TRACE0("< MIDI_IN_PutMessage\n");
108        break;
109
110    case MIM_ERROR:
111        ERROR0("< MIDI_IN_PutMessage: MIM_ERROR!\n");
112        break;
113
114    case MIM_LONGERROR:
115        if (dwParam1 != 0) {
116            MIDIHDR* hdr = (MIDIHDR*) dwParam1;
117#ifdef USE_TRACE
118            if (hdr->dwBytesRecorded > 0) {
119                TRACE2("  MIDI_IN_PutMessage: MIM_LONGERROR! recorded: %d bytes with status 0x%2x\n",
120                        hdr->dwBytesRecorded, (int) (*((UBYTE*) hdr->lpData)));
121            }
122#endif
123            // re-add hdr to device query
124            hdr->dwBytesRecorded = 0;
125            midiInAddBuffer((HMIDIIN)handle->deviceHandle, hdr, sizeof(MIDIHDR));
126        }
127        ERROR0("< MIDI_IN_PutMessage: MIM_LONGERROR!\n");
128        break;
129
130    default:
131        ERROR1("< MIDI_IN_PutMessage: ERROR unknown message %d!\n", wMsg);
132        break;
133
134    } // switch (wMsg)
135}
136
137
138/*
139** data/routines for opening MIDI input (MidiIn) device by separate thread
140** (joint into MidiIn_OpenHelper class)
141** see 6415669 - MidiIn device stops work and crushes JVM after exiting
142** from thread that has open the device (it looks like WinMM bug).
143*/
144class MidiIn_OpenHelper {
145public:
146    /* opens MidiIn device  */
147    static MMRESULT midiInOpen(INT32 deviceID, MidiDeviceHandle* handle);
148    /* checks for initialization success */
149    static inline BOOL isInitialized() { return data.threadHandle != NULL; }
150protected:
151    MidiIn_OpenHelper() {}  // no need to create an instance
152
153    /* data class */
154    class Data {
155    public:
156        Data();
157        ~Data();
158        // public data to access from parent class
159        CRITICAL_SECTION crit_sect;
160        volatile HANDLE threadHandle;
161        volatile HANDLE doEvent;    // event to resume thread
162        volatile HANDLE doneEvent;  // processing has been completed
163        volatile MMRESULT err;      // processing result
164        // data to process; (handle == null) is command to thread terminating
165        volatile INT32 deviceID;
166        volatile MidiDeviceHandle* handle;
167    } static data;
168
169    /* StartThread function */
170    static DWORD WINAPI __stdcall ThreadProc(void *param);
171};
172
173/* MidiIn_OpenHelper class implementation
174*/
175MidiIn_OpenHelper::Data MidiIn_OpenHelper::data;
176
177MidiIn_OpenHelper::Data::Data() {
178    threadHandle = NULL;
179    ::InitializeCriticalSection(&crit_sect);
180    doEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
181    doneEvent = ::CreateEvent(NULL, FALSE, FALSE, NULL);
182    if (doEvent != NULL && doneEvent != NULL)
183        threadHandle = ::CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
184}
185
186MidiIn_OpenHelper::Data::~Data() {
187    ::EnterCriticalSection(&crit_sect);
188    if (threadHandle != NULL) {
189        // terminate thread
190        handle = NULL;
191        ::SetEvent(doEvent);
192        ::CloseHandle(threadHandle);
193        threadHandle = NULL;
194    }
195    ::LeaveCriticalSection(&crit_sect);
196    // won't delete doEvent/doneEvent/crit_sect
197    // - Windows will do during process shutdown
198}
199
200DWORD WINAPI __stdcall MidiIn_OpenHelper::ThreadProc(void *param) {
201    while (1) {
202        // wait for something to do
203        ::WaitForSingleObject(data.doEvent, INFINITE);
204        if (data.handle == NULL) {
205            // (data.handle == NULL) is a signal to terminate thread
206            break;
207        }
208
209        data.err = ::midiInOpen((HMIDIIN*)&(data.handle->deviceHandle),
210                                data.deviceID, (UINT_PTR)&(MIDI_IN_PutMessage),
211                                (UINT_PTR)data.handle,
212                                CALLBACK_FUNCTION|MIDI_IO_STATUS);
213
214        ::SetEvent(data.doneEvent);
215    }
216    return 0;
217}
218
219MMRESULT MidiIn_OpenHelper::midiInOpen(INT32 deviceID, MidiDeviceHandle* handle) {
220    MMRESULT err;
221    ::EnterCriticalSection(&data.crit_sect);
222    if (!isInitialized()) {
223        ::LeaveCriticalSection(&data.crit_sect);
224        return MMSYSERR_ERROR;
225    }
226    data.deviceID = deviceID;
227    data.handle = handle;
228    ::SetEvent(data.doEvent);
229    ::WaitForSingleObject(data.doneEvent, INFINITE);
230    err = data.err;
231    ::LeaveCriticalSection(&data.crit_sect);
232    return err;
233}
234
235
236// PLATFORM_MIDI_IN method implementations
237
238/* not thread safe */
239static char winMidiInErrMsg[WIN_MAX_ERROR_LEN];
240
241char* MIDI_IN_GetErrorStr(INT32 err) {
242    winMidiInErrMsg[0] = 0;
243    midiInGetErrorText((MMRESULT) err, winMidiInErrMsg, WIN_MAX_ERROR_LEN);
244    return winMidiInErrMsg;
245}
246
247INT32 MIDI_IN_GetNumDevices() {
248    return (INT32) midiInGetNumDevs();
249}
250
251INT32 getMidiInCaps(INT32 deviceID, MIDIINCAPS* caps, INT32* err) {
252    (*err) = midiInGetDevCaps(deviceID, caps, sizeof(MIDIINCAPS));
253    return ((*err) == MMSYSERR_NOERROR);
254}
255
256INT32 MIDI_IN_GetDeviceName(INT32 deviceID, char *name, UINT32 nameLength) {
257    MIDIINCAPS midiInCaps;
258    INT32 err;
259
260    if (getMidiInCaps(deviceID, &midiInCaps, &err)) {
261        strncpy(name, midiInCaps.szPname, nameLength-1);
262        name[nameLength-1] = 0;
263        return MIDI_SUCCESS;
264    }
265    MIDIIN_CHECK_ERROR;
266    return err;
267}
268
269
270INT32 MIDI_IN_GetDeviceVendor(INT32 deviceID, char *name, UINT32 nameLength) {
271    return MIDI_NOT_SUPPORTED;
272}
273
274
275INT32 MIDI_IN_GetDeviceDescription(INT32 deviceID, char *name, UINT32 nameLength) {
276    return MIDI_NOT_SUPPORTED;
277}
278
279
280
281INT32 MIDI_IN_GetDeviceVersion(INT32 deviceID, char *name, UINT32 nameLength) {
282    MIDIINCAPS midiInCaps;
283    INT32 err = MIDI_NOT_SUPPORTED;
284
285    if (getMidiInCaps(deviceID, &midiInCaps, &err) && (nameLength>7)) {
286        sprintf(name, "%d.%d", (midiInCaps.vDriverVersion & 0xFF00) >> 8, midiInCaps.vDriverVersion & 0xFF);
287        return MIDI_SUCCESS;
288    }
289    MIDIIN_CHECK_ERROR;
290    return err;
291}
292
293
294INT32 prepareBuffers(MidiDeviceHandle* handle) {
295    SysExQueue* sysex;
296    MMRESULT err = MMSYSERR_NOERROR;
297    int i;
298
299    if (!handle || !handle->longBuffers || !handle->deviceHandle) {
300        ERROR0("MIDI_IN_prepareBuffers: handle, or longBuffers, or deviceHandle==NULL\n");
301        return MIDI_INVALID_HANDLE;
302    }
303    sysex = (SysExQueue*) handle->longBuffers;
304    for (i = 0; i<sysex->count; i++) {
305        MIDIHDR* hdr = &(sysex->header[i]);
306        midiInPrepareHeader((HMIDIIN) handle->deviceHandle, hdr, sizeof(MIDIHDR));
307        err = midiInAddBuffer((HMIDIIN) handle->deviceHandle, hdr, sizeof(MIDIHDR));
308    }
309    MIDIIN_CHECK_ERROR;
310    return (INT32) err;
311}
312
313INT32 unprepareBuffers(MidiDeviceHandle* handle) {
314    SysExQueue* sysex;
315    MMRESULT err = MMSYSERR_NOERROR;
316    int i;
317
318    if (!handle || !handle->longBuffers || !handle->deviceHandle) {
319        ERROR0("MIDI_IN_unprepareBuffers: handle, or longBuffers, or deviceHandle==NULL\n");
320        return MIDI_INVALID_HANDLE;
321    }
322    sysex = (SysExQueue*) handle->longBuffers;
323    for (i = 0; i<sysex->count; i++) {
324        err = midiInUnprepareHeader((HMIDIIN) handle->deviceHandle, &(sysex->header[i]), sizeof(MIDIHDR));
325    }
326    MIDIIN_CHECK_ERROR;
327    return (INT32) err;
328}
329
330INT32 MIDI_IN_OpenDevice(INT32 deviceID, MidiDeviceHandle** handle) {
331    MMRESULT err;
332
333    TRACE0("> MIDI_IN_OpenDevice\n");
334#ifdef USE_ERROR
335    setvbuf(stdout, NULL, (int)_IONBF, 0);
336    setvbuf(stderr, NULL, (int)_IONBF, 0);
337#endif
338
339    (*handle) = (MidiDeviceHandle*) malloc(sizeof(MidiDeviceHandle));
340    if (!(*handle)) {
341        ERROR0("< ERROR: MIDI_IN_OpenDevice: out of memory\n");
342        return MIDI_OUT_OF_MEMORY;
343    }
344    memset(*handle, 0, sizeof(MidiDeviceHandle));
345
346    // create queue
347    (*handle)->queue = MIDI_CreateQueue(MIDI_IN_MESSAGE_QUEUE_SIZE);
348    if (!(*handle)->queue) {
349        ERROR0("< ERROR: MIDI_IN_OpenDevice: could not create queue\n");
350        free(*handle);
351        (*handle) = NULL;
352        return MIDI_OUT_OF_MEMORY;
353    }
354
355    // create long buffer queue
356    if (!MIDI_WinCreateLongBufferQueue(*handle, MIDI_IN_LONG_QUEUE_SIZE, MIDI_IN_LONG_MESSAGE_SIZE, NULL)) {
357        ERROR0("< ERROR: MIDI_IN_OpenDevice: could not create long Buffers\n");
358        MIDI_DestroyQueue((*handle)->queue);
359        free(*handle);
360        (*handle) = NULL;
361        return MIDI_OUT_OF_MEMORY;
362    }
363
364    // finally open the device
365    err = MidiIn_OpenHelper::midiInOpen(deviceID, *handle);
366
367    if ((err != MMSYSERR_NOERROR) || (!(*handle)->deviceHandle)) {
368        MIDIIN_CHECK_ERROR;
369        MIDI_WinDestroyLongBufferQueue(*handle);
370        MIDI_DestroyQueue((*handle)->queue);
371        free(*handle);
372        (*handle) = NULL;
373        return (INT32) err;
374    }
375
376    prepareBuffers(*handle);
377        MIDI_SetStartTime(*handle);
378    TRACE0("< MIDI_IN_OpenDevice: midiInOpen succeeded\n");
379    return MIDI_SUCCESS;
380}
381
382
383INT32 MIDI_IN_CloseDevice(MidiDeviceHandle* handle) {
384    MMRESULT err;
385
386    TRACE0("> MIDI_IN_CloseDevice: midiInClose\n");
387    if (!handle) {
388        ERROR0("ERROR: MIDI_IN_CloseDevice: handle is NULL\n");
389        return MIDI_INVALID_HANDLE;
390    }
391    midiInReset((HMIDIIN) handle->deviceHandle);
392    unprepareBuffers(handle);
393    err = midiInClose((HMIDIIN) handle->deviceHandle);
394    handle->deviceHandle=NULL;
395    MIDIIN_CHECK_ERROR;
396    MIDI_WinDestroyLongBufferQueue(handle);
397
398    if (handle->queue!=NULL) {
399        MidiMessageQueue* queue = handle->queue;
400        handle->queue = NULL;
401        MIDI_DestroyQueue(queue);
402    }
403    free(handle);
404
405    TRACE0("< MIDI_IN_CloseDevice: midiInClose succeeded\n");
406    return (INT32) err;
407}
408
409
410INT32 MIDI_IN_StartDevice(MidiDeviceHandle* handle) {
411    MMRESULT err;
412
413    if (!handle || !handle->deviceHandle || !handle->queue) {
414        ERROR0("ERROR: MIDI_IN_StartDevice: handle or queue is NULL\n");
415        return MIDI_INVALID_HANDLE;
416    }
417
418    // clear all the events from the queue
419    MIDI_QueueClear(handle->queue);
420
421    handle->platformData = (void*) CreateEvent(NULL, FALSE /*manual reset*/, FALSE /*signaled*/, NULL);
422    if (!handle->platformData) {
423        ERROR0("ERROR: MIDI_IN_StartDevice: could not create event\n");
424        return MIDI_OUT_OF_MEMORY;
425    }
426
427    err = midiInStart((HMIDIIN) handle->deviceHandle);
428        /* $$mp 200308-11: This method is already called in ...open(). It is
429           unclear why it is called again. The specification says that
430           MidiDevice.getMicrosecondPosition() returns the time since the
431           device was opened (the spec doesn't know about start/stop).
432           So I guess this call is obsolete. */
433        MIDI_SetStartTime(handle);
434
435    MIDIIN_CHECK_ERROR;
436    TRACE0("MIDI_IN_StartDevice: midiInStart finished\n");
437    return (INT32) err;
438}
439
440
441INT32 MIDI_IN_StopDevice(MidiDeviceHandle* handle) {
442    MMRESULT err;
443    HANDLE event;
444
445    TRACE0("> MIDI_IN_StopDevice: midiInStop \n");
446    if (!handle || !handle->platformData) {
447        ERROR0("ERROR: MIDI_IN_StopDevice: handle or event is NULL\n");
448        return MIDI_INVALID_HANDLE;
449    }
450    // encourage MIDI_IN_GetMessage to return soon
451    event = handle->platformData;
452    handle->platformData = NULL;
453    SetEvent(event);
454
455    err = midiInStop((HMIDIIN) handle->deviceHandle);
456
457    // wait until the Java thread has exited
458    while (handle->isWaiting) Sleep(0);
459    CloseHandle(event);
460
461    MIDIIN_CHECK_ERROR;
462    TRACE0("< MIDI_IN_StopDevice: midiInStop finished\n");
463    return (INT32) err;
464}
465
466
467/* return time stamp in microseconds */
468INT64 MIDI_IN_GetTimeStamp(MidiDeviceHandle* handle) {
469        return MIDI_GetTimeStamp(handle);
470}
471
472
473// read the next message from the queue
474MidiMessage* MIDI_IN_GetMessage(MidiDeviceHandle* handle) {
475    if (handle == NULL) {
476        return NULL;
477    }
478    while (handle->queue!=NULL && handle->platformData!=NULL) {
479        MidiMessage* msg = MIDI_QueueRead(handle->queue);
480        DWORD res;
481        if (msg != NULL) {
482            //fprintf(stdout, "GetMessage returns index %d\n", msg->data.l.index); fflush(stdout);
483            return msg;
484        }
485        TRACE0("MIDI_IN_GetMessage: before waiting\n");
486        handle->isWaiting = TRUE;
487        res = WaitForSingleObject((HANDLE) handle->platformData, 2000);
488        handle->isWaiting = FALSE;
489        if (res == WAIT_TIMEOUT) {
490            // break out back to Java from time to time - just to be sure
491            TRACE0("MIDI_IN_GetMessage: waiting finished with timeout\n");
492            break;
493        }
494        TRACE0("MIDI_IN_GetMessage: waiting finished\n");
495    }
496    return NULL;
497}
498
499void MIDI_IN_ReleaseMessage(MidiDeviceHandle* handle, MidiMessage* msg) {
500    SysExQueue* sysex;
501    if (handle == NULL || handle->queue == NULL) {
502        return;
503    }
504    sysex = (SysExQueue*) handle->longBuffers;
505    if (msg->type == LONG_MESSAGE && sysex) {
506        MIDIHDR* hdr = &(sysex->header[msg->data.l.index]);
507        //fprintf(stdout, "ReleaseMessage index %d\n", msg->data.l.index); fflush(stdout);
508        hdr->dwBytesRecorded = 0;
509        midiInAddBuffer((HMIDIIN) handle->deviceHandle, hdr, sizeof(MIDIHDR));
510    }
511    MIDI_QueueRemove(handle->queue, TRUE /*onlyLocked*/);
512}
513
514#endif // USE_PLATFORM_MIDI_IN
515