1 /*
2 Copyright (C) 2011 Devin Anderson
3 
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.
8 
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.
13 
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.
17 
18 */
19 
20 #include <cassert>
21 #include <memory>
22 #include <stdexcept>
23 
24 #include "JackError.h"
25 #include "JackTime.h"
26 #include "JackMidiUtil.h"
27 #include "JackWinMMEInputPort.h"
28 #include "JackMidiWriteQueue.h"
29 
30 using Jack::JackWinMMEInputPort;
31 
32 ///////////////////////////////////////////////////////////////////////////////
33 // Static callbacks
34 ///////////////////////////////////////////////////////////////////////////////
35 
36 void CALLBACK
HandleMidiInputEvent(HMIDIIN handle,UINT message,DWORD port,DWORD param1,DWORD param2)37 JackWinMMEInputPort::HandleMidiInputEvent(HMIDIIN handle, UINT message,
38                                           DWORD port, DWORD param1,
39                                           DWORD param2)
40 {
41     ((JackWinMMEInputPort *) port)->ProcessWinMME(message, param1, param2);
42 }
43 
44 ///////////////////////////////////////////////////////////////////////////////
45 // Class
46 ///////////////////////////////////////////////////////////////////////////////
47 
JackWinMMEInputPort(const char * alias_name,const char * client_name,const char * driver_name,UINT index,size_t max_bytes,size_t max_messages)48 JackWinMMEInputPort::JackWinMMEInputPort(const char *alias_name,
49                                          const char *client_name,
50                                          const char *driver_name, UINT index,
51                                          size_t max_bytes, size_t max_messages)
52 {
53     thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages);
54     std::unique_ptr<JackMidiAsyncQueue> thread_queue_ptr(thread_queue);
55     write_queue = new JackMidiBufferWriteQueue();
56     std::unique_ptr<JackMidiBufferWriteQueue> write_queue_ptr(write_queue);
57     sysex_buffer = new jack_midi_data_t[max_bytes];
58     char error_message[MAXERRORLENGTH];
59     MMRESULT result = midiInOpen(&handle, index,
60                                  (DWORD_PTR) HandleMidiInputEvent,
61                                  (DWORD_PTR)this,
62                                  CALLBACK_FUNCTION | MIDI_IO_STATUS);
63     if (result != MMSYSERR_NOERROR) {
64         GetInErrorString(result, error_message);
65         goto delete_sysex_buffer;
66     }
67     sysex_header.dwBufferLength = max_bytes;
68     sysex_header.dwFlags = 0;
69     sysex_header.lpData = (LPSTR)sysex_buffer;
70     result = midiInPrepareHeader(handle, &sysex_header, sizeof(MIDIHDR));
71     if (result != MMSYSERR_NOERROR) {
72         GetInErrorString(result, error_message);
73         goto close_handle;
74     }
75     result = midiInAddBuffer(handle, &sysex_header, sizeof(MIDIHDR));
76     if (result != MMSYSERR_NOERROR) {
77         GetInErrorString(result, error_message);
78         goto unprepare_header;
79     }
80 
81     MIDIINCAPS capabilities;
82     char *name_tmp;
83     result = midiInGetDevCaps(index, &capabilities, sizeof(capabilities));
84     if (result != MMSYSERR_NOERROR) {
85         WriteInError("JackWinMMEInputPort [constructor]", "midiInGetDevCaps",
86                    result);
87         name_tmp = (char*) driver_name;
88     } else {
89         name_tmp = capabilities.szPname;
90     }
91 
92     snprintf(alias, sizeof(alias) - 1, "%s:%s:in%d", alias_name, name_tmp,
93              index + 1);
94     snprintf(name, sizeof(name) - 1, "%s:capture_%d", client_name, index + 1);
95     strncpy(device_name, name_tmp, sizeof(device_name) - 1);
96     jack_event = 0;
97     started = false;
98     write_queue_ptr.release();
99     thread_queue_ptr.release();
100     return;
101 
102  unprepare_header:
103     result = midiInUnprepareHeader(handle, &sysex_header, sizeof(MIDIHDR));
104     if (result != MMSYSERR_NOERROR) {
105         WriteInError("JackWinMMEInputPort [constructor]",
106                      "midiInUnprepareHeader", result);
107     }
108  close_handle:
109     result = midiInClose(handle);
110     if (result != MMSYSERR_NOERROR) {
111         WriteInError("JackWinMMEInputPort [constructor]", "midiInClose",
112                      result);
113     }
114  delete_sysex_buffer:
115     delete[] sysex_buffer;
116     throw std::runtime_error(error_message);
117 }
118 
~JackWinMMEInputPort()119 JackWinMMEInputPort::~JackWinMMEInputPort()
120 {
121     MMRESULT result = midiInReset(handle);
122     if (result != MMSYSERR_NOERROR) {
123         WriteInError("JackWinMMEInputPort [destructor]", "midiInReset", result);
124     }
125     result = midiInUnprepareHeader(handle, &sysex_header, sizeof(MIDIHDR));
126     if (result != MMSYSERR_NOERROR) {
127         WriteInError("JackWinMMEInputPort [destructor]",
128                      "midiInUnprepareHeader", result);
129     }
130     result = midiInClose(handle);
131     if (result != MMSYSERR_NOERROR) {
132         WriteInError("JackWinMMEInputPort [destructor]", "midiInClose", result);
133     }
134     delete[] sysex_buffer;
135     delete thread_queue;
136     delete write_queue;
137 }
138 
139 void
EnqueueMessage(DWORD timestamp,size_t length,jack_midi_data_t * data)140 JackWinMMEInputPort::EnqueueMessage(DWORD timestamp, size_t length,
141                                     jack_midi_data_t *data)
142 {
143     jack_nframes_t frame =
144         GetFramesFromTime(start_time + (((jack_time_t) timestamp) * 1000));
145 
146     // Debugging code
147     jack_time_t current_time = GetMicroSeconds();
148     jack_log("JackWinMMEInputPort::EnqueueMessage - enqueueing event at %f "
149              "(frame: %d) with start offset '%d' scheduled for frame '%d'",
150              ((double) current_time) / 1000.0, GetFramesFromTime(current_time),
151              timestamp, frame);
152     // End debugging code
153 
154     switch (thread_queue->EnqueueEvent(frame, length, data)) {
155     case JackMidiWriteQueue::BUFFER_FULL:
156         jack_error("JackWinMMEInputPort::EnqueueMessage - The thread queue "
157                    "cannot currently accept a %d-byte event.  Dropping event.",
158                    length);
159         break;
160     case JackMidiWriteQueue::BUFFER_TOO_SMALL:
161         jack_error("JackWinMMEInputPort::EnqueueMessage - The thread queue "
162                    "buffer is too small to enqueue a %d-byte event.  Dropping "
163                    "event.", length);
164         break;
165     default:
166         ;
167     }
168 }
169 
170 void
GetInErrorString(MMRESULT error,LPTSTR text)171 JackWinMMEInputPort::GetInErrorString(MMRESULT error, LPTSTR text)
172 {
173     MMRESULT result = midiInGetErrorText(error, text, MAXERRORLENGTH);
174     if (result != MMSYSERR_NOERROR) {
175         snprintf(text, MAXERRORLENGTH, "Unknown error code '%d'", error);
176     }
177 }
178 
179 void
ProcessJack(JackMidiBuffer * port_buffer,jack_nframes_t frames)180 JackWinMMEInputPort::ProcessJack(JackMidiBuffer *port_buffer,
181                                  jack_nframes_t frames)
182 {
183     write_queue->ResetMidiBuffer(port_buffer, frames);
184     if (! jack_event) {
185         jack_event = thread_queue->DequeueEvent();
186     }
187     for (; jack_event; jack_event = thread_queue->DequeueEvent()) {
188         switch (write_queue->EnqueueEvent(jack_event, frames)) {
189         case JackMidiWriteQueue::BUFFER_TOO_SMALL:
190             jack_error("JackWinMMEMidiInputPort::Process - The buffer write "
191                        "queue couldn't enqueue a %d-byte event. Dropping "
192                        "event.", jack_event->size);
193             // Fallthrough on purpose
194         case JackMidiWriteQueue::OK:
195             continue;
196         default:
197             break;
198         }
199         break;
200     }
201 }
202 
203 void
ProcessWinMME(UINT message,DWORD param1,DWORD param2)204 JackWinMMEInputPort::ProcessWinMME(UINT message, DWORD param1, DWORD param2)
205 {
206     set_threaded_log_function();
207     switch (message) {
208     case MIM_CLOSE:
209         jack_log("JackWinMMEInputPort::ProcessWinMME - MIDI device closed.");
210         break;
211     case MIM_MOREDATA:
212         jack_log("JackWinMMEInputPort::ProcessWinMME - The MIDI input device "
213                   "driver thinks that JACK is not processing messages fast "
214                   "enough.");
215         // Fallthrough on purpose.
216     case MIM_DATA: {
217         jack_midi_data_t message_buffer[3];
218         jack_midi_data_t status = param1 & 0xff;
219         int length = GetMessageLength(status);
220 
221         switch (length) {
222         case 3:
223              message_buffer[2] = (param1 >> 16)  & 0xff;
224             // Fallthrough on purpose.
225         case 2:
226             message_buffer[1] = (param1 >> 8) & 0xff;
227             // Fallthrough on purpose.
228         case 1:
229             message_buffer[0] = status;
230             break;
231         case 0:
232             jack_error("JackWinMMEInputPort::ProcessWinMME - **BUG** MIDI "
233                        "input driver sent an MIM_DATA message with a sysex "
234                        "status byte.");
235             return;
236         case -1:
237             jack_error("JackWinMMEInputPort::ProcessWinMME - **BUG** MIDI "
238                        "input driver sent an MIM_DATA message with an invalid "
239                        "status byte.");
240             return;
241         }
242         EnqueueMessage(param2, (size_t) length, message_buffer);
243         break;
244     }
245     case MIM_LONGDATA: {
246         LPMIDIHDR header = (LPMIDIHDR) param1;
247         size_t byte_count = header->dwBytesRecorded;
248         if (! byte_count) {
249             jack_log("JackWinMMEInputPort::ProcessWinMME - WinMME driver has "
250                       "returned sysex header to us with no bytes.  The JACK "
251                       "driver is probably being stopped.");
252             break;
253         }
254         jack_midi_data_t *data = (jack_midi_data_t *) header->lpData;
255         if ((data[0] != 0xf0) || (data[byte_count - 1] != 0xf7)) {
256             jack_error("JackWinMMEInputPort::ProcessWinMME - Discarding "
257                        "%d-byte sysex chunk.", byte_count);
258         } else {
259             EnqueueMessage(param2, byte_count, data);
260         }
261         // Is this realtime-safe?  This function isn't run in the JACK thread,
262         // but we still want it to perform as quickly as possible.  Even if
263         // this isn't realtime safe, it may not be avoidable.
264         MMRESULT result = midiInAddBuffer(handle, &sysex_header,
265                                           sizeof(MIDIHDR));
266         if (result != MMSYSERR_NOERROR) {
267             WriteInError("JackWinMMEInputPort::ProcessWinMME",
268                          "midiInAddBuffer", result);
269         }
270         break;
271     }
272     case MIM_LONGERROR:
273         jack_error("JackWinMMEInputPort::ProcessWinMME - Invalid or "
274                    "incomplete sysex message received.");
275         break;
276     case MIM_OPEN:
277         jack_log("JackWinMMEInputPort::ProcessWinMME - MIDI device opened.");
278     }
279 }
280 
281 bool
Start()282 JackWinMMEInputPort::Start()
283 {
284     if (! started) {
285         start_time = GetMicroSeconds();
286         MMRESULT result = midiInStart(handle);
287         started = result == MMSYSERR_NOERROR;
288         if (! started) {
289             WriteInError("JackWinMMEInputPort::Start", "midiInStart", result);
290         }
291     }
292     return started;
293 }
294 
295 bool
Stop()296 JackWinMMEInputPort::Stop()
297 {
298     if (started) {
299         MMRESULT result = midiInStop(handle);
300         started = result != MMSYSERR_NOERROR;
301         if (started) {
302             WriteInError("JackWinMMEInputPort::Stop", "midiInStop", result);
303         }
304     }
305     return ! started;
306 }
307 
308 void
WriteInError(const char * jack_func,const char * mm_func,MMRESULT result)309 JackWinMMEInputPort::WriteInError(const char *jack_func, const char *mm_func,
310                                 MMRESULT result)
311 {
312     char error_message[MAXERRORLENGTH];
313     GetInErrorString(result, error_message);
314     jack_error("%s - %s: %s", jack_func, mm_func, error_message);
315 }
316