1 /** 
2  * Copyright: Enalye
3  * License: Zlib
4  * Authors: Enalye
5  */
6 module minuit.device.winmm;
7 
8 version(Windows) {
9 import std.conv: to;
10 import core.sys.windows.mmsystem, core.sys.windows.basetsd;
11 import core.stdc.string;
12 import core.sync.mutex, core.sync.semaphore;
13 
14 import minuit.common;
15 
16 private enum MnInputBufferSize = 512;
17 
18 /**
19  * A single midi output port.
20  *
21  * It cannot be instantiated.
22  * Fetch them using mnFetchOutputs instead.
23  *
24  * See_Also:
25  *	MnInputPort, mnFetchOutputs
26  */
27 final class MnOutputPort {
28 	private {
29 		uint _id;
30 		string _name;
31 	}
32 
33 	@property {
34 		///User-friendly name of the port.
35 		string name() const { return _name; }
36 	}
37 
38 	private this() {}
39 }
40 
41 /**
42  * A single midi input port.
43  *
44  * It cannot be instantiated.
45  * Fetch them using mnFetchInputs instead.
46  *
47  * See_Also:
48  *	MnOutputPort, mnFetchOutputs
49  */
50 final class MnInputPort {
51 	private {
52 		uint _id;
53 		string _name;
54 	}
55 
56 	@property {
57 		///User-friendly name of the port.
58 		string name() const { return _name; }
59 	}
60 
61 	private this() {}
62 }
63 
64 /**
65  * Handle of an output port.
66  *
67  * See_Also:
68  *	MnOutputPort, mnOpenOutput
69  */
70 final class MnOutputHandle {
71 	private {
72 		HMIDIOUT _handle;
73 		MnOutputPort _port;
74 		Mutex _mutex;
75 	}
76 
77 	@property {
78 		///The port associated with this handle.
79 		MnOutputPort port() { return _port; }
80 	}
81 
82 	private this() {
83 		_mutex = new Mutex;
84 	}
85 }
86 
87 /**
88  * Handle of an input port.
89  *
90  * See_Also:
91  *	MnInputPort, mnOpenInput
92  */
93 final class MnInputHandle {
94 	private {
95 		HMIDIIN _handle;
96 		ubyte[MnInputBufferSize] _buffer;
97 		ushort _pread, _pwrite, _size;
98 		Mutex _mutex;
99 		MnInputPort _port;
100 	}
101 
102 	@property {
103 		///The port associated with this handle.
104 		MnInputPort port() { return _port; }
105 	}
106 
107 	private this() {
108 		_mutex = new Mutex;
109 	}
110 }
111 
112 private union MnWord {
113 	uint word;
114 	ubyte[4] bytes;
115 }
116 
117 MnOutputPort[] mnFetchOutputs() {
118 	wchar[MAXPNAMELEN] name;
119 	uint midiPort;
120 	const maxDevs = midiOutGetNumDevs();
121 	MnOutputPort[] midiDevices;
122 
123 	for(; midiPort < maxDevs; midiPort++) {
124 		MnOutputPort midiDevice = new MnOutputPort;
125 		midiDevice._id = midiPort;
126 
127 		MIDIOUTCAPS midiOutCaps;
128 		midiOutGetDevCaps(midiPort, &midiOutCaps, midiOutCaps.sizeof);
129 
130 		int len;
131 		foreach(i; 0.. MAXPNAMELEN) {
132 			if(midiOutCaps.szPname[i] >= 0x20 && midiOutCaps.szPname[i] < 0x7F) {
133 				name[i] = midiOutCaps.szPname[i];
134 				continue;
135 			}
136 			len = i;
137 			break;
138 		}
139 		midiDevice._name = to!string(name);
140 		midiDevice._name.length = len;
141 		midiDevices ~= midiDevice;
142 	}
143 	return midiDevices;
144 }
145 
146 MnInputPort[] mnFetchInputs() {
147 	wchar[MAXPNAMELEN] name;
148 	uint midiPort;
149 	const maxDevs = midiInGetNumDevs();
150 	MnInputPort[] midiDevices;
151 
152 	for(; midiPort < maxDevs; midiPort++) {
153 		MnInputPort midiDevice = new MnInputPort;
154 		midiDevice._id = midiPort;
155 
156 		MIDIINCAPS midiInCaps;
157 		midiInGetDevCaps(midiPort, &midiInCaps, midiInCaps.sizeof);
158 
159 		int len;
160 		foreach(i; 0.. MAXPNAMELEN) {
161 			if(midiInCaps.szPname[i] >= 0x20 && midiInCaps.szPname[i] < 0x7F) {
162 				name[i] = midiInCaps.szPname[i];
163 				continue;
164 			}
165 			len = i;
166 			break;
167 		}
168 		midiDevice._name = to!string(name);
169 		midiDevice._name.length = len;
170 		midiDevices ~= midiDevice;
171 	}
172 	return midiDevices;
173 }
174 
175 MnOutputHandle mnOpenOutput(MnOutputPort port) {
176 	if(!port)
177 		return null;
178 
179 	if(port._id >= midiOutGetNumDevs())
180 		return null;
181 
182 	HMIDIOUT handle;
183 	const flag = midiOutOpen(&handle, port._id, 0, 0, CALLBACK_NULL);
184 	if(flag != MMSYSERR_NOERROR)
185 		return null;
186 
187 	MnOutputHandle midiOutHandle = new MnOutputHandle;
188 	midiOutHandle._handle = handle;
189 	midiOutHandle._port = port;
190 	
191 	return midiOutHandle;
192 }
193 
194 MnInputHandle mnOpenInput(MnInputPort port) {
195 	if(!port)
196 		return null;
197 
198 	if(port._id >= midiInGetNumDevs())
199 		return null;
200 	
201 	MnInputHandle midiInHandle = new MnInputHandle;
202 	HMIDIIN handle;
203 	const flag = midiInOpen(
204 		&handle,
205 		port._id,
206 		cast(DWORD_PTR)(&_mnListen),
207 		cast(DWORD_PTR)cast(void*)midiInHandle,
208 		CALLBACK_FUNCTION);
209 	
210 	if(flag != MMSYSERR_NOERROR)
211 		return null;
212 	
213 	midiInHandle._handle = handle;
214 	midiInHandle._port = port;
215 	
216 	if(midiInStart(handle) != MMSYSERR_NOERROR)
217 		return null;
218 	
219 	return midiInHandle;
220 }
221 
222 extern(Windows) private void _mnListen(HMIDIIN, uint msg, DWORD_PTR dwHandle, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
223 	if(!dwHandle)
224 		return;
225 	
226 	version(X86_64) {
227 		MnInputHandle handle = cast(MnInputHandle)(cast(void*)dwHandle);
228 	}
229 	else version(X86) {
230 		MnInputHandle handle = cast(MnInputHandle)(cast(void*)(dwHandle));
231 	}
232 
233 	if (msg != MIM_DATA) return;
234 	
235 	MnWord msgWord;
236 	msgWord.word = cast(uint) dwParam1;
237 	cast(void) dwParam2; // dwParam2 = timestamp in milliseconds since device open, unused here
238 
239 	const uint dataBytesCount = mnGetDataBytesCountByStatus(msgWord.bytes[0]);
240 	if(dataBytesCount >= 3)
241 		return;
242 	for(ushort i; i <= dataBytesCount; i ++)
243 		_mnPush(handle, msgWord.bytes[i]);
244 }
245 
246 private void _mnPush(MnInputHandle handle, ubyte value) {
247 	synchronized(handle._mutex) {
248 		if(handle._size == MnInputBufferSize) {
249 			//If full, we replace old data.
250 			handle._buffer[handle._pwrite] = value;
251 			handle._pwrite = (handle._pwrite + 1u) & (MnInputBufferSize - 1);
252 			handle._pread = (handle._pread + 1u) & (MnInputBufferSize - 1);
253 		}
254 		else {
255 			handle._buffer[handle._pwrite] = value;
256 			handle._pwrite = (handle._pwrite + 1u) & (MnInputBufferSize - 1);
257 			handle._size ++;
258 		}
259 	}
260 }
261 
262 void mnCloseOutput(MnOutputHandle handle) {
263 	if(!handle)
264 		return;
265 	synchronized(handle._mutex) {
266 		midiOutReset(handle._handle);
267 		midiOutClose(handle._handle);
268 	}
269 }
270 
271 void mnCloseInput(MnInputHandle handle) {
272 	if(!handle)
273 		return;
274 	synchronized(handle._mutex) {
275 		midiInStop(handle._handle);
276 
277 		handle._mutex.unlock();
278 		handle._size = 0u;
279 		handle._pread = 0u;
280 		handle._pwrite = 0u;
281 
282 		midiInReset(handle._handle);
283 		midiInClose(handle._handle);
284 	}
285 }
286 
287 void mnSendOutput(MnOutputHandle handle, ubyte a) {
288 	if(!handle)
289 		return;
290 	synchronized(handle._mutex) {
291 		MnWord midiWord;
292 		midiWord.bytes[0] = a;
293 		midiOutShortMsg(handle._handle, midiWord.word);
294 	}
295 }
296 
297 void mnSendOutput(MnOutputHandle handle, ubyte a, byte b) {
298 	if(!handle)
299 		return;
300 	synchronized(handle._mutex) {
301 		MnWord midiWord;
302 		midiWord.bytes[0] = a;
303 		midiWord.bytes[1] = b;
304 		midiOutShortMsg(handle._handle, midiWord.word);
305 	}
306 }
307 
308 void mnSendOutput(MnOutputHandle handle, ubyte a, ubyte b, ubyte c) {
309 	if(!handle)
310 		return;
311 	synchronized(handle._mutex) {
312 		MnWord midiWord;
313 		midiWord.bytes[0] = a;
314 		midiWord.bytes[1] = b;
315 		midiWord.bytes[2] = c;
316 		midiOutShortMsg(handle._handle, midiWord.word);
317 	}
318 }
319 
320 void mnSendOutput(MnOutputHandle handle, ubyte a, ubyte b, ubyte c, ubyte d) {
321 	if(!handle)
322 		return;
323 	synchronized(handle._mutex) {
324 		MnWord midiWord;
325 		midiWord.bytes[0] = a;
326 		midiWord.bytes[1] = b;
327 		midiWord.bytes[2] = c;
328 		midiWord.bytes[3] = d;
329 		midiOutShortMsg(handle._handle, midiWord.word);
330 	}
331 }
332 
333 private void _mnSendOutput(MnOutputHandle handle, MnWord midiWord) {
334 	if(!handle)
335 		return;
336 	synchronized(handle._mutex) {
337 		midiOutShortMsg(handle._handle, midiWord.word);
338 	}
339 }
340 
341 void mnSendOutput(MnOutputHandle handle, const(ubyte)[] data) {
342 	if(!handle)
343 		return;
344 	synchronized(handle._mutex) {
345 		MIDIHDR midiHeader;
346 		midiHeader.lpData = cast(char*)data;
347 		midiHeader.dwBufferLength = cast(uint)data.length;
348 		midiHeader.dwFlags = 0;
349 		midiOutPrepareHeader(handle._handle, &midiHeader, midiHeader.sizeof);
350 		midiOutLongMsg(handle._handle, &midiHeader, midiHeader.sizeof);
351 		midiOutUnprepareHeader(handle._handle, &midiHeader, midiHeader.sizeof);
352 	}
353 }
354 
355 private ubyte _mnPeek(MnInputHandle handle, ushort offset) {
356 	return handle._buffer[(handle._pread + offset) & (MnInputBufferSize - 1)];
357 }
358 
359 private void _mnConsume(MnInputHandle handle, ushort count) {
360 	handle._pread = (handle._pread + count) & (MnInputBufferSize - 1);
361 	handle._size -= count;
362 }
363 
364 private void _mnCleanup(MnInputHandle handle) {
365 	while(handle._size) {
366 		const ubyte status = _mnPeek(handle, 0u);
367 		if((status & 0x80) != 0u)
368 			return;
369 		_mnConsume(handle, 1u);
370 	}
371 }
372 
373 ubyte[] mnReceiveInput(MnInputHandle handle) {
374 	if(!handle)
375 		return [];
376 	ubyte[] data;
377 	synchronized(handle._mutex) {
378 		_mnCleanup(handle);
379 		if(handle._size != 0u) {
380 			const ubyte status = _mnPeek(handle, 0u);
381 			data ~= status;
382 			//Fill SysEx message
383 			if(status == 0xF0) {
384 				for(ushort i = 1u; i < handle._size; i ++) {
385 					const ubyte value = _mnPeek(handle, i);
386 					data ~= value;
387 					if(value == 0xF7)
388 						break;
389 				}
390 				//Incomplete SysEx
391 				if(data[$ - 1] != 0xF7)
392 					return [];
393 			}
394 			//Common messages
395 			else {
396 				const int messageDataCount = mnGetDataBytesCountByStatus(status);
397 				if(messageDataCount > handle._size)
398 					return [];
399 				
400 				for(ushort i = 1u; i <= messageDataCount; i ++)
401 					data ~= _mnPeek(handle, i);
402 			}
403 		}
404 	}
405 	_mnConsume(handle, cast(ushort)data.length);
406 	return data;
407 }
408 
409 bool mnCanReceiveInput(MnInputHandle handle) {
410 	if(!handle)
411 		return false;
412 	bool isNotEmpty;
413 	synchronized(handle._mutex) {
414 		_mnCleanup(handle);
415 		if(handle._size != 0u) {
416 			const ubyte status = _mnPeek(handle, 0u);
417 			//Check SysEx validity
418 			if(status == 0xF0) {
419 				for(ushort i = 1; i < handle._size; i ++) {
420 					if(_mnPeek(handle, i) == 0xF7) {
421 						isNotEmpty = true;
422 						break;
423 					}
424 				}
425 			}
426 			//Common messages
427 			else {
428 				const int messageDataCount = mnGetDataBytesCountByStatus(status);
429 				if((messageDataCount + 1) <= handle._size)
430 					isNotEmpty = true;
431 			}
432 		}
433 	}
434 	return isNotEmpty;
435 }
436 
437 //WINMM Bindings
438 private:
439 extern(C):
440 @nogc nothrow:
441 
442 //MMRESULT _midiInOpen(HMIDIIN, uint, void*, void*, uint);
443 
444 }