1 /**
2 Minuit
3 Copyright (c) 2018 Enalye
4 
5 This software is provided 'as-is', without any express or implied warranty.
6 In no event will the authors be held liable for any damages arising
7 from the use of this software.
8 
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute
11 it freely, subject to the following restrictions:
12 
13 	1. The origin of this software must not be misrepresented;
14 	   you must not claim that you wrote the original software.
15 	   If you use this software in a product, an acknowledgment
16 	   in the product documentation would be appreciated but
17 	   is not required.
18 
19 	2. Altered source versions must be plainly marked as such,
20 	   and must not be misrepresented as being the original software.
21 
22 	3. This notice may not be removed or altered from any source distribution.
23 */
24 
25 module minuit.device.alsa;
26 
27 version(linux) {
28 import core.sys.posix.sys.ioctl;
29 import core.sys.posix.unistd;
30 import core.sys.posix.fcntl;
31 import core.stdc.stdlib;
32 import core.stdc.string;
33 import core.stdc.stdio;
34 import core.stdc.errno;
35 import core.thread;
36 import std.conv;
37 import core.sync.mutex;
38 import std.string: toStringz, fromStringz;
39 
40 import minuit.common;
41 
42 private enum MnInputBufferSize = 512;
43 
44 /**
45  * A single midi output port.
46  *
47  * It cannot be instantiated.
48  * Fetch them using mnFetchOutputs instead.
49  *
50  * See_Also:
51  *	MnInputPort, mnFetchOutputs
52  */
53 final class MnOutputPort {
54 	private {
55 		int _card = 0, _device = 0, _sub = 0;
56 		string _deviceName;
57 		string _name;
58 	}
59 	///User-friendly name of the port.
60 	@property {
61 		string name() const { return _name; }
62 	}
63 
64 	private this() {}
65 }
66 
67 /**
68  * A single midi input port.
69  *
70  * It cannot be instantiated.
71  * Fetch them using mnFetchInputs instead.
72  *
73  * See_Also:
74  *	MnInputPort, mnFetchInputs
75  */
76 final class MnInputPort {
77 	private {
78 		int _card = 0, _device = 0, _sub = 0;
79 		string _deviceName;
80 		string _name;
81 	}
82 	///User-friendly name of the port.
83 	@property {
84 		string name() const { return _name; }
85 	}
86 
87 	private this() {}
88 }
89 
90 /**
91  * Handle of an output port.
92  *
93  * See_Also:
94  *	MnOutputPort, mnOpenOutput
95  */
96 final class MnOutputHandle {
97 	private {
98 		snd_rawmidi_t* _handle;
99 		Mutex _mutex;
100 		MnOutputPort _port;
101 	}
102 	
103 	@property {
104 		///The port associated with this handle.
105 		MnOutputPort port() { return _port; }
106 	}
107 
108 	private this() {
109 		_mutex = new Mutex;
110 	}
111 }
112 
113 /**
114  * Handle of an input port.
115  *
116  * See_Also:
117  *	MnInputPort, mnOpenInput
118  */
119 final class MnInputHandle {
120 	private {
121 		snd_rawmidi_t* _handle;
122 		ubyte[MnInputBufferSize] _buffer;
123 		ushort _pread, _pwrite, _size;
124 		Mutex _mutex;
125 		MnInputPort _port;
126 		MnInputListener _listener;
127 		shared bool _isPooling = true;
128 	}
129 	
130 	@property {
131 		///The port associated with this handle.
132 		MnInputPort port() { return _port; }
133 	}
134 
135 	private this() {
136 		_mutex = new Mutex;
137 	}
138 }
139 
140 private final class MnInputListener: Thread {
141 	private {
142 		MnInputHandle _handle;
143 	}
144 
145 	this(MnInputHandle handle) {
146 		_handle = handle;
147 		super(&run);
148 	}
149 
150 	private void run() {
151 		while(_handle._isPooling) {
152 			ubyte value;
153 			const size_t nbReceived = snd_rawmidi_read(_handle._handle, &value, 1u);
154 			if(nbReceived == 1u) {
155 				push(value);
156 			}
157 		}
158 	}
159 
160 	private void push(ubyte value) {
161 		synchronized(_handle._mutex) {
162 			if(_handle._size == MnInputBufferSize) {
163 				//If full, we replace old data.
164 				_handle._buffer[_handle._pwrite] = value;
165 				_handle._pwrite = (_handle._pwrite + 1u) & (MnInputBufferSize - 1);
166 				_handle._pread = (_handle._pread + 1u) & (MnInputBufferSize - 1);
167 			}
168 			else {
169 				_handle._buffer[_handle._pwrite] = value;
170 				_handle._pwrite = (_handle._pwrite + 1u) & (MnInputBufferSize - 1);
171 				_handle._size ++;
172 			}
173 		}
174 	}
175 }
176 
177 MnOutputPort[] mnFetchOutputs() {
178 	int status;
179 	int card = -1;  // use -1 to prime the pump of iterating through card list
180 
181 	MnOutputPort[] midiDevices;
182 
183 	if((status = snd_card_next(&card)) < 0) {
184 		printf("cannot determine card number: %s", snd_strerror(status));
185 		return midiDevices;
186 	}
187 
188 	//No card found
189 	if(card < 0) {
190 		printf("no soundcards found");
191 		return midiDevices;
192 	}
193 
194 	//List soundcards
195 	while(card >= 0) {
196 		midiDevices ~= _mnListOutputDevices(card);
197 		if((status = snd_card_next(&card)) < 0) {
198 			printf("cannot determine card number: %s", snd_strerror(status));
199 			break;
200 		}
201 	}
202 	return midiDevices;
203 }
204 
205 
206 MnInputPort[] mnFetchInputs() {
207 	int status;
208 	int card = -1;  // use -1 to prime the pump of iterating through card list
209 
210 	MnInputPort[] midiDevices;
211 
212 	if((status = snd_card_next(&card)) < 0) {
213 		printf("cannot determine card number: %s", snd_strerror(status));
214 		return midiDevices;
215 	}
216 
217 	//No card found
218 	if(card < 0) {
219 		printf("no soundcards found");
220 		return midiDevices;
221 	}
222 
223 	//List soundcards
224 	while(card >= 0) {
225 		midiDevices ~= _mnListInputDevices(card);
226 		if((status = snd_card_next(&card)) < 0) {
227 			printf("cannot determine card number: %s", snd_strerror(status));
228 			break;
229 		}
230 	}
231 	return midiDevices;
232 }
233 
234 MnOutputHandle mnOpenOutput(MnOutputPort port) {
235 	snd_rawmidi_t* handle;
236 	if(snd_rawmidi_open(null, &handle, toStringz(port._deviceName), 0) != 0)
237 		return null;
238 
239 	MnOutputHandle midiOutHandle = new MnOutputHandle;
240 	midiOutHandle._handle = handle;
241 	midiOutHandle._port = port;
242 	return midiOutHandle;
243 }
244 
245 private enum {
246 	SND_RAWMIDI_APPEND = 1,
247 	SND_RAWMIDI_NONBLOCK = 2,
248 	SND_RAWMIDI_SYNC = 4
249 }
250 
251 MnInputHandle mnOpenInput(MnInputPort port) {
252 	snd_rawmidi_t* handle;
253 	if(snd_rawmidi_open(&handle, null, toStringz(port._deviceName), SND_RAWMIDI_NONBLOCK) != 0)
254 		return null;
255 
256 	MnInputHandle midiInHandle = new MnInputHandle;
257 	midiInHandle._handle = handle;
258 	midiInHandle._port = port;
259 	midiInHandle._listener = new MnInputListener(midiInHandle);
260 	midiInHandle._listener.start();
261 	return midiInHandle;
262 }
263 
264 void mnCloseOutput(MnOutputHandle handle) {
265 	if(!handle)
266 		return;
267 	synchronized(handle._mutex) {
268 		snd_rawmidi_close(handle._handle);
269 	}
270 }
271 
272 void mnCloseInput(MnInputHandle handle) {
273 	if(!handle)
274 		return;
275 	synchronized(handle._mutex) {
276 		handle._isPooling = false;
277 	}
278 	handle._listener.join();
279 	synchronized(handle._mutex) {
280 		snd_rawmidi_close(handle._handle);
281 	}
282 }
283 
284 void mnSendOutput(MnOutputHandle handle, ubyte a) {
285 	ubyte[1] data;
286 	data[0] = a;
287 	mnSendOutput(handle, data);
288 }
289 
290 void mnSendOutput(MnOutputHandle handle, ubyte a, byte b) {
291 	ubyte[2] data;
292 	data[0] = a;
293 	data[1] = b;
294 	mnSendOutput(handle, data);
295 }
296 
297 void mnSendOutput(MnOutputHandle handle, ubyte a, ubyte b, ubyte c) {
298 	ubyte[3] data;
299 	data[0] = a;
300 	data[1] = b;
301 	data[2] = c;
302 	mnSendOutput(handle, data);
303 }
304 
305 void mnSendOutput(MnOutputHandle handle, ubyte a, ubyte b, ubyte c, ubyte d) {
306 	ubyte[4] data;
307 	data[0] = a;
308 	data[1] = b;
309 	data[2] = c;
310 	data[3] = d;
311 	mnSendOutput(handle, data);
312 }
313 
314 void mnSendOutput(MnOutputHandle handle, const(ubyte)[] data) {
315 	if(!handle)
316 		return;
317 	synchronized(handle._mutex) {
318 		while(data.length) {
319 			size_t size = snd_rawmidi_write(handle._handle, data.ptr, data.length);
320 			if(size < 0)
321 				throw new Exception("Error writting to midi port");
322 			data = data[size .. $];
323 		}
324 		snd_rawmidi_drain(handle._handle);
325 	}
326 }
327 
328 private ubyte _mnPeek(MnInputHandle handle, ushort offset) {
329 	return handle._buffer[(handle._pread + offset) & (MnInputBufferSize - 1)];
330 }
331 
332 private void _mnConsume(MnInputHandle handle, ushort count) {
333 	handle._pread = (handle._pread + count) & (MnInputBufferSize - 1);
334 	handle._size -= count;
335 }
336 
337 private void _mnCleanup(MnInputHandle handle) {
338 	while(handle._size) {
339 		const ubyte status = _mnPeek(handle, 0u);
340 		if((status & 0x80) != 0u)
341 			return;
342 		_mnConsume(handle, 1u);
343 	}
344 }
345 
346 ubyte[] mnReceiveInput(MnInputHandle handle) {
347 	if(!handle)
348 		return [];
349 	ubyte[] data;
350 	synchronized(handle._mutex) {
351 		_mnCleanup(handle);
352 		if(handle._size != 0u) {
353 			const ubyte status = _mnPeek(handle, 0u);
354 			data ~= status;
355 			//Fill SysEx message
356 			if(status == 0xF0) {
357 				for(ushort i = 1u; i < handle._size; i ++) {
358 					const ushort value = _mnPeek(handle, i);
359 					data ~= value;
360 					if(value == 0xF7)
361 						break;
362 				}
363 				//Incomplete SysEx
364 				if(data[$ - 1] != 0xF7)
365 					return [];
366 			}
367 			//Common messages
368 			else {
369 				const int messageDataCount = mnGetDataBytesCountByStatus(status);
370 				if(messageDataCount > handle._size)
371 					return [];
372 				
373 				for(ushort i = 1u; i <= messageDataCount; i ++)
374 					data ~= _mnPeek(handle, i);
375 			}
376 		}
377 	}
378 	_mnConsume(handle, cast(ushort)data.length);
379 	return data;
380 }
381 
382 bool mnCanReceiveInput(MnInputHandle handle) {
383 	if(!handle)
384 		return false;
385 	bool isNotEmpty;
386 	synchronized(handle._mutex) {
387 		_mnCleanup(handle);
388 		if(handle._size != 0u) {
389 			const ubyte status = _mnPeek(handle, 0u);
390 			//Check SysEx validity
391 			if(status == 0xF0) {
392 				for(ushort i = 1; i < handle._size; i ++) {
393 					if(_mnPeek(handle, i) == 0xF7) {
394 						isNotEmpty = true;
395 						break;
396 					}
397 				}
398 			}
399 			//Common messages
400 			else {
401 				const int messageDataCount = mnGetDataBytesCountByStatus(status);
402 				if((messageDataCount + 1) <= handle._size)
403 					isNotEmpty = true;
404 			}
405 		}
406 	}
407 	return isNotEmpty;
408 }
409 
410 private MnOutputPort _mnCreateOutputDevice(int card, int device, int subdevice, char* name) {
411 	MnOutputPort midiDevice = new MnOutputPort;
412 	midiDevice._card = card;
413 	midiDevice._device = device;
414 	midiDevice._sub = subdevice;
415 	midiDevice._deviceName = "hw:" ~ to!string(card) ~ "," ~ to!string(device) ~ "," ~ to!string(subdevice);
416 	midiDevice._name = to!string(name);
417 	return midiDevice;
418 }
419 
420 private MnInputPort _mnCreateInputDevice(int card, int device, int subdevice, char* name) {
421 	MnInputPort midiDevice = new MnInputPort;
422 	midiDevice._card = card;
423 	midiDevice._device = device;
424 	midiDevice._sub = subdevice;
425 	midiDevice._deviceName = "hw:" ~ to!string(card) ~ "," ~ to!string(device) ~ "," ~ to!string(subdevice);
426 	midiDevice._name = to!string(name);
427 	return midiDevice;
428 }
429 
430 private enum snd_rawmidi_stream_t {
431 	SND_RAWMIDI_STREAM_OUTPUT = 0,
432 	SND_RAWMIDI_STREAM_INPUT = 1
433 }
434 
435 private MnOutputPort[] _mnListOutputDevices(int card) {
436 	MnOutputPort[] midiDevices;
437 	snd_ctl_t* ctl;
438 	char[32] name;
439 	int device = -1;
440 	int status;
441 	sprintf(cast(char*)name, "hw:%d", card);
442 	if ((status = snd_ctl_open(&ctl, cast(immutable(char)*)name, 0)) < 0) { //FIXME: CRASH
443 		printf("cannot open control for card %d: %s", card, snd_strerror(status));
444 		return midiDevices;
445 	}
446 	do {
447 		status = snd_ctl_rawmidi_next_device(ctl, &device);
448 		if (status < 0) {
449 			printf("cannot determine device number: ", snd_strerror(status));
450 			break;
451 		}
452 		if (device >= 0) {
453 			_mnListSubDevicesInfo(&midiDevices, null, ctl, card, device);
454 		}
455 	} while (device >= 0);
456 	snd_ctl_close(ctl);
457 
458 	return midiDevices;
459 }
460 
461 private MnInputPort[] _mnListInputDevices(int card) {
462 	MnInputPort[] midiDevices;
463 	snd_ctl_t* ctl;
464 	char[32] name;
465 	int device = -1;
466 	int status;
467 	sprintf(cast(char*)name, "hw:%d", card);
468 	if ((status = snd_ctl_open(&ctl, cast(immutable(char)*)name, 0)) < 0) { //FIXME: CRASH
469 		printf("cannot open control for card %d: %s", card, snd_strerror(status));
470 		return midiDevices;
471 	}
472 	do {
473 		status = snd_ctl_rawmidi_next_device(ctl, &device);
474 		if (status < 0) {
475 			printf("cannot determine device number: ", snd_strerror(status));
476 			break;
477 		}
478 		if (device >= 0) {
479 			_mnListSubDevicesInfo(null, &midiDevices, ctl, card, device);
480 		}
481 	} while (device >= 0);
482 	snd_ctl_close(ctl);
483 
484 	return midiDevices;
485 }
486 
487 private void _mnListSubDevicesInfo(
488 	MnOutputPort[]* outPorts,
489 	MnInputPort[]* inPorts,
490 	snd_ctl_t *ctl,
491 	int card,
492 	int device) {
493 	snd_rawmidi_info_t* info;
494 	char[32] name, sub_name;
495 	int subs, subs_in, subs_out;
496 	int sub, ina, outa;
497 	int status;
498 
499 	_mnDeviceInfoAlloca(&info);
500 	snd_rawmidi_info_set_device(info, device);
501 
502 	snd_rawmidi_info_set_stream(info, snd_rawmidi_stream_t.SND_RAWMIDI_STREAM_INPUT);
503 	snd_ctl_rawmidi_info(ctl, info);
504 	subs_in = snd_rawmidi_info_get_subdevices_count(info);
505 	snd_rawmidi_info_set_stream(info, snd_rawmidi_stream_t.SND_RAWMIDI_STREAM_OUTPUT);
506 	snd_ctl_rawmidi_info(ctl, info);
507 	subs_out = snd_rawmidi_info_get_subdevices_count(info);
508 	subs = subs_in > subs_out ? subs_in : subs_out;
509 
510 	sub = 0;
511 	ina = outa = 0;
512 	if ((status = _mnIsOutput(ctl, card, device, sub)) < 0) {
513 		printf("cannot get rawmidi information %d:%d: %s",
514 			card, device, snd_strerror(status));
515 		return;
516 	} else if (status)
517 		outa = 1;
518 
519 	if (status == 0) {
520 		if ((status = _mnIsInput(ctl, card, device, sub)) < 0) {
521 		 printf("cannot get rawmidi information %d:%d: %s",
522 				card, device, snd_strerror(status));
523 		 return;
524 		}
525 	} else if (status) 
526 		ina = 1;
527 
528 	if (status == 0)
529 		return;
530 
531 	auto namePtr = snd_rawmidi_info_get_name(info);
532 	auto len = strlen(namePtr);
533 	if(len)
534 		strncpy(name.ptr, namePtr, len);
535 	foreach(i; 0.. 32) {
536 		if(name[i] >= 0x20 && name[i] < 0x7F)
537 			continue;
538 		name[i] = '\0';
539 	}
540 
541 	namePtr = snd_rawmidi_info_get_subdevice_name(info);
542 	len = strlen(namePtr);
543 	if(len)
544 		strncpy(sub_name.ptr, namePtr, len);
545 	foreach(i; 0.. 32) {
546 		if(sub_name[i] >= 0x20 && sub_name[i] < 0x7F)
547 			continue;
548 		sub_name[i] = '\0';
549 	}
550 
551 	sub = 0;
552 	for (;;) {
553 		ina = _mnIsInput(ctl, card, device, sub);
554 		outa = _mnIsOutput(ctl, card, device, sub);
555 		snd_rawmidi_info_set_subdevice(info, sub);
556 		if (outa) {
557 			snd_rawmidi_info_set_stream(info, snd_rawmidi_stream_t.SND_RAWMIDI_STREAM_OUTPUT);
558 			if ((status = snd_ctl_rawmidi_info(ctl, info)) < 0) {
559 				printf("cannot get rawmidi information hw:%d,%d,%d : %s",
560 					card, device, sub, snd_strerror(status));
561 				break;
562 			} 
563 		} else {
564 			snd_rawmidi_info_set_stream(info, snd_rawmidi_stream_t.SND_RAWMIDI_STREAM_INPUT);
565 			if ((status = snd_ctl_rawmidi_info(ctl, info)) < 0) {
566 				printf("cannot get rawmidi information hw:%d,%d,%d : %s",
567 					card, device, sub, snd_strerror(status));
568 				break;
569 			}
570  		}
571 		namePtr = snd_rawmidi_info_get_subdevice_name(info);
572 		len = strlen(namePtr);
573 		if(len)
574 			memcpy(sub_name.ptr, namePtr, len);
575 		
576 		foreach(i; 0.. 32) {
577 			if(sub_name[i] >= 0x20 && sub_name[i] < 0x7F)
578 				continue;
579 			sub_name[i] = '\0';
580 		}
581 
582 		if(outa && outPorts) {
583 			*outPorts ~= _mnCreateOutputDevice(card, device, sub, sub_name.ptr);
584 		}
585 		if(ina && inPorts) {
586 			*inPorts ~= _mnCreateInputDevice(card, device, sub, sub_name.ptr);
587 		}
588 		if (++sub >= subs)
589 			break;
590 	}
591 }
592 
593 private int _mnIsInput(snd_ctl_t* ctl, int, int device, int sub) {
594 	snd_rawmidi_info_t *info;
595 	int status;
596 
597 	_mnDeviceInfoAlloca(&info);
598 	snd_rawmidi_info_set_device(info, device);
599 	snd_rawmidi_info_set_subdevice(info, sub);
600 	snd_rawmidi_info_set_stream(info, snd_rawmidi_stream_t.SND_RAWMIDI_STREAM_INPUT);
601 	
602 	if ((status = snd_ctl_rawmidi_info(ctl, info)) < 0 && status != -ENXIO)
603 		return status;
604 	else if (status == 0)
605 		return 1;
606 	return 0;
607 }
608 
609 private int _mnIsOutput(snd_ctl_t* ctl, int, int device, int sub) {
610 	snd_rawmidi_info_t *info;
611 	int status;
612 
613 	_mnDeviceInfoAlloca(&info);
614 	snd_rawmidi_info_set_device(info, device);
615 	snd_rawmidi_info_set_subdevice(info, sub);
616 	snd_rawmidi_info_set_stream(info, snd_rawmidi_stream_t.SND_RAWMIDI_STREAM_OUTPUT);
617 	
618 	if ((status = snd_ctl_rawmidi_info(ctl, info)) < 0 && status != -ENXIO)
619 		return status;
620 	else if (status == 0)
621 		return 1;
622 	return 0;
623 }
624 
625 private void _mnDeviceInfoAlloca(snd_rawmidi_info_t** ptr) {
626 	*ptr = cast(snd_rawmidi_info_t*) alloca(snd_rawmidi_info_sizeof()); memset(*ptr, 0, snd_rawmidi_info_sizeof());
627 }
628 
629 //ALSA Bindings
630 private:
631 extern(C):
632 @nogc nothrow:
633 
634 struct snd_rawmidi_t {}
635 struct snd_ctl_t {}
636 struct snd_rawmidi_info_t {}
637 
638 char* snd_strerror(int);
639 
640 int snd_rawmidi_open(snd_rawmidi_t**, snd_rawmidi_t**, const char*, int);
641 int snd_rawmidi_close(snd_rawmidi_t*);
642 int snd_rawmidi_drain(snd_rawmidi_t*);
643 size_t snd_rawmidi_write(snd_rawmidi_t*, const void*, size_t);
644 size_t snd_rawmidi_read(snd_rawmidi_t*, void*, size_t);
645 
646 int snd_card_next(int*);
647 
648 int snd_ctl_open(snd_ctl_t**, immutable(char)*, int);
649 int snd_ctl_close(snd_ctl_t*);
650 int snd_ctl_rawmidi_next_device(snd_ctl_t*, int*);
651 
652 void snd_rawmidi_info_set_device(snd_rawmidi_info_t*, uint);
653 void snd_rawmidi_info_set_stream(snd_rawmidi_info_t*, snd_rawmidi_stream_t);
654 int snd_ctl_rawmidi_info(snd_ctl_t*, snd_rawmidi_info_t*);
655 uint snd_rawmidi_info_get_subdevices_count(const snd_rawmidi_info_t*);
656 char* snd_rawmidi_info_get_name(const snd_rawmidi_info_t*);
657 char* snd_rawmidi_info_get_subdevice_name(const snd_rawmidi_info_t*);
658 void snd_rawmidi_info_set_subdevice(snd_rawmidi_info_t*, uint);
659 
660 size_t snd_rawmidi_info_sizeof();
661 }