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 }