1 /** 2 The purpose of this module is to provide audio functions for 3 things like playback, capture, and volume on both Windows 4 (via the mmsystem calls)and Linux (through ALSA). 5 6 It is only aimed at the basics, and will be filled in as I want 7 a particular feature. I don't generally need super configurability 8 and see it as a minus, since I don't generally care either, so I'm 9 going to be going for defaults that just work. If you need more though, 10 you can hack the source or maybe just use it for the operating system 11 bindings. 12 13 For example, I'm starting this because I want to write a volume 14 control program for my linux box, so that's what is going first. 15 That will consist of a listening callback for volume changes and 16 being able to get/set the volume. 17 18 19 HOW IT WORKS: 20 You make a callback which feeds data to the device. Make an 21 AudioOutput struct then feed your callback to it. Then play. 22 23 Then call loop? Or that could be in play? 24 25 Methods: 26 setCallback 27 play 28 pause 29 30 31 TODO: 32 * play audio high level with options to wait until completion or return immediately 33 * midi mid-level stuff 34 * audio callback stuff (it tells us when to fill the buffer) 35 36 * Windows support for waveOut and waveIn. Maybe mixer too, but that's lowest priority. 37 38 * I'll also write .mid and .wav functions at least eventually. Maybe in separate modules but probably here since they aren't that complex. 39 40 I will probably NOT do OSS anymore, since my computer doesn't even work with it now. 41 Ditto for Macintosh, as I don't have one and don't really care about them. 42 */ 43 module arsd.simpleaudio; 44 45 enum BUFFER_SIZE_FRAMES = 1024;//512;//2048; 46 enum BUFFER_SIZE_SHORT = BUFFER_SIZE_FRAMES * 2; 47 48 /// A reasonable default volume for an individual sample. It doesn't need to be large; in fact it needs to not be large so mixing doesn't clip too much. 49 enum DEFAULT_VOLUME = 20; 50 51 version(Demo) 52 void main() { 53 /+ 54 55 version(none) { 56 import iv.stb.vorbis; 57 58 int channels; 59 short* decoded; 60 auto v = new VorbisDecoder("test.ogg"); 61 62 auto ao = AudioOutput(0); 63 ao.fillData = (short[] buffer) { 64 auto got = v.getSamplesShortInterleaved(2, buffer.ptr, buffer.length); 65 if(got == 0) { 66 ao.stop(); 67 } 68 }; 69 70 ao.play(); 71 return; 72 } 73 74 75 76 77 auto thread = new AudioPcmOutThread(); 78 thread.start(); 79 80 thread.playOgg("test.ogg"); 81 82 Thread.sleep(5.seconds); 83 84 //Thread.sleep(150.msecs); 85 thread.beep(); 86 Thread.sleep(250.msecs); 87 thread.blip(); 88 Thread.sleep(250.msecs); 89 thread.boop(); 90 Thread.sleep(1000.msecs); 91 /* 92 thread.beep(800, 500); 93 Thread.sleep(500.msecs); 94 thread.beep(366, 500); 95 Thread.sleep(600.msecs); 96 thread.beep(800, 500); 97 thread.beep(366, 500); 98 Thread.sleep(500.msecs); 99 Thread.sleep(150.msecs); 100 thread.beep(200); 101 Thread.sleep(150.msecs); 102 thread.beep(100); 103 Thread.sleep(150.msecs); 104 thread.noise(); 105 Thread.sleep(150.msecs); 106 */ 107 108 109 thread.stop(); 110 111 thread.join(); 112 113 return; 114 115 /* 116 auto aio = AudioMixer(0); 117 118 import std.stdio; 119 writeln(aio.muteMaster); 120 */ 121 122 /* 123 mciSendStringA("play test.wav", null, 0, null); 124 Sleep(3000); 125 import std.stdio; 126 if(auto err = mciSendStringA("play test2.wav", null, 0, null)) 127 writeln(err); 128 Sleep(6000); 129 return; 130 */ 131 132 // output about a second of random noise to demo PCM 133 auto ao = AudioOutput(0); 134 short[BUFFER_SIZE_SHORT] randomSpam = void; 135 import core.stdc.stdlib; 136 foreach(ref s; randomSpam) 137 s = cast(short)((cast(short) rand()) - short.max / 2); 138 139 int loopCount = 40; 140 141 //import std.stdio; 142 //writeln("Should be about ", loopCount * BUFFER_SIZE_FRAMES * 1000 / SampleRate, " microseconds"); 143 144 int loops = 0; 145 // only do simple stuff in here like fill the data, set simple 146 // variables, or call stop anything else might cause deadlock 147 ao.fillData = (short[] buffer) { 148 buffer[] = randomSpam[0 .. buffer.length]; 149 loops++; 150 if(loops == loopCount) 151 ao.stop(); 152 }; 153 154 ao.play(); 155 156 return; 157 +/ 158 // Play a C major scale on the piano to demonstrate midi 159 auto midi = MidiOutput(0); 160 161 ubyte[16] buffer = void; 162 ubyte[] where = buffer[]; 163 midi.writeRawMessageData(where.midiProgramChange(1, 1)); 164 for(ubyte note = MidiNote.C; note <= MidiNote.C + 12; note++) { 165 where = buffer[]; 166 midi.writeRawMessageData(where.midiNoteOn(1, note, 127)); 167 import core.thread; 168 Thread.sleep(dur!"msecs"(500)); 169 midi.writeRawMessageData(where.midiNoteOff(1, note, 127)); 170 171 if(note != 76 && note != 83) 172 note++; 173 } 174 import core.thread; 175 Thread.sleep(dur!"msecs"(500)); // give the last note a chance to finish 176 } 177 178 /++ 179 Wraps [AudioPcmOutThreadImplementation] with RAII semantics for better 180 error handling and disposal than the old way. 181 182 DO NOT USE THE `new` OPERATOR ON THIS! Just construct it inline: 183 184 --- 185 auto audio = AudioOutputThread(true); 186 audio.beep(); 187 --- 188 189 History: 190 Added May 9, 2020 to replace the old [AudioPcmOutThread] class 191 that proved pretty difficult to use correctly. 192 +/ 193 struct AudioOutputThread { 194 @disable this(); 195 196 /++ 197 Pass `true` to enable the audio thread. Otherwise, it will 198 just live as a dummy mock object that you should not actually 199 try to use. 200 +/ 201 this(bool enable, int SampleRate = 44100, int channels = 2) { 202 if(enable) { 203 impl = new AudioPcmOutThreadImplementation(SampleRate, channels); 204 impl.refcount++; 205 impl.start(); 206 impl.waitForInitialization(); 207 } 208 } 209 210 /// Keeps an internal refcount. 211 this(this) { 212 if(impl) 213 impl.refcount++; 214 } 215 216 /// When the internal refcount reaches zero, it stops the audio and rejoins the thread, throwing any pending exception (yes the dtor can throw! extremely unlikely though). 217 ~this() { 218 if(impl) { 219 impl.refcount--; 220 if(impl.refcount == 0) { 221 impl.stop(); 222 impl.join(); 223 } 224 } 225 } 226 227 /++ 228 This allows you to check `if(audio)` to see if it is enabled. 229 +/ 230 bool opCast(T : bool)() { 231 return impl !is null; 232 } 233 234 /++ 235 Other methods are forwarded to the implementation of type 236 [AudioPcmOutThreadImplementation]. See that for more information 237 on what you can do. 238 239 This opDispatch template will forward all other methods directly 240 to that [AudioPcmOutThreadImplementation] if this is live, otherwise 241 it does nothing. 242 +/ 243 template opDispatch(string name) { 244 static if(is(typeof(__traits(getMember, impl, name)) Params == __parameters)) 245 auto opDispatch(Params params) { 246 if(impl) 247 return __traits(getMember, impl, name)(params); 248 static if(!is(typeof(return) == void)) 249 return typeof(return).init; 250 } 251 else static assert(0); 252 } 253 254 void playOgg()(string filename, bool loop = false) { 255 if(impl) 256 impl.playOgg(filename, loop); 257 } 258 void playMp3()(string filename) { 259 if(impl) 260 impl.playMp3(filename); 261 } 262 263 264 /// provides automatic [arsd.jsvar] script wrapping capability. Make sure the 265 /// script also finishes before this goes out of scope or it may end up talking 266 /// to a dead object.... 267 auto toArsdJsvar() { 268 return impl; 269 } 270 271 /+ 272 alias getImpl this; 273 AudioPcmOutThreadImplementation getImpl() { 274 assert(impl !is null); 275 return impl; 276 } 277 +/ 278 private AudioPcmOutThreadImplementation impl; 279 } 280 281 /++ 282 Old thread implementation. I decided to deprecate it in favor of [AudioOutputThread] because 283 RAII semantics make it easier to get right at the usage point. See that to go forward. 284 285 History: 286 Deprecated on May 9, 2020. 287 +/ 288 deprecated("Use AudioOutputThread instead.") class AudioPcmOutThread {} 289 290 import core.thread; 291 /++ 292 Makes an audio thread for you that you can make 293 various sounds on and it will mix them with good 294 enough latency for simple games. 295 296 DO NOT USE THIS DIRECTLY. Instead, access it through 297 [AudioOutputThread]. 298 299 --- 300 auto audio = AudioOutputThread(true); 301 audio.beep(); 302 303 // you need to keep the main program alive long enough 304 // to keep this thread going to hear anything 305 Thread.sleep(1.seconds); 306 --- 307 +/ 308 final class AudioPcmOutThreadImplementation : Thread { 309 private this(int SampleRate, int channels) { 310 this.isDaemon = true; 311 312 this.SampleRate = SampleRate; 313 this.channels = channels; 314 315 super(&run); 316 } 317 318 private int SampleRate; 319 private int channels; 320 private int refcount; 321 322 private void waitForInitialization() { 323 shared(AudioOutput*)* ao = cast(shared(AudioOutput*)*) &this.ao; 324 while(isRunning && *ao is null) { 325 Thread.sleep(5.msecs); 326 } 327 328 if(*ao is null) 329 join(); // it couldn't initialize, just rethrow the exception 330 } 331 332 /// 333 @scriptable 334 void pause() { 335 if(ao) { 336 ao.pause(); 337 } 338 } 339 340 /// 341 @scriptable 342 void unpause() { 343 if(ao) { 344 ao.unpause(); 345 } 346 } 347 348 /// 349 void stop() { 350 if(ao) { 351 ao.stop(); 352 } 353 } 354 355 /// Args in hertz and milliseconds 356 @scriptable 357 void beep(int freq = 900, int dur = 150, int volume = DEFAULT_VOLUME) { 358 Sample s; 359 s.operation = 0; // square wave 360 s.frequency = SampleRate / freq; 361 s.duration = dur * SampleRate / 1000; 362 s.volume = volume; 363 addSample(s); 364 } 365 366 /// 367 @scriptable 368 void noise(int dur = 150, int volume = DEFAULT_VOLUME) { 369 Sample s; 370 s.operation = 1; // noise 371 s.frequency = 0; 372 s.volume = volume; 373 s.duration = dur * SampleRate / 1000; 374 addSample(s); 375 } 376 377 /// 378 @scriptable 379 void boop(float attack = 8, int freqBase = 500, int dur = 150, int volume = DEFAULT_VOLUME) { 380 Sample s; 381 s.operation = 5; // custom 382 s.volume = volume; 383 s.duration = dur * SampleRate / 1000; 384 s.f = delegate short(int x) { 385 auto currentFrequency = cast(float) freqBase / (1 + cast(float) x / (cast(float) SampleRate / attack)); 386 import std.math; 387 auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency); 388 return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100); 389 }; 390 addSample(s); 391 } 392 393 /// 394 @scriptable 395 void blip(float attack = 6, int freqBase = 800, int dur = 150, int volume = DEFAULT_VOLUME) { 396 Sample s; 397 s.operation = 5; // custom 398 s.volume = volume; 399 s.duration = dur * SampleRate / 1000; 400 s.f = delegate short(int x) { 401 auto currentFrequency = cast(float) freqBase * (1 + cast(float) x / (cast(float) SampleRate / attack)); 402 import std.math; 403 auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency); 404 return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100); 405 }; 406 addSample(s); 407 } 408 409 version(none) 410 void custom(int dur = 150, int volume = DEFAULT_VOLUME) { 411 Sample s; 412 s.operation = 5; // custom 413 s.volume = volume; 414 s.duration = dur * SampleRate / 1000; 415 s.f = delegate short(int x) { 416 auto currentFrequency = 500.0 / (1 + cast(float) x / (cast(float) SampleRate / 8)); 417 import std.math; 418 auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency); 419 return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100); 420 }; 421 addSample(s); 422 } 423 424 /// Requires vorbis.d to be compiled in (module arsd.vorbis) 425 void playOgg()(string filename, bool loop = false) { 426 import arsd.vorbis; 427 428 auto v = new VorbisDecoder(filename); 429 430 addChannel( 431 delegate bool(short[] buffer) { 432 if(cast(int) buffer.length != buffer.length) 433 throw new Exception("eeeek"); 434 auto got = v.getSamplesShortInterleaved(2, buffer.ptr, cast(int) buffer.length); 435 if(got == 0) { 436 if(loop) { 437 v.seekStart(); 438 return true; 439 } 440 441 return false; 442 } 443 return true; 444 } 445 ); 446 } 447 448 /// Requires mp3.d to be compiled in (module arsd.mp3) which is LGPL licensed. 449 void playMp3()(string filename) { 450 import arsd.mp3; 451 452 import std.stdio; 453 auto fi = File(filename); 454 scope auto reader = delegate(void[] buf) { 455 return cast(int) fi.rawRead(buf[]).length; 456 }; 457 458 auto mp3 = new MP3Decoder(reader); 459 if(!mp3.valid) 460 throw new Exception("no file"); 461 462 auto next = mp3.frameSamples; 463 464 addChannel( 465 delegate bool(short[] buffer) { 466 if(cast(int) buffer.length != buffer.length) 467 throw new Exception("eeeek"); 468 469 more: 470 if(next.length >= buffer.length) { 471 buffer[] = next[0 .. buffer.length]; 472 next = next[buffer.length .. $]; 473 } else { 474 buffer[0 .. next.length] = next[]; 475 buffer = buffer[next.length .. $]; 476 477 next = next[$..$]; 478 479 if(buffer.length) { 480 if(mp3.valid) { 481 mp3.decodeNextFrame(reader); 482 next = mp3.frameSamples; 483 goto more; 484 } else { 485 buffer[] = 0; 486 return false; 487 } 488 } 489 } 490 491 return true; 492 } 493 ); 494 } 495 496 497 498 struct Sample { 499 int operation; 500 int frequency; /* in samples */ 501 int duration; /* in samples */ 502 int volume; /* between 1 and 100. You should generally shoot for something lowish, like 20. */ 503 int delay; /* in samples */ 504 505 int x; 506 short delegate(int x) f; 507 } 508 509 final void addSample(Sample currentSample) { 510 int frequencyCounter; 511 short val = cast(short) (cast(int) short.max * currentSample.volume / 100); 512 addChannel( 513 delegate bool (short[] buffer) { 514 if(currentSample.duration) { 515 size_t i = 0; 516 if(currentSample.delay) { 517 if(buffer.length <= currentSample.delay * 2) { 518 // whole buffer consumed by delay 519 buffer[] = 0; 520 currentSample.delay -= buffer.length / 2; 521 } else { 522 i = currentSample.delay * 2; 523 buffer[0 .. i] = 0; 524 currentSample.delay = 0; 525 } 526 } 527 if(currentSample.delay > 0) 528 return true; 529 530 size_t sampleFinish; 531 if(currentSample.duration * 2 <= buffer.length) { 532 sampleFinish = currentSample.duration * 2; 533 currentSample.duration = 0; 534 } else { 535 sampleFinish = buffer.length; 536 currentSample.duration -= buffer.length / 2; 537 } 538 539 switch(currentSample.operation) { 540 case 0: // square wave 541 for(; i < sampleFinish; i++) { 542 buffer[i] = val; 543 // left and right do the same thing so we only count 544 // every other sample 545 if(i & 1) { 546 if(frequencyCounter) 547 frequencyCounter--; 548 if(frequencyCounter == 0) { 549 // are you kidding me dmd? random casts suck 550 val = cast(short) -cast(int)(val); 551 frequencyCounter = currentSample.frequency / 2; 552 } 553 } 554 } 555 break; 556 case 1: // noise 557 for(; i < sampleFinish; i++) { 558 import std.random; 559 buffer[i] = uniform(cast(short) -cast(int)val, val); 560 } 561 break; 562 /+ 563 case 2: // triangle wave 564 565 short[] tone; 566 tone.length = 22050 * len / 1000; 567 568 short valmax = cast(short) (cast(int) volume * short.max / 100); 569 int wavelength = 22050 / freq; 570 wavelength /= 2; 571 int da = valmax / wavelength; 572 int val = 0; 573 574 for(int a = 0; a < tone.length; a++){ 575 tone[a] = cast(short) val; 576 val+= da; 577 if(da > 0 && val >= valmax) 578 da *= -1; 579 if(da < 0 && val <= -valmax) 580 da *= -1; 581 } 582 583 data ~= tone; 584 585 586 for(; i < sampleFinish; i++) { 587 buffer[i] = val; 588 // left and right do the same thing so we only count 589 // every other sample 590 if(i & 1) { 591 if(frequencyCounter) 592 frequencyCounter--; 593 if(frequencyCounter == 0) { 594 val = 0; 595 frequencyCounter = currentSample.frequency / 2; 596 } 597 } 598 } 599 600 break; 601 case 3: // sawtooth wave 602 short[] tone; 603 tone.length = 22050 * len / 1000; 604 605 int valmax = volume * short.max / 100; 606 int wavelength = 22050 / freq; 607 int da = valmax / wavelength; 608 short val = 0; 609 610 for(int a = 0; a < tone.length; a++){ 611 tone[a] = val; 612 val+= da; 613 if(val >= valmax) 614 val = 0; 615 } 616 617 data ~= tone; 618 case 4: // sine wave 619 short[] tone; 620 tone.length = 22050 * len / 1000; 621 622 int valmax = volume * short.max / 100; 623 int val = 0; 624 625 float i = 2*PI / (22050/freq); 626 627 float f = 0; 628 for(int a = 0; a < tone.length; a++){ 629 tone[a] = cast(short) (valmax * sin(f)); 630 f += i; 631 if(f>= 2*PI) 632 f -= 2*PI; 633 } 634 635 data ~= tone; 636 637 +/ 638 case 5: // custom function 639 val = currentSample.f(currentSample.x); 640 for(; i < sampleFinish; i++) { 641 buffer[i] = val; 642 if(i & 1) { 643 currentSample.x++; 644 val = currentSample.f(currentSample.x); 645 } 646 } 647 break; 648 default: // unknown; use silence 649 currentSample.duration = 0; 650 } 651 652 if(i < buffer.length) 653 buffer[i .. $] = 0; 654 655 return currentSample.duration > 0; 656 } else { 657 return false; 658 } 659 } 660 ); 661 } 662 663 /++ 664 The delegate returns false when it is finished (true means keep going). 665 It must fill the buffer with waveform data on demand and must be latency 666 sensitive; as fast as possible. 667 +/ 668 public void addChannel(bool delegate(short[] buffer) dg) { 669 synchronized(this) { 670 // silently drops info if we don't have room in the buffer... 671 // don't do a lot of long running things lol 672 if(fillDatasLength < fillDatas.length) 673 fillDatas[fillDatasLength++] = dg; 674 } 675 } 676 677 private { 678 AudioOutput* ao; 679 680 bool delegate(short[] buffer)[32] fillDatas; 681 int fillDatasLength = 0; 682 } 683 684 private void run() { 685 686 version(linux) { 687 // this thread has no business intercepting signals from the main thread, 688 // so gonna block a couple of them 689 import core.sys.posix.signal; 690 sigset_t sigset; 691 auto err = sigemptyset(&sigset); 692 assert(!err); 693 694 err = sigaddset(&sigset, SIGINT); assert(!err); 695 err = sigaddset(&sigset, SIGCHLD); assert(!err); 696 697 err = sigprocmask(SIG_BLOCK, &sigset, null); 698 assert(!err); 699 } 700 701 AudioOutput ao = AudioOutput(0); 702 this.ao = &ao; 703 scope(exit) this.ao = null; 704 auto omg = this; 705 ao.fillData = (short[] buffer) { 706 short[BUFFER_SIZE_SHORT] bfr; 707 bool first = true; 708 if(fillDatasLength) { 709 for(int idx = 0; idx < fillDatasLength; idx++) { 710 auto dg = fillDatas[idx]; 711 auto ret = dg(bfr[0 .. buffer.length][]); 712 foreach(i, v; bfr[0 .. buffer.length][]) { 713 int val; 714 if(first) 715 val = 0; 716 else 717 val = buffer[i]; 718 719 int a = val; 720 int b = v; 721 int cap = a + b; 722 if(cap > short.max) cap = short.max; 723 else if(cap < short.min) cap = short.min; 724 val = cast(short) cap; 725 buffer[i] = cast(short) val; 726 } 727 if(!ret) { 728 // it returned false meaning this one is finished... 729 synchronized(omg) { 730 fillDatas[idx] = fillDatas[fillDatasLength - 1]; 731 fillDatasLength--; 732 } 733 idx--; 734 } 735 736 first = false; 737 } 738 } else { 739 buffer[] = 0; 740 } 741 }; 742 ao.play(); 743 } 744 } 745 746 747 import core.stdc.config; 748 749 version(linux) version=ALSA; 750 version(Windows) version=WinMM; 751 752 version(ALSA) { 753 enum cardName = "default"; 754 755 // this is the virtual rawmidi device on my computer at least 756 // maybe later i'll make it probe 757 // 758 // Getting midi to actually play on Linux is a bit of a pain. 759 // Here's what I did: 760 /* 761 # load the kernel driver, if amidi -l gives ioctl error, 762 # you haven't done this yet! 763 modprobe snd-virmidi 764 765 # start a software synth. timidity -iA is also an option 766 fluidsynth soundfont.sf2 767 768 # connect the virtual hardware port to the synthesizer 769 aconnect 24:0 128:0 770 771 772 I might also add a snd_seq client here which is a bit 773 easier to setup but for now I'm using the rawmidi so you 774 gotta get them connected somehow. 775 */ 776 enum midiName = "hw:3,0"; 777 778 enum midiCaptureName = "hw:4,0"; 779 780 // fyi raw midi dump: amidi -d --port hw:4,0 781 // connect my midi out to fluidsynth: aconnect 28:0 128:0 782 // and my keyboard to it: aconnect 32:0 128:0 783 } 784 785 /// Thrown on audio failures. 786 /// Subclass this to provide OS-specific exceptions 787 class AudioException : Exception { 788 this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 789 super(message, file, line, next); 790 } 791 } 792 793 /++ 794 Gives PCM input access (such as a microphone). 795 796 History: 797 Windows support added May 10, 2020 and the API got overhauled too. 798 +/ 799 struct AudioInput { 800 version(ALSA) { 801 snd_pcm_t* handle; 802 } else version(WinMM) { 803 HWAVEIN handle; 804 HANDLE event; 805 } else static assert(0); 806 807 @disable this(); 808 @disable this(this); 809 810 int channels; 811 int SampleRate; 812 813 /// Always pass card == 0. 814 this(int card, int SampleRate = 44100, int channels = 2) { 815 assert(card == 0); 816 817 assert(channels == 1 || channels == 2); 818 819 this.channels = channels; 820 this.SampleRate = SampleRate; 821 822 version(ALSA) { 823 handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_CAPTURE, SampleRate, channels); 824 } else version(WinMM) { 825 event = CreateEvent(null, false /* manual reset */, false /* initially triggered */, null); 826 827 WAVEFORMATEX format; 828 format.wFormatTag = WAVE_FORMAT_PCM; 829 format.nChannels = 2; 830 format.nSamplesPerSec = SampleRate; 831 format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample 832 format.nBlockAlign = 4; 833 format.wBitsPerSample = 16; 834 format.cbSize = 0; 835 if(auto err = waveInOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) event, cast(DWORD_PTR) &this, CALLBACK_EVENT)) 836 throw new WinMMException("wave in open", err); 837 838 } else static assert(0); 839 } 840 841 /// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz 842 /// Each item in the array thus alternates between left and right channel 843 /// and it takes a total of 88,200 items to make one second of sound. 844 /// 845 /// Returns the slice of the buffer actually read into 846 /// 847 /// LINUX ONLY. You should prolly use [record] instead 848 version(ALSA) 849 short[] read(short[] buffer) { 850 snd_pcm_sframes_t read; 851 852 read = snd_pcm_readi(handle, buffer.ptr, buffer.length / channels /* div number of channels apparently */); 853 if(read < 0) 854 throw new AlsaException("pcm read", cast(int)read); 855 856 return buffer[0 .. read * channels]; 857 } 858 859 /// passes a buffer of data to fill 860 /// 861 /// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz 862 /// Each item in the array thus alternates between left and right channel 863 /// and it takes a total of 88,200 items to make one second of sound. 864 void delegate(short[]) receiveData; 865 866 /// 867 void stop() { 868 recording = false; 869 } 870 871 /// First, set [receiveData], then call this. 872 void record() { 873 assert(receiveData !is null); 874 recording = true; 875 876 version(ALSA) { 877 short[BUFFER_SIZE_SHORT] buffer; 878 while(recording) { 879 auto got = read(buffer); 880 receiveData(got); 881 } 882 } else version(WinMM) { 883 884 enum numBuffers = 2; // use a lot of buffers to get smooth output with Sleep, see below 885 short[BUFFER_SIZE_SHORT][numBuffers] buffers; 886 887 WAVEHDR[numBuffers] headers; 888 889 foreach(i, ref header; headers) { 890 auto buffer = buffers[i][]; 891 header.lpData = cast(char*) buffer.ptr; 892 header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof; 893 header.dwFlags = 0;// WHDR_BEGINLOOP | WHDR_ENDLOOP; 894 header.dwLoops = 0; 895 896 if(auto err = waveInPrepareHeader(handle, &header, header.sizeof)) 897 throw new WinMMException("prepare header", err); 898 899 header.dwUser = 1; // mark that the driver is using it 900 if(auto err = waveInAddBuffer(handle, &header, header.sizeof)) 901 throw new WinMMException("wave in read", err); 902 } 903 904 waveInStart(handle); 905 scope(failure) waveInReset(handle); 906 907 while(recording) { 908 if(auto err = WaitForSingleObject(event, INFINITE)) 909 throw new Exception("WaitForSingleObject"); 910 if(!recording) 911 break; 912 913 foreach(ref header; headers) { 914 if(!(header.dwFlags & WHDR_DONE)) continue; 915 916 receiveData((cast(short*) header.lpData)[0 .. header.dwBytesRecorded / short.sizeof]); 917 if(!recording) break; 918 header.dwUser = 1; // mark that the driver is using it 919 if(auto err = waveInAddBuffer(handle, &header, header.sizeof)) { 920 throw new WinMMException("waveInAddBuffer", err); 921 } 922 } 923 } 924 925 /* 926 if(auto err = waveInStop(handle)) 927 throw new WinMMException("wave in stop", err); 928 */ 929 930 if(auto err = waveInReset(handle)) { 931 throw new WinMMException("wave in reset", err); 932 } 933 934 still_in_use: 935 foreach(idx, header; headers) 936 if(!(header.dwFlags & WHDR_DONE)) { 937 Sleep(1); 938 goto still_in_use; 939 } 940 941 foreach(ref header; headers) 942 if(auto err = waveInUnprepareHeader(handle, &header, header.sizeof)) { 943 throw new WinMMException("unprepare header", err); 944 } 945 946 ResetEvent(event); 947 } else static assert(0); 948 } 949 950 private bool recording; 951 952 ~this() { 953 receiveData = null; 954 version(ALSA) { 955 snd_pcm_close(handle); 956 } else version(WinMM) { 957 if(auto err = waveInClose(handle)) 958 throw new WinMMException("close", err); 959 960 CloseHandle(event); 961 // in wine (though not Windows nor winedbg as far as I can tell) 962 // this randomly segfaults. the sleep prevents it. idk why. 963 Sleep(5); 964 } else static assert(0); 965 } 966 } 967 968 /// 969 enum SampleRateFull = 44100; 970 971 /// Gives PCM output access (such as the speakers). 972 struct AudioOutput { 973 version(ALSA) { 974 snd_pcm_t* handle; 975 } else version(WinMM) { 976 HWAVEOUT handle; 977 } 978 979 @disable this(); 980 // This struct must NEVER be moved or copied, a pointer to it may 981 // be passed to a device driver and stored! 982 @disable this(this); 983 984 private int SampleRate; 985 private int channels; 986 987 /// Always pass card == 0. 988 this(int card, int SampleRate = 44100, int channels = 2) { 989 assert(card == 0); 990 991 assert(channels == 1 || channels == 2); 992 993 this.SampleRate = SampleRate; 994 this.channels = channels; 995 996 version(ALSA) { 997 handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK, SampleRate, channels); 998 } else version(WinMM) { 999 WAVEFORMATEX format; 1000 format.wFormatTag = WAVE_FORMAT_PCM; 1001 format.nChannels = cast(ushort) channels; 1002 format.nSamplesPerSec = SampleRate; 1003 format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample 1004 format.nBlockAlign = 4; 1005 format.wBitsPerSample = 16; 1006 format.cbSize = 0; 1007 if(auto err = waveOutOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION)) 1008 throw new WinMMException("wave out open", err); 1009 } else static assert(0); 1010 } 1011 1012 /// passes a buffer of data to fill 1013 /// 1014 /// Data is assumed to be interleaved stereo, LE 16 bit, 44.1 kHz (unless you change that in the ctor) 1015 /// Each item in the array thus alternates between left and right channel (unless you change that in the ctor) 1016 /// and it takes a total of 88,200 items to make one second of sound. 1017 void delegate(short[]) fillData; 1018 1019 shared(bool) playing = false; // considered to be volatile 1020 1021 /// Starts playing, loops until stop is called 1022 void play() { 1023 assert(fillData !is null); 1024 playing = true; 1025 1026 version(ALSA) { 1027 short[BUFFER_SIZE_SHORT] buffer; 1028 while(playing) { 1029 auto err = snd_pcm_wait(handle, 500); 1030 if(err < 0) 1031 throw new AlsaException("uh oh", err); 1032 // err == 0 means timeout 1033 // err == 1 means ready 1034 1035 auto ready = snd_pcm_avail_update(handle); 1036 if(ready < 0) 1037 throw new AlsaException("avail", cast(int)ready); 1038 if(ready > BUFFER_SIZE_FRAMES) 1039 ready = BUFFER_SIZE_FRAMES; 1040 //import std.stdio; writeln("filling ", ready); 1041 fillData(buffer[0 .. ready * channels]); 1042 if(playing) { 1043 snd_pcm_sframes_t written; 1044 auto data = buffer[0 .. ready * channels]; 1045 1046 while(data.length) { 1047 written = snd_pcm_writei(handle, data.ptr, data.length / channels); 1048 if(written < 0) { 1049 written = snd_pcm_recover(handle, cast(int)written, 0); 1050 if (written < 0) throw new AlsaException("pcm write", cast(int)written); 1051 } 1052 data = data[written * channels .. $]; 1053 } 1054 } 1055 } 1056 } else version(WinMM) { 1057 1058 enum numBuffers = 4; // use a lot of buffers to get smooth output with Sleep, see below 1059 short[BUFFER_SIZE_SHORT][numBuffers] buffers; 1060 1061 WAVEHDR[numBuffers] headers; 1062 1063 foreach(i, ref header; headers) { 1064 // since this is wave out, it promises not to write... 1065 auto buffer = buffers[i][]; 1066 header.lpData = cast(char*) buffer.ptr; 1067 header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof; 1068 header.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP; 1069 header.dwLoops = 1; 1070 1071 if(auto err = waveOutPrepareHeader(handle, &header, header.sizeof)) 1072 throw new WinMMException("prepare header", err); 1073 1074 // prime it 1075 fillData(buffer[]); 1076 1077 // indicate that they are filled and good to go 1078 header.dwUser = 1; 1079 } 1080 1081 while(playing) { 1082 // and queue both to be played, if they are ready 1083 foreach(ref header; headers) 1084 if(header.dwUser) { 1085 if(auto err = waveOutWrite(handle, &header, header.sizeof)) 1086 throw new WinMMException("wave out write", err); 1087 header.dwUser = 0; 1088 } 1089 Sleep(1); 1090 // the system resolution may be lower than this sleep. To avoid gaps 1091 // in output, we use multiple buffers. Might introduce latency, not 1092 // sure how best to fix. I don't want to busy loop... 1093 } 1094 1095 // wait for the system to finish with our buffers 1096 bool anyInUse = true; 1097 1098 while(anyInUse) { 1099 anyInUse = false; 1100 foreach(header; headers) { 1101 if(!header.dwUser) { 1102 anyInUse = true; 1103 break; 1104 } 1105 } 1106 if(anyInUse) 1107 Sleep(1); 1108 } 1109 1110 foreach(ref header; headers) 1111 if(auto err = waveOutUnprepareHeader(handle, &header, header.sizeof)) 1112 throw new WinMMException("unprepare", err); 1113 } else static assert(0); 1114 } 1115 1116 /// Breaks the play loop 1117 void stop() { 1118 playing = false; 1119 } 1120 1121 /// 1122 void pause() { 1123 version(WinMM) 1124 waveOutPause(handle); 1125 else version(ALSA) 1126 snd_pcm_pause(handle, 1); 1127 } 1128 1129 /// 1130 void unpause() { 1131 version(WinMM) 1132 waveOutRestart(handle); 1133 else version(ALSA) 1134 snd_pcm_pause(handle, 0); 1135 1136 } 1137 1138 version(WinMM) { 1139 extern(Windows) 1140 static void mmCallback(HWAVEOUT handle, UINT msg, void* userData, WAVEHDR* header, DWORD_PTR param2) { 1141 AudioOutput* ao = cast(AudioOutput*) userData; 1142 if(msg == WOM_DONE) { 1143 // we want to bounce back and forth between two buffers 1144 // to keep the sound going all the time 1145 if(ao.playing) { 1146 ao.fillData((cast(short*) header.lpData)[0 .. header.dwBufferLength / short.sizeof]); 1147 } 1148 header.dwUser = 1; 1149 } 1150 } 1151 } 1152 1153 // FIXME: add async function hooks 1154 1155 ~this() { 1156 version(ALSA) { 1157 snd_pcm_close(handle); 1158 } else version(WinMM) { 1159 waveOutClose(handle); 1160 } else static assert(0); 1161 } 1162 } 1163 1164 /++ 1165 For reading midi events from hardware, for example, an electronic piano keyboard 1166 attached to the computer. 1167 +/ 1168 struct MidiInput { 1169 // reading midi devices... 1170 version(ALSA) { 1171 snd_rawmidi_t* handle; 1172 } else version(WinMM) { 1173 HMIDIIN handle; 1174 } 1175 1176 @disable this(); 1177 @disable this(this); 1178 1179 /+ 1180 B0 40 7F # pedal on 1181 B0 40 00 # sustain pedal off 1182 +/ 1183 1184 /// Always pass card == 0. 1185 this(int card) { 1186 assert(card == 0); 1187 1188 version(ALSA) { 1189 if(auto err = snd_rawmidi_open(&handle, null, midiCaptureName, 0)) 1190 throw new AlsaException("rawmidi open", err); 1191 } else version(WinMM) { 1192 if(auto err = midiInOpen(&handle, 0, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION)) 1193 throw new WinMMException("midi in open", err); 1194 } else static assert(0); 1195 } 1196 1197 private bool recording = false; 1198 1199 /// 1200 void stop() { 1201 recording = false; 1202 } 1203 1204 /++ 1205 Records raw midi input data from the device. 1206 1207 The timestamp is given in milliseconds since recording 1208 began (if you keep this program running for 23ish days 1209 it might overflow! so... don't do that.). The other bytes 1210 are the midi messages. 1211 1212 $(PITFALL Do not call any other multimedia functions from the callback!) 1213 +/ 1214 void record(void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg) { 1215 version(ALSA) { 1216 recording = true; 1217 ubyte[1024] data; 1218 import core.time; 1219 auto start = MonoTime.currTime; 1220 while(recording) { 1221 auto read = snd_rawmidi_read(handle, data.ptr, data.length); 1222 if(read < 0) 1223 throw new AlsaException("midi read", cast(int) read); 1224 1225 auto got = data[0 .. read]; 1226 while(got.length) { 1227 // FIXME some messages are fewer bytes.... 1228 dg(cast(uint) (MonoTime.currTime - start).total!"msecs", got[0], got[1], got[2]); 1229 got = got[3 .. $]; 1230 } 1231 } 1232 } else version(WinMM) { 1233 recording = true; 1234 this.dg = dg; 1235 scope(exit) 1236 this.dg = null; 1237 midiInStart(handle); 1238 scope(exit) 1239 midiInReset(handle); 1240 1241 while(recording) { 1242 Sleep(1); 1243 } 1244 } else static assert(0); 1245 } 1246 1247 version(WinMM) 1248 private void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg; 1249 1250 1251 version(WinMM) 1252 extern(Windows) 1253 static 1254 void mmCallback(HMIDIIN handle, UINT msg, DWORD_PTR user, DWORD_PTR param1, DWORD_PTR param2) { 1255 MidiInput* mi = cast(MidiInput*) user; 1256 if(msg == MIM_DATA) { 1257 mi.dg( 1258 cast(uint) param2, 1259 param1 & 0xff, 1260 (param1 >> 8) & 0xff, 1261 (param1 >> 16) & 0xff 1262 ); 1263 } 1264 } 1265 1266 ~this() { 1267 version(ALSA) { 1268 snd_rawmidi_close(handle); 1269 } else version(WinMM) { 1270 midiInClose(handle); 1271 } else static assert(0); 1272 } 1273 } 1274 1275 // plays a midi file in the background with methods to tweak song as it plays 1276 struct MidiOutputThread { 1277 void injectCommand() {} 1278 void pause() {} 1279 void unpause() {} 1280 1281 void trackEnabled(bool on) {} 1282 void channelEnabled(bool on) {} 1283 1284 void loopEnabled(bool on) {} 1285 1286 // stops the current song, pushing its position to the stack for later 1287 void pushSong() {} 1288 // restores a popped song from where it was. 1289 void popSong() {} 1290 } 1291 1292 version(Posix) { 1293 import core.sys.posix.signal; 1294 private sigaction_t oldSigIntr; 1295 void setSigIntHandler() { 1296 sigaction_t n; 1297 n.sa_handler = &interruptSignalHandler; 1298 n.sa_mask = cast(sigset_t) 0; 1299 n.sa_flags = 0; 1300 sigaction(SIGINT, &n, &oldSigIntr); 1301 } 1302 void restoreSigIntHandler() { 1303 sigaction(SIGINT, &oldSigIntr, null); 1304 } 1305 1306 __gshared bool interrupted; 1307 1308 private 1309 extern(C) 1310 void interruptSignalHandler(int sigNumber) nothrow { 1311 interrupted = true; 1312 } 1313 } 1314 1315 /// Gives MIDI output access. 1316 struct MidiOutput { 1317 version(ALSA) { 1318 snd_rawmidi_t* handle; 1319 } else version(WinMM) { 1320 HMIDIOUT handle; 1321 } 1322 1323 @disable this(); 1324 @disable this(this); 1325 1326 /// Always pass card == 0. 1327 this(int card) { 1328 assert(card == 0); 1329 1330 version(ALSA) { 1331 if(auto err = snd_rawmidi_open(null, &handle, midiName, 0)) 1332 throw new AlsaException("rawmidi open", err); 1333 } else version(WinMM) { 1334 if(auto err = midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL)) 1335 throw new WinMMException("midi out open", err); 1336 } else static assert(0); 1337 } 1338 1339 void silenceAllNotes() { 1340 foreach(a; 0 .. 16) 1341 writeMidiMessage((0x0b << 4)|a /*MIDI_EVENT_CONTROLLER*/, 123, 0); 1342 } 1343 1344 /// Send a reset message, silencing all notes 1345 void reset() { 1346 version(ALSA) { 1347 static immutable ubyte[3] resetSequence = [0x0b << 4, 123, 0]; 1348 // send a controller event to reset it 1349 writeRawMessageData(resetSequence[]); 1350 static immutable ubyte[1] resetCmd = [0xff]; 1351 writeRawMessageData(resetCmd[]); 1352 // and flush it immediately 1353 snd_rawmidi_drain(handle); 1354 } else version(WinMM) { 1355 if(auto error = midiOutReset(handle)) 1356 throw new WinMMException("midi reset", error); 1357 } else static assert(0); 1358 } 1359 1360 /// Writes a single low-level midi message 1361 /// Timing and sending sane data is your responsibility! 1362 void writeMidiMessage(int status, int param1, int param2) { 1363 version(ALSA) { 1364 ubyte[3] dataBuffer; 1365 1366 dataBuffer[0] = cast(ubyte) status; 1367 dataBuffer[1] = cast(ubyte) param1; 1368 dataBuffer[2] = cast(ubyte) param2; 1369 1370 auto msg = status >> 4; 1371 ubyte[] data; 1372 if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) 1373 data = dataBuffer[0 .. 2]; 1374 else 1375 data = dataBuffer[]; 1376 1377 writeRawMessageData(data); 1378 } else version(WinMM) { 1379 DWORD word = (param2 << 16) | (param1 << 8) | status; 1380 if(auto error = midiOutShortMsg(handle, word)) 1381 throw new WinMMException("midi out", error); 1382 } else static assert(0); 1383 1384 } 1385 1386 /// Writes a series of individual raw messages. 1387 /// Timing and sending sane data is your responsibility! 1388 /// The data should NOT include any timestamp bytes - each midi message should be 2 or 3 bytes. 1389 void writeRawMessageData(scope const(ubyte)[] data) { 1390 if(data.length == 0) 1391 return; 1392 version(ALSA) { 1393 ssize_t written; 1394 1395 while(data.length) { 1396 written = snd_rawmidi_write(handle, data.ptr, data.length); 1397 if(written < 0) 1398 throw new AlsaException("midi write", cast(int) written); 1399 data = data[cast(int) written .. $]; 1400 } 1401 } else version(WinMM) { 1402 while(data.length) { 1403 auto msg = data[0] >> 4; 1404 if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) { 1405 writeMidiMessage(data[0], data[1], 0); 1406 data = data[2 .. $]; 1407 } else { 1408 writeMidiMessage(data[0], data[1], data[2]); 1409 data = data[3 .. $]; 1410 } 1411 } 1412 } else static assert(0); 1413 } 1414 1415 ~this() { 1416 version(ALSA) { 1417 snd_rawmidi_close(handle); 1418 } else version(WinMM) { 1419 midiOutClose(handle); 1420 } else static assert(0); 1421 } 1422 } 1423 1424 1425 // FIXME: maybe add a PC speaker beep function for completeness 1426 1427 /// Interfaces with the default sound card. You should only have a single instance of this and it should 1428 /// be stack allocated, so its destructor cleans up after it. 1429 /// 1430 /// A mixer gives access to things like volume controls and mute buttons. It should also give a 1431 /// callback feature to alert you of when the settings are changed by another program. 1432 version(ALSA) // FIXME 1433 struct AudioMixer { 1434 // To port to a new OS: put the data in the right version blocks 1435 // then implement each function. Leave else static assert(0) at the 1436 // end of each version group in a function so it is easier to implement elsewhere later. 1437 // 1438 // If a function is only relevant on your OS, put the whole function in a version block 1439 // and give it an OS specific name of some sort. 1440 // 1441 // Feel free to do that btw without worrying about lowest common denominator: we want low level access when we want it. 1442 // 1443 // Put necessary bindings at the end of the file, or use an import if you like, but I prefer these files to be standalone. 1444 version(ALSA) { 1445 snd_mixer_t* handle; 1446 snd_mixer_selem_id_t* sid; 1447 snd_mixer_elem_t* selem; 1448 1449 c_long maxVolume, minVolume; // these are ok to use if you are writing ALSA specific code i guess 1450 1451 enum selemName = "Master"; 1452 } 1453 1454 @disable this(); 1455 @disable this(this); 1456 1457 /// Only cardId == 0 is supported 1458 this(int cardId) { 1459 assert(cardId == 0, "Pass 0 to use default sound card."); 1460 1461 version(ALSA) { 1462 if(auto err = snd_mixer_open(&handle, 0)) 1463 throw new AlsaException("open sound", err); 1464 scope(failure) 1465 snd_mixer_close(handle); 1466 if(auto err = snd_mixer_attach(handle, cardName)) 1467 throw new AlsaException("attach to sound card", err); 1468 if(auto err = snd_mixer_selem_register(handle, null, null)) 1469 throw new AlsaException("register mixer", err); 1470 if(auto err = snd_mixer_load(handle)) 1471 throw new AlsaException("load mixer", err); 1472 1473 if(auto err = snd_mixer_selem_id_malloc(&sid)) 1474 throw new AlsaException("master channel open", err); 1475 scope(failure) 1476 snd_mixer_selem_id_free(sid); 1477 snd_mixer_selem_id_set_index(sid, 0); 1478 snd_mixer_selem_id_set_name(sid, selemName); 1479 selem = snd_mixer_find_selem(handle, sid); 1480 if(selem is null) 1481 throw new AlsaException("find master element", 0); 1482 1483 if(auto err = snd_mixer_selem_get_playback_volume_range(selem, &minVolume, &maxVolume)) 1484 throw new AlsaException("get volume range", err); 1485 1486 version(with_eventloop) { 1487 import arsd.eventloop; 1488 addFileEventListeners(getAlsaFileDescriptors()[0], &eventListener, null, null); 1489 setAlsaElemCallback(&alsaCallback); 1490 } 1491 } else static assert(0); 1492 } 1493 1494 ~this() { 1495 version(ALSA) { 1496 version(with_eventloop) { 1497 import arsd.eventloop; 1498 removeFileEventListeners(getAlsaFileDescriptors()[0]); 1499 } 1500 snd_mixer_selem_id_free(sid); 1501 snd_mixer_close(handle); 1502 } else static assert(0); 1503 } 1504 1505 version(ALSA) 1506 version(with_eventloop) { 1507 static struct MixerEvent {} 1508 nothrow @nogc 1509 extern(C) static int alsaCallback(snd_mixer_elem_t*, uint) { 1510 import arsd.eventloop; 1511 try 1512 send(MixerEvent()); 1513 catch(Exception) 1514 return 1; 1515 1516 return 0; 1517 } 1518 1519 void eventListener(int fd) { 1520 handleAlsaEvents(); 1521 } 1522 } 1523 1524 /// Gets the master channel's mute state 1525 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 1526 @property bool muteMaster() { 1527 version(ALSA) { 1528 int result; 1529 if(auto err = snd_mixer_selem_get_playback_switch(selem, 0, &result)) 1530 throw new AlsaException("get mute state", err); 1531 return result == 0; 1532 } else static assert(0); 1533 } 1534 1535 /// Mutes or unmutes the master channel 1536 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 1537 @property void muteMaster(bool mute) { 1538 version(ALSA) { 1539 if(auto err = snd_mixer_selem_set_playback_switch_all(selem, mute ? 0 : 1)) 1540 throw new AlsaException("set mute state", err); 1541 } else static assert(0); 1542 } 1543 1544 /// returns a percentage, between 0 and 100 (inclusive) 1545 int getMasterVolume() { 1546 version(ALSA) { 1547 auto volume = getMasterVolumeExact(); 1548 return cast(int)(volume * 100 / (maxVolume - minVolume)); 1549 } else static assert(0); 1550 } 1551 1552 /// Gets the exact value returned from the operating system. The range may vary. 1553 int getMasterVolumeExact() { 1554 version(ALSA) { 1555 c_long volume; 1556 snd_mixer_selem_get_playback_volume(selem, 0, &volume); 1557 return cast(int)volume; 1558 } else static assert(0); 1559 } 1560 1561 /// sets a percentage on the volume, so it must be 0 <= volume <= 100 1562 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 1563 void setMasterVolume(int volume) { 1564 version(ALSA) { 1565 assert(volume >= 0 && volume <= 100); 1566 setMasterVolumeExact(cast(int)(volume * (maxVolume - minVolume) / 100)); 1567 } else static assert(0); 1568 } 1569 1570 /// Sets an exact volume. Must be in range of the OS provided min and max. 1571 void setMasterVolumeExact(int volume) { 1572 version(ALSA) { 1573 if(auto err = snd_mixer_selem_set_playback_volume_all(selem, volume)) 1574 throw new AlsaException("set volume", err); 1575 } else static assert(0); 1576 } 1577 1578 version(ALSA) { 1579 /// Gets the ALSA descriptors which you can watch for events 1580 /// on using regular select, poll, epoll, etc. 1581 int[] getAlsaFileDescriptors() { 1582 import core.sys.posix.poll; 1583 pollfd[32] descriptors = void; 1584 int got = snd_mixer_poll_descriptors(handle, descriptors.ptr, descriptors.length); 1585 int[] result; 1586 result.length = got; 1587 foreach(i, desc; descriptors[0 .. got]) 1588 result[i] = desc.fd; 1589 return result; 1590 } 1591 1592 /// When the FD is ready, call this to let ALSA do its thing. 1593 void handleAlsaEvents() { 1594 snd_mixer_handle_events(handle); 1595 } 1596 1597 /// Set a callback for the master volume change events. 1598 void setAlsaElemCallback(snd_mixer_elem_callback_t dg) { 1599 snd_mixer_elem_set_callback(selem, dg); 1600 } 1601 } 1602 } 1603 1604 // **************** 1605 // Midi helpers 1606 // **************** 1607 1608 // FIXME: code the .mid file format, read and write 1609 1610 enum MidiEvent { 1611 NoteOff = 0x08, 1612 NoteOn = 0x09, 1613 NoteAftertouch = 0x0a, 1614 Controller = 0x0b, 1615 ProgramChange = 0x0c, // one param 1616 ChannelAftertouch = 0x0d, // one param 1617 PitchBend = 0x0e, 1618 } 1619 1620 enum MidiNote : ubyte { 1621 middleC = 60, 1622 A = 69, // 440 Hz 1623 As = 70, 1624 B = 71, 1625 C = 72, 1626 Cs = 73, 1627 D = 74, 1628 Ds = 75, 1629 E = 76, 1630 F = 77, 1631 Fs = 78, 1632 G = 79, 1633 Gs = 80, 1634 } 1635 1636 /// Puts a note on at the beginning of the passed slice, advancing it by the amount of the message size. 1637 /// Returns the message slice. 1638 /// 1639 /// See: http://www.midi.org/techspecs/midimessages.php 1640 ubyte[] midiNoteOn(ref ubyte[] where, ubyte channel, byte note, byte velocity) { 1641 where[0] = (MidiEvent.NoteOn << 4) | (channel&0x0f); 1642 where[1] = note; 1643 where[2] = velocity; 1644 auto it = where[0 .. 3]; 1645 where = where[3 .. $]; 1646 return it; 1647 } 1648 1649 /// Note off. 1650 ubyte[] midiNoteOff(ref ubyte[] where, ubyte channel, byte note, byte velocity) { 1651 where[0] = (MidiEvent.NoteOff << 4) | (channel&0x0f); 1652 where[1] = note; 1653 where[2] = velocity; 1654 auto it = where[0 .. 3]; 1655 where = where[3 .. $]; 1656 return it; 1657 } 1658 1659 /// Aftertouch. 1660 ubyte[] midiNoteAftertouch(ref ubyte[] where, ubyte channel, byte note, byte pressure) { 1661 where[0] = (MidiEvent.NoteAftertouch << 4) | (channel&0x0f); 1662 where[1] = note; 1663 where[2] = pressure; 1664 auto it = where[0 .. 3]; 1665 where = where[3 .. $]; 1666 return it; 1667 } 1668 1669 /// Controller. 1670 ubyte[] midiNoteController(ref ubyte[] where, ubyte channel, byte controllerNumber, byte controllerValue) { 1671 where[0] = (MidiEvent.Controller << 4) | (channel&0x0f); 1672 where[1] = controllerNumber; 1673 where[2] = controllerValue; 1674 auto it = where[0 .. 3]; 1675 where = where[3 .. $]; 1676 return it; 1677 } 1678 1679 /// Program change. 1680 ubyte[] midiProgramChange(ref ubyte[] where, ubyte channel, byte program) { 1681 where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f); 1682 where[1] = program; 1683 auto it = where[0 .. 2]; 1684 where = where[2 .. $]; 1685 return it; 1686 } 1687 1688 /// Channel aftertouch. 1689 ubyte[] midiChannelAftertouch(ref ubyte[] where, ubyte channel, byte amount) { 1690 where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f); 1691 where[1] = amount; 1692 auto it = where[0 .. 2]; 1693 where = where[2 .. $]; 1694 return it; 1695 } 1696 1697 /// Pitch bend. FIXME doesn't work right 1698 ubyte[] midiNotePitchBend(ref ubyte[] where, ubyte channel, short change) { 1699 /* 1700 first byte is llllll 1701 second byte is mmmmmm 1702 1703 Pitch Bend Change. 0mmmmmmm This message is sent to indicate a change in the pitch bender (wheel or lever, typically). The pitch bender is measured by a fourteen bit value. Center (no pitch change) is 2000H. Sensitivity is a function of the transmitter. (llllll) are the least significant 7 bits. (mmmmmm) are the most significant 7 bits. 1704 */ 1705 where[0] = (MidiEvent.PitchBend << 4) | (channel&0x0f); 1706 // FIXME 1707 where[1] = 0; 1708 where[2] = 0; 1709 auto it = where[0 .. 3]; 1710 where = where[3 .. $]; 1711 return it; 1712 } 1713 1714 1715 // **************** 1716 // Wav helpers 1717 // **************** 1718 1719 // FIXME: the .wav file format should be here, read and write (at least basics) 1720 // as well as some kind helpers to generate some sounds. 1721 1722 // **************** 1723 // OS specific helper stuff follows 1724 // **************** 1725 1726 version(ALSA) 1727 // Opens the PCM device with default settings: stereo, 16 bit, 44.1 kHz, interleaved R/W. 1728 snd_pcm_t* openAlsaPcm(snd_pcm_stream_t direction, int SampleRate, int channels) { 1729 snd_pcm_t* handle; 1730 snd_pcm_hw_params_t* hwParams; 1731 1732 /* Open PCM and initialize hardware */ 1733 1734 if (auto err = snd_pcm_open(&handle, cardName, direction, 0)) 1735 throw new AlsaException("open device", err); 1736 scope(failure) 1737 snd_pcm_close(handle); 1738 1739 1740 if (auto err = snd_pcm_hw_params_malloc(&hwParams)) 1741 throw new AlsaException("params malloc", err); 1742 scope(exit) 1743 snd_pcm_hw_params_free(hwParams); 1744 1745 if (auto err = snd_pcm_hw_params_any(handle, hwParams)) 1746 // can actually survive a failure here, we will just move forward 1747 {} // throw new AlsaException("params init", err); 1748 1749 if (auto err = snd_pcm_hw_params_set_access(handle, hwParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED)) 1750 throw new AlsaException("params access", err); 1751 1752 if (auto err = snd_pcm_hw_params_set_format(handle, hwParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE)) 1753 throw new AlsaException("params format", err); 1754 1755 uint rate = SampleRate; 1756 int dir = 0; 1757 if (auto err = snd_pcm_hw_params_set_rate_near(handle, hwParams, &rate, &dir)) 1758 throw new AlsaException("params rate", err); 1759 1760 assert(rate == SampleRate); // cheap me 1761 1762 if (auto err = snd_pcm_hw_params_set_channels(handle, hwParams, channels)) 1763 throw new AlsaException("params channels", err); 1764 1765 uint periods = 2; 1766 { 1767 auto err = snd_pcm_hw_params_set_periods_near(handle, hwParams, &periods, 0); 1768 if(err < 0) 1769 throw new AlsaException("periods", err); 1770 1771 snd_pcm_uframes_t sz = (BUFFER_SIZE_FRAMES * periods); 1772 err = snd_pcm_hw_params_set_buffer_size_near(handle, hwParams, &sz); 1773 if(err < 0) 1774 throw new AlsaException("buffer size", err); 1775 } 1776 1777 if (auto err = snd_pcm_hw_params(handle, hwParams)) 1778 throw new AlsaException("params install", err); 1779 1780 /* Setting up the callbacks */ 1781 1782 snd_pcm_sw_params_t* swparams; 1783 if(auto err = snd_pcm_sw_params_malloc(&swparams)) 1784 throw new AlsaException("sw malloc", err); 1785 scope(exit) 1786 snd_pcm_sw_params_free(swparams); 1787 if(auto err = snd_pcm_sw_params_current(handle, swparams)) 1788 throw new AlsaException("sw set", err); 1789 if(auto err = snd_pcm_sw_params_set_avail_min(handle, swparams, BUFFER_SIZE_FRAMES)) 1790 throw new AlsaException("sw min", err); 1791 if(auto err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0)) 1792 throw new AlsaException("sw threshold", err); 1793 if(auto err = snd_pcm_sw_params(handle, swparams)) 1794 throw new AlsaException("sw params", err); 1795 1796 /* finish setup */ 1797 1798 if (auto err = snd_pcm_prepare(handle)) 1799 throw new AlsaException("prepare", err); 1800 1801 assert(handle !is null); 1802 return handle; 1803 } 1804 1805 version(ALSA) 1806 class AlsaException : AudioException { 1807 this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1808 auto msg = snd_strerror(error); 1809 import core.stdc.string; 1810 super(cast(string) (message ~ ": " ~ msg[0 .. strlen(msg)]), file, line, next); 1811 } 1812 } 1813 1814 version(WinMM) 1815 class WinMMException : AudioException { 1816 this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1817 // FIXME: format the error 1818 // midiOutGetErrorText, etc. 1819 super(message, file, line, next); 1820 } 1821 } 1822 1823 // **************** 1824 // Bindings follow 1825 // **************** 1826 1827 version(ALSA) { 1828 extern(C): 1829 @nogc nothrow: 1830 pragma(lib, "asound"); 1831 private import core.sys.posix.poll; 1832 1833 const(char)* snd_strerror(int); 1834 1835 // pcm 1836 enum snd_pcm_stream_t { 1837 SND_PCM_STREAM_PLAYBACK, 1838 SND_PCM_STREAM_CAPTURE 1839 } 1840 1841 enum snd_pcm_access_t { 1842 /** mmap access with simple interleaved channels */ 1843 SND_PCM_ACCESS_MMAP_INTERLEAVED = 0, 1844 /** mmap access with simple non interleaved channels */ 1845 SND_PCM_ACCESS_MMAP_NONINTERLEAVED, 1846 /** mmap access with complex placement */ 1847 SND_PCM_ACCESS_MMAP_COMPLEX, 1848 /** snd_pcm_readi/snd_pcm_writei access */ 1849 SND_PCM_ACCESS_RW_INTERLEAVED, 1850 /** snd_pcm_readn/snd_pcm_writen access */ 1851 SND_PCM_ACCESS_RW_NONINTERLEAVED, 1852 SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED 1853 } 1854 1855 enum snd_pcm_format { 1856 /** Unknown */ 1857 SND_PCM_FORMAT_UNKNOWN = -1, 1858 /** Signed 8 bit */ 1859 SND_PCM_FORMAT_S8 = 0, 1860 /** Unsigned 8 bit */ 1861 SND_PCM_FORMAT_U8, 1862 /** Signed 16 bit Little Endian */ 1863 SND_PCM_FORMAT_S16_LE, 1864 /** Signed 16 bit Big Endian */ 1865 SND_PCM_FORMAT_S16_BE, 1866 /** Unsigned 16 bit Little Endian */ 1867 SND_PCM_FORMAT_U16_LE, 1868 /** Unsigned 16 bit Big Endian */ 1869 SND_PCM_FORMAT_U16_BE, 1870 /** Signed 24 bit Little Endian using low three bytes in 32-bit word */ 1871 SND_PCM_FORMAT_S24_LE, 1872 /** Signed 24 bit Big Endian using low three bytes in 32-bit word */ 1873 SND_PCM_FORMAT_S24_BE, 1874 /** Unsigned 24 bit Little Endian using low three bytes in 32-bit word */ 1875 SND_PCM_FORMAT_U24_LE, 1876 /** Unsigned 24 bit Big Endian using low three bytes in 32-bit word */ 1877 SND_PCM_FORMAT_U24_BE, 1878 /** Signed 32 bit Little Endian */ 1879 SND_PCM_FORMAT_S32_LE, 1880 /** Signed 32 bit Big Endian */ 1881 SND_PCM_FORMAT_S32_BE, 1882 /** Unsigned 32 bit Little Endian */ 1883 SND_PCM_FORMAT_U32_LE, 1884 /** Unsigned 32 bit Big Endian */ 1885 SND_PCM_FORMAT_U32_BE, 1886 /** Float 32 bit Little Endian, Range -1.0 to 1.0 */ 1887 SND_PCM_FORMAT_FLOAT_LE, 1888 /** Float 32 bit Big Endian, Range -1.0 to 1.0 */ 1889 SND_PCM_FORMAT_FLOAT_BE, 1890 /** Float 64 bit Little Endian, Range -1.0 to 1.0 */ 1891 SND_PCM_FORMAT_FLOAT64_LE, 1892 /** Float 64 bit Big Endian, Range -1.0 to 1.0 */ 1893 SND_PCM_FORMAT_FLOAT64_BE, 1894 /** IEC-958 Little Endian */ 1895 SND_PCM_FORMAT_IEC958_SUBFRAME_LE, 1896 /** IEC-958 Big Endian */ 1897 SND_PCM_FORMAT_IEC958_SUBFRAME_BE, 1898 /** Mu-Law */ 1899 SND_PCM_FORMAT_MU_LAW, 1900 /** A-Law */ 1901 SND_PCM_FORMAT_A_LAW, 1902 /** Ima-ADPCM */ 1903 SND_PCM_FORMAT_IMA_ADPCM, 1904 /** MPEG */ 1905 SND_PCM_FORMAT_MPEG, 1906 /** GSM */ 1907 SND_PCM_FORMAT_GSM, 1908 /** Special */ 1909 SND_PCM_FORMAT_SPECIAL = 31, 1910 /** Signed 24bit Little Endian in 3bytes format */ 1911 SND_PCM_FORMAT_S24_3LE = 32, 1912 /** Signed 24bit Big Endian in 3bytes format */ 1913 SND_PCM_FORMAT_S24_3BE, 1914 /** Unsigned 24bit Little Endian in 3bytes format */ 1915 SND_PCM_FORMAT_U24_3LE, 1916 /** Unsigned 24bit Big Endian in 3bytes format */ 1917 SND_PCM_FORMAT_U24_3BE, 1918 /** Signed 20bit Little Endian in 3bytes format */ 1919 SND_PCM_FORMAT_S20_3LE, 1920 /** Signed 20bit Big Endian in 3bytes format */ 1921 SND_PCM_FORMAT_S20_3BE, 1922 /** Unsigned 20bit Little Endian in 3bytes format */ 1923 SND_PCM_FORMAT_U20_3LE, 1924 /** Unsigned 20bit Big Endian in 3bytes format */ 1925 SND_PCM_FORMAT_U20_3BE, 1926 /** Signed 18bit Little Endian in 3bytes format */ 1927 SND_PCM_FORMAT_S18_3LE, 1928 /** Signed 18bit Big Endian in 3bytes format */ 1929 SND_PCM_FORMAT_S18_3BE, 1930 /** Unsigned 18bit Little Endian in 3bytes format */ 1931 SND_PCM_FORMAT_U18_3LE, 1932 /** Unsigned 18bit Big Endian in 3bytes format */ 1933 SND_PCM_FORMAT_U18_3BE, 1934 /* G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes */ 1935 SND_PCM_FORMAT_G723_24, 1936 /* G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte */ 1937 SND_PCM_FORMAT_G723_24_1B, 1938 /* G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes */ 1939 SND_PCM_FORMAT_G723_40, 1940 /* G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte */ 1941 SND_PCM_FORMAT_G723_40_1B, 1942 /* Direct Stream Digital (DSD) in 1-byte samples (x8) */ 1943 SND_PCM_FORMAT_DSD_U8, 1944 /* Direct Stream Digital (DSD) in 2-byte samples (x16) */ 1945 SND_PCM_FORMAT_DSD_U16_LE, 1946 SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U16_LE, 1947 1948 // I snipped a bunch of endian-specific ones! 1949 } 1950 1951 struct snd_pcm_t {} 1952 struct snd_pcm_hw_params_t {} 1953 struct snd_pcm_sw_params_t {} 1954 1955 int snd_pcm_open(snd_pcm_t**, const char*, snd_pcm_stream_t, int); 1956 int snd_pcm_close(snd_pcm_t*); 1957 int snd_pcm_pause(snd_pcm_t*, int); 1958 int snd_pcm_prepare(snd_pcm_t*); 1959 int snd_pcm_hw_params(snd_pcm_t*, snd_pcm_hw_params_t*); 1960 int snd_pcm_hw_params_set_periods(snd_pcm_t*, snd_pcm_hw_params_t*, uint, int); 1961 int snd_pcm_hw_params_set_periods_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int); 1962 int snd_pcm_hw_params_set_buffer_size(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t); 1963 int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t*); 1964 int snd_pcm_hw_params_set_channels(snd_pcm_t*, snd_pcm_hw_params_t*, uint); 1965 int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t**); 1966 void snd_pcm_hw_params_free(snd_pcm_hw_params_t*); 1967 int snd_pcm_hw_params_any(snd_pcm_t*, snd_pcm_hw_params_t*); 1968 int snd_pcm_hw_params_set_access(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_access_t); 1969 int snd_pcm_hw_params_set_format(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_format); 1970 int snd_pcm_hw_params_set_rate_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int*); 1971 1972 int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t**); 1973 void snd_pcm_sw_params_free(snd_pcm_sw_params_t*); 1974 1975 int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params); 1976 int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params); 1977 int snd_pcm_sw_params_set_avail_min(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 1978 int snd_pcm_sw_params_set_start_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 1979 int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 1980 1981 alias snd_pcm_sframes_t = c_long; 1982 alias snd_pcm_uframes_t = c_ulong; 1983 snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t*, const void*, snd_pcm_uframes_t size); 1984 snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t*, void*, snd_pcm_uframes_t size); 1985 1986 int snd_pcm_wait(snd_pcm_t *pcm, int timeout); 1987 snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm); 1988 snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm); 1989 1990 int snd_pcm_recover (snd_pcm_t* pcm, int err, int silent); 1991 1992 alias snd_lib_error_handler_t = void function (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...); 1993 int snd_lib_error_set_handler (snd_lib_error_handler_t handler); 1994 1995 import core.stdc.stdarg; 1996 private void alsa_message_silencer (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...) {} 1997 //k8: ALSAlib loves to trash stderr; shut it up 1998 void silence_alsa_messages () { snd_lib_error_set_handler(&alsa_message_silencer); } 1999 shared static this () { silence_alsa_messages(); } 2000 2001 // raw midi 2002 2003 static if(is(size_t == uint)) 2004 alias ssize_t = int; 2005 else 2006 alias ssize_t = long; 2007 2008 2009 struct snd_rawmidi_t {} 2010 int snd_rawmidi_open(snd_rawmidi_t**, snd_rawmidi_t**, const char*, int); 2011 int snd_rawmidi_close(snd_rawmidi_t*); 2012 int snd_rawmidi_drain(snd_rawmidi_t*); 2013 ssize_t snd_rawmidi_write(snd_rawmidi_t*, const void*, size_t); 2014 ssize_t snd_rawmidi_read(snd_rawmidi_t*, void*, size_t); 2015 2016 // mixer 2017 2018 struct snd_mixer_t {} 2019 struct snd_mixer_elem_t {} 2020 struct snd_mixer_selem_id_t {} 2021 2022 alias snd_mixer_elem_callback_t = int function(snd_mixer_elem_t*, uint); 2023 2024 int snd_mixer_open(snd_mixer_t**, int mode); 2025 int snd_mixer_close(snd_mixer_t*); 2026 int snd_mixer_attach(snd_mixer_t*, const char*); 2027 int snd_mixer_load(snd_mixer_t*); 2028 2029 // FIXME: those aren't actually void* 2030 int snd_mixer_selem_register(snd_mixer_t*, void*, void*); 2031 int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t**); 2032 void snd_mixer_selem_id_free(snd_mixer_selem_id_t*); 2033 void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t*, uint); 2034 void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t*, const char*); 2035 snd_mixer_elem_t* snd_mixer_find_selem(snd_mixer_t*, in snd_mixer_selem_id_t*); 2036 2037 // FIXME: the int should be an enum for channel identifier 2038 int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t*, int, c_long*); 2039 2040 int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t*, c_long*, c_long*); 2041 2042 int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t*, c_long); 2043 2044 void snd_mixer_elem_set_callback(snd_mixer_elem_t*, snd_mixer_elem_callback_t); 2045 int snd_mixer_poll_descriptors(snd_mixer_t*, pollfd*, uint space); 2046 2047 int snd_mixer_handle_events(snd_mixer_t*); 2048 2049 // FIXME: the first int should be an enum for channel identifier 2050 int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t*, int, int* value); 2051 int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t*, int); 2052 } 2053 2054 version(WinMM) { 2055 extern(Windows): 2056 @nogc nothrow: 2057 pragma(lib, "winmm"); 2058 import core.sys.windows.windows; 2059 2060 /* 2061 Windows functions include: 2062 http://msdn.microsoft.com/en-us/library/ms713762%28VS.85%29.aspx 2063 http://msdn.microsoft.com/en-us/library/ms713504%28v=vs.85%29.aspx 2064 http://msdn.microsoft.com/en-us/library/windows/desktop/dd798480%28v=vs.85%29.aspx# 2065 http://msdn.microsoft.com/en-US/subscriptions/ms712109.aspx 2066 */ 2067 2068 // pcm 2069 2070 // midi 2071 /+ 2072 alias HMIDIOUT = HANDLE; 2073 alias MMRESULT = UINT; 2074 2075 MMRESULT midiOutOpen(HMIDIOUT*, UINT, DWORD, DWORD, DWORD); 2076 MMRESULT midiOutClose(HMIDIOUT); 2077 MMRESULT midiOutReset(HMIDIOUT); 2078 MMRESULT midiOutShortMsg(HMIDIOUT, DWORD); 2079 2080 alias HWAVEOUT = HANDLE; 2081 2082 struct WAVEFORMATEX { 2083 WORD wFormatTag; 2084 WORD nChannels; 2085 DWORD nSamplesPerSec; 2086 DWORD nAvgBytesPerSec; 2087 WORD nBlockAlign; 2088 WORD wBitsPerSample; 2089 WORD cbSize; 2090 } 2091 2092 struct WAVEHDR { 2093 void* lpData; 2094 DWORD dwBufferLength; 2095 DWORD dwBytesRecorded; 2096 DWORD dwUser; 2097 DWORD dwFlags; 2098 DWORD dwLoops; 2099 WAVEHDR *lpNext; 2100 DWORD reserved; 2101 } 2102 2103 enum UINT WAVE_MAPPER= -1; 2104 2105 MMRESULT waveOutOpen(HWAVEOUT*, UINT_PTR, WAVEFORMATEX*, void* callback, void*, DWORD); 2106 MMRESULT waveOutClose(HWAVEOUT); 2107 MMRESULT waveOutPrepareHeader(HWAVEOUT, WAVEHDR*, UINT); 2108 MMRESULT waveOutUnprepareHeader(HWAVEOUT, WAVEHDR*, UINT); 2109 MMRESULT waveOutWrite(HWAVEOUT, WAVEHDR*, UINT); 2110 2111 MMRESULT waveOutGetVolume(HWAVEOUT, PDWORD); 2112 MMRESULT waveOutSetVolume(HWAVEOUT, DWORD); 2113 2114 enum CALLBACK_TYPEMASK = 0x70000; 2115 enum CALLBACK_NULL = 0; 2116 enum CALLBACK_WINDOW = 0x10000; 2117 enum CALLBACK_TASK = 0x20000; 2118 enum CALLBACK_FUNCTION = 0x30000; 2119 enum CALLBACK_THREAD = CALLBACK_TASK; 2120 enum CALLBACK_EVENT = 0x50000; 2121 2122 enum WAVE_FORMAT_PCM = 1; 2123 2124 enum WHDR_PREPARED = 2; 2125 enum WHDR_BEGINLOOP = 4; 2126 enum WHDR_ENDLOOP = 8; 2127 enum WHDR_INQUEUE = 16; 2128 2129 enum WinMMMessage : UINT { 2130 MM_JOY1MOVE = 0x3A0, 2131 MM_JOY2MOVE, 2132 MM_JOY1ZMOVE, 2133 MM_JOY2ZMOVE, // = 0x3A3 2134 MM_JOY1BUTTONDOWN = 0x3B5, 2135 MM_JOY2BUTTONDOWN, 2136 MM_JOY1BUTTONUP, 2137 MM_JOY2BUTTONUP, 2138 MM_MCINOTIFY, // = 0x3B9 2139 MM_WOM_OPEN = 0x3BB, 2140 MM_WOM_CLOSE, 2141 MM_WOM_DONE, 2142 MM_WIM_OPEN, 2143 MM_WIM_CLOSE, 2144 MM_WIM_DATA, 2145 MM_MIM_OPEN, 2146 MM_MIM_CLOSE, 2147 MM_MIM_DATA, 2148 MM_MIM_LONGDATA, 2149 MM_MIM_ERROR, 2150 MM_MIM_LONGERROR, 2151 MM_MOM_OPEN, 2152 MM_MOM_CLOSE, 2153 MM_MOM_DONE, // = 0x3C9 2154 MM_DRVM_OPEN = 0x3D0, 2155 MM_DRVM_CLOSE, 2156 MM_DRVM_DATA, 2157 MM_DRVM_ERROR, 2158 MM_STREAM_OPEN, 2159 MM_STREAM_CLOSE, 2160 MM_STREAM_DONE, 2161 MM_STREAM_ERROR, // = 0x3D7 2162 MM_MOM_POSITIONCB = 0x3CA, 2163 MM_MCISIGNAL, 2164 MM_MIM_MOREDATA, // = 0x3CC 2165 MM_MIXM_LINE_CHANGE = 0x3D0, 2166 MM_MIXM_CONTROL_CHANGE = 0x3D1 2167 } 2168 2169 2170 enum WOM_OPEN = WinMMMessage.MM_WOM_OPEN; 2171 enum WOM_CLOSE = WinMMMessage.MM_WOM_CLOSE; 2172 enum WOM_DONE = WinMMMessage.MM_WOM_DONE; 2173 enum WIM_OPEN = WinMMMessage.MM_WIM_OPEN; 2174 enum WIM_CLOSE = WinMMMessage.MM_WIM_CLOSE; 2175 enum WIM_DATA = WinMMMessage.MM_WIM_DATA; 2176 2177 2178 uint mciSendStringA(in char*,char*,uint,void*); 2179 2180 +/ 2181 } 2182 2183 private enum scriptable = "arsd_jsvar_compatible";