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