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 version(none) { 55 import iv.stb.vorbis; 56 57 int channels; 58 short* decoded; 59 auto v = new VorbisDecoder("test.ogg"); 60 61 auto ao = AudioOutput(0); 62 ao.fillData = (short[] buffer) { 63 auto got = v.getSamplesShortInterleaved(2, buffer.ptr, buffer.length); 64 if(got == 0) { 65 ao.stop(); 66 } 67 }; 68 69 ao.play(); 70 return; 71 } 72 73 74 75 76 auto thread = new AudioPcmOutThread(); 77 thread.start(); 78 79 thread.playOgg("test.ogg"); 80 81 Thread.sleep(5.seconds); 82 83 //Thread.sleep(150.msecs); 84 thread.beep(); 85 Thread.sleep(250.msecs); 86 thread.blip(); 87 Thread.sleep(250.msecs); 88 thread.boop(); 89 Thread.sleep(1000.msecs); 90 /* 91 thread.beep(800, 500); 92 Thread.sleep(500.msecs); 93 thread.beep(366, 500); 94 Thread.sleep(600.msecs); 95 thread.beep(800, 500); 96 thread.beep(366, 500); 97 Thread.sleep(500.msecs); 98 Thread.sleep(150.msecs); 99 thread.beep(200); 100 Thread.sleep(150.msecs); 101 thread.beep(100); 102 Thread.sleep(150.msecs); 103 thread.noise(); 104 Thread.sleep(150.msecs); 105 */ 106 107 108 thread.stop(); 109 110 thread.join(); 111 112 return; 113 114 /* 115 auto aio = AudioMixer(0); 116 117 import std.stdio; 118 writeln(aio.muteMaster); 119 */ 120 121 /* 122 mciSendStringA("play test.wav", null, 0, null); 123 Sleep(3000); 124 import std.stdio; 125 if(auto err = mciSendStringA("play test2.wav", null, 0, null)) 126 writeln(err); 127 Sleep(6000); 128 return; 129 */ 130 131 // output about a second of random noise to demo PCM 132 auto ao = AudioOutput(0); 133 short[BUFFER_SIZE_SHORT] randomSpam = void; 134 import core.stdc.stdlib; 135 foreach(ref s; randomSpam) 136 s = cast(short)((cast(short) rand()) - short.max / 2); 137 138 int loopCount = 40; 139 140 //import std.stdio; 141 //writeln("Should be about ", loopCount * BUFFER_SIZE_FRAMES * 1000 / SampleRate, " microseconds"); 142 143 int loops = 0; 144 // only do simple stuff in here like fill the data, set simple 145 // variables, or call stop anything else might cause deadlock 146 ao.fillData = (short[] buffer) { 147 buffer[] = randomSpam[0 .. buffer.length]; 148 loops++; 149 if(loops == loopCount) 150 ao.stop(); 151 }; 152 153 ao.play(); 154 155 return; 156 157 // Play a C major scale on the piano to demonstrate midi 158 auto midi = MidiOutput(0); 159 160 ubyte[16] buffer = void; 161 ubyte[] where = buffer[]; 162 midi.writeRawMessageData(where.midiProgramChange(1, 1)); 163 for(ubyte note = MidiNote.C; note <= MidiNote.C + 12; note++) { 164 where = buffer[]; 165 midi.writeRawMessageData(where.midiNoteOn(1, note, 127)); 166 import core.thread; 167 Thread.sleep(dur!"msecs"(500)); 168 midi.writeRawMessageData(where.midiNoteOff(1, note, 127)); 169 170 if(note != 76 && note != 83) 171 note++; 172 } 173 import core.thread; 174 Thread.sleep(dur!"msecs"(500)); // give the last note a chance to finish 175 } 176 177 import core.thread; 178 /++ 179 Makes an audio thread for you that you can make 180 various sounds on and it will mix them with good 181 enough latency for simple games. 182 183 --- 184 auto audio = new AudioPcmOutThread(); 185 audio.start(); 186 scope(exit) { 187 audio.stop(); 188 audio.join(); 189 } 190 191 audio.beep(); 192 193 // you need to keep the main program alive long enough 194 // to keep this thread going to hear anything 195 Thread.sleep(1.seconds); 196 --- 197 +/ 198 final class AudioPcmOutThread : Thread { 199 /// 200 this() { 201 this.isDaemon = true; 202 version(linux) { 203 // this thread has no business intercepting signals from the main thread, 204 // so gonna block a couple of them 205 import core.sys.posix.signal; 206 sigset_t sigset; 207 auto err = sigfillset(&sigset); 208 assert(!err); 209 err = sigprocmask(SIG_BLOCK, &sigset, null); 210 assert(!err); 211 } 212 213 super(&run); 214 } 215 216 /// 217 void stop() { 218 if(ao) { 219 ao.stop(); 220 } 221 } 222 223 /// Args in hertz and milliseconds 224 void beep(int freq = 900, int dur = 150, int volume = DEFAULT_VOLUME) { 225 Sample s; 226 s.operation = 0; // square wave 227 s.frequency = SampleRate / freq; 228 s.duration = dur * SampleRate / 1000; 229 s.volume = volume; 230 addSample(s); 231 } 232 233 /// 234 void noise(int dur = 150, int volume = DEFAULT_VOLUME) { 235 Sample s; 236 s.operation = 1; // noise 237 s.frequency = 0; 238 s.volume = volume; 239 s.duration = dur * SampleRate / 1000; 240 addSample(s); 241 } 242 243 /// 244 void boop(float attack = 8, int freqBase = 500, int dur = 150, int volume = DEFAULT_VOLUME) { 245 Sample s; 246 s.operation = 5; // custom 247 s.volume = volume; 248 s.duration = dur * SampleRate / 1000; 249 s.f = delegate short(int x) { 250 auto currentFrequency = cast(float) freqBase / (1 + cast(float) x / (cast(float) SampleRate / attack)); 251 import std.math; 252 auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency); 253 return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100); 254 }; 255 addSample(s); 256 } 257 258 /// 259 void blip(float attack = 6, int freqBase = 800, int dur = 150, int volume = DEFAULT_VOLUME) { 260 Sample s; 261 s.operation = 5; // custom 262 s.volume = volume; 263 s.duration = dur * SampleRate / 1000; 264 s.f = delegate short(int x) { 265 auto currentFrequency = cast(float) freqBase * (1 + cast(float) x / (cast(float) SampleRate / attack)); 266 import std.math; 267 auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency); 268 return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100); 269 }; 270 addSample(s); 271 } 272 273 version(none) 274 void custom(int dur = 150, int volume = DEFAULT_VOLUME) { 275 Sample s; 276 s.operation = 5; // custom 277 s.volume = volume; 278 s.duration = dur * SampleRate / 1000; 279 s.f = delegate short(int x) { 280 auto currentFrequency = 500.0 / (1 + cast(float) x / (cast(float) SampleRate / 8)); 281 import std.math; 282 auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency); 283 return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100); 284 }; 285 addSample(s); 286 } 287 288 /// Requires vorbis.d to be compiled in (module arsd.vorbis) 289 void playOgg()(string filename, bool loop = false) { 290 import arsd.vorbis; 291 292 auto v = new VorbisDecoder(filename); 293 294 addChannel( 295 delegate bool(short[] buffer) { 296 if(cast(int) buffer.length != buffer.length) 297 throw new Exception("eeeek"); 298 auto got = v.getSamplesShortInterleaved(2, buffer.ptr, cast(int) buffer.length); 299 if(got == 0) { 300 if(loop) { 301 v.seekStart(); 302 return true; 303 } 304 305 return false; 306 } 307 return true; 308 } 309 ); 310 } 311 312 313 struct Sample { 314 int operation; 315 int frequency; /* in samples */ 316 int duration; /* in samples */ 317 int volume; /* between 1 and 100. You should generally shoot for something lowish, like 20. */ 318 int delay; /* in samples */ 319 320 int x; 321 short delegate(int x) f; 322 } 323 324 final void addSample(Sample currentSample) { 325 int frequencyCounter; 326 short val = cast(short) (cast(int) short.max * currentSample.volume / 100); 327 addChannel( 328 delegate bool (short[] buffer) { 329 if(currentSample.duration) { 330 size_t i = 0; 331 if(currentSample.delay) { 332 if(buffer.length <= currentSample.delay * 2) { 333 // whole buffer consumed by delay 334 buffer[] = 0; 335 currentSample.delay -= buffer.length / 2; 336 } else { 337 i = currentSample.delay * 2; 338 buffer[0 .. i] = 0; 339 currentSample.delay = 0; 340 } 341 } 342 if(currentSample.delay > 0) 343 return true; 344 345 size_t sampleFinish; 346 if(currentSample.duration * 2 <= buffer.length) { 347 sampleFinish = currentSample.duration * 2; 348 currentSample.duration = 0; 349 } else { 350 sampleFinish = buffer.length; 351 currentSample.duration -= buffer.length / 2; 352 } 353 354 switch(currentSample.operation) { 355 case 0: // square wave 356 for(; i < sampleFinish; i++) { 357 buffer[i] = val; 358 // left and right do the same thing so we only count 359 // every other sample 360 if(i & 1) { 361 if(frequencyCounter) 362 frequencyCounter--; 363 if(frequencyCounter == 0) { 364 // are you kidding me dmd? random casts suck 365 val = cast(short) -cast(int)(val); 366 frequencyCounter = currentSample.frequency / 2; 367 } 368 } 369 } 370 break; 371 case 1: // noise 372 for(; i < sampleFinish; i++) { 373 import std.random; 374 buffer[i] = uniform(cast(short) -cast(int)val, val); 375 } 376 break; 377 /+ 378 case 2: // triangle wave 379 for(; i < sampleFinish; i++) { 380 buffer[i] = val; 381 // left and right do the same thing so we only count 382 // every other sample 383 if(i & 1) { 384 if(frequencyCounter) 385 frequencyCounter--; 386 if(frequencyCounter == 0) { 387 val = 0; 388 frequencyCounter = currentSample.frequency / 2; 389 } 390 } 391 } 392 393 break; 394 case 3: // sawtooth wave 395 case 4: // sine wave 396 +/ 397 case 5: // custom function 398 val = currentSample.f(currentSample.x); 399 for(; i < sampleFinish; i++) { 400 buffer[i] = val; 401 if(i & 1) { 402 currentSample.x++; 403 val = currentSample.f(currentSample.x); 404 } 405 } 406 break; 407 default: // unknown; use silence 408 currentSample.duration = 0; 409 } 410 411 if(i < buffer.length) 412 buffer[i .. $] = 0; 413 414 return currentSample.duration > 0; 415 } else { 416 return false; 417 } 418 } 419 ); 420 } 421 422 /++ 423 The delegate returns false when it is finished (true means keep going). 424 It must fill the buffer with waveform data on demand and must be latency 425 sensitive; as fast as possible. 426 +/ 427 public void addChannel(bool delegate(short[] buffer) dg) { 428 synchronized(this) { 429 // silently drops info if we don't have room in the buffer... 430 // don't do a lot of long running things lol 431 if(fillDatasLength < fillDatas.length) 432 fillDatas[fillDatasLength++] = dg; 433 } 434 } 435 436 private { 437 AudioOutput* ao; 438 439 bool delegate(short[] buffer)[32] fillDatas; 440 int fillDatasLength = 0; 441 } 442 443 private void run() { 444 AudioOutput ao = AudioOutput(0); 445 this.ao = &ao; 446 ao.fillData = (short[] buffer) { 447 short[BUFFER_SIZE_SHORT] bfr; 448 bool first = true; 449 if(fillDatasLength) { 450 for(int idx = 0; idx < fillDatasLength; idx++) { 451 auto dg = fillDatas[idx]; 452 auto ret = dg(bfr[0 .. buffer.length][]); 453 foreach(i, v; bfr[0 .. buffer.length][]) { 454 int val; 455 if(first) 456 val = 0; 457 else 458 val = buffer[i]; 459 460 int a = val; 461 int b = v; 462 int cap = a + b; 463 if(cap > short.max) cap = short.max; 464 else if(cap < short.min) cap = short.min; 465 val = cast(short) cap; 466 buffer[i] = cast(short) val; 467 } 468 if(!ret) { 469 // it returned false meaning this one is finished... 470 synchronized(this) { 471 fillDatas[idx] = fillDatas[fillDatasLength - 1]; 472 fillDatasLength--; 473 } 474 idx--; 475 } 476 477 first = false; 478 } 479 } else { 480 buffer[] = 0; 481 } 482 }; 483 ao.play(); 484 } 485 } 486 487 488 import core.stdc.config; 489 490 version(linux) version=ALSA; 491 version(Windows) version=WinMM; 492 493 enum SampleRate = 44100; 494 495 version(ALSA) { 496 enum cardName = "default"; 497 498 // this is the virtual rawmidi device on my computer at least 499 // maybe later i'll make it probe 500 // 501 // Getting midi to actually play on Linux is a bit of a pain. 502 // Here's what I did: 503 /* 504 # load the kernel driver, if amidi -l gives ioctl error, 505 # you haven't done this yet! 506 modprobe snd-virmidi 507 508 # start a software synth. timidity -iA is also an option 509 fluidsynth soundfont.sf2 510 511 # connect the virtual hardware port to the synthesizer 512 aconnect 24:0 128:0 513 514 515 I might also add a snd_seq client here which is a bit 516 easier to setup but for now I'm using the rawmidi so you 517 gotta get them connected somehow. 518 */ 519 enum midiName = "hw:3,0"; 520 } 521 522 /// Thrown on audio failures. 523 /// Subclass this to provide OS-specific exceptions 524 class AudioException : Exception { 525 this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 526 super(message, file, line, next); 527 } 528 } 529 530 /// Gives PCM input access (such as a microphone). 531 version(ALSA) // FIXME 532 struct AudioInput { 533 version(ALSA) { 534 snd_pcm_t* handle; 535 } 536 537 @disable this(); 538 @disable this(this); 539 540 /// Always pass card == 0. 541 this(int card) { 542 assert(card == 0); 543 544 version(ALSA) { 545 handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_CAPTURE); 546 } else static assert(0); 547 } 548 549 /// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz 550 /// Each item in the array thus alternates between left and right channel 551 /// and it takes a total of 88,200 items to make one second of sound. 552 /// 553 /// Returns the slice of the buffer actually read into 554 short[] read(short[] buffer) { 555 version(ALSA) { 556 snd_pcm_sframes_t read; 557 558 read = snd_pcm_readi(handle, buffer.ptr, buffer.length / 2 /* div number of channels apparently */); 559 if(read < 0) 560 throw new AlsaException("pcm read", cast(int)read); 561 562 return buffer[0 .. read * 2]; 563 } else static assert(0); 564 } 565 566 // FIXME: add async function hooks 567 568 ~this() { 569 version(ALSA) { 570 snd_pcm_close(handle); 571 } else static assert(0); 572 } 573 } 574 575 /// Gives PCM output access (such as the speakers). 576 struct AudioOutput { 577 version(ALSA) { 578 snd_pcm_t* handle; 579 } else version(WinMM) { 580 HWAVEOUT handle; 581 } 582 583 @disable this(); 584 // This struct must NEVER be moved or copied, a pointer to it may 585 // be passed to a device driver and stored! 586 @disable this(this); 587 588 /// Always pass card == 0. 589 this(int card) { 590 assert(card == 0); 591 592 version(ALSA) { 593 handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK); 594 } else version(WinMM) { 595 WAVEFORMATEX format; 596 format.wFormatTag = WAVE_FORMAT_PCM; 597 format.nChannels = 2; 598 format.nSamplesPerSec = SampleRate; 599 format.nAvgBytesPerSec = SampleRate * 2 * 2; // two channels, two bytes per sample 600 format.nBlockAlign = 4; 601 format.wBitsPerSample = 16; 602 format.cbSize = 0; 603 if(auto err = waveOutOpen(&handle, WAVE_MAPPER, &format, &mmCallback, &this, CALLBACK_FUNCTION)) 604 throw new WinMMException("wave out open", err); 605 } else static assert(0); 606 } 607 608 /// passes a buffer of data to fill 609 /// 610 /// Data is assumed to be interleaved stereo, LE 16 bit, 44.1 kHz 611 /// Each item in the array thus alternates between left and right channel 612 /// and it takes a total of 88,200 items to make one second of sound. 613 void delegate(short[]) fillData; 614 615 shared(bool) playing = false; // considered to be volatile 616 617 /// Starts playing, loops until stop is called 618 void play() { 619 assert(fillData !is null); 620 playing = true; 621 622 version(ALSA) { 623 short[BUFFER_SIZE_SHORT] buffer; 624 while(playing) { 625 auto err = snd_pcm_wait(handle, 500); 626 if(err < 0) 627 throw new AlsaException("uh oh", err); 628 // err == 0 means timeout 629 // err == 1 means ready 630 631 auto ready = snd_pcm_avail_update(handle); 632 if(ready < 0) 633 throw new AlsaException("avail", cast(int)ready); 634 if(ready > BUFFER_SIZE_FRAMES) 635 ready = BUFFER_SIZE_FRAMES; 636 //import std.stdio; writeln("filling ", ready); 637 fillData(buffer[0 .. ready * 2]); 638 if(playing) { 639 snd_pcm_sframes_t written; 640 auto data = buffer[0 .. ready * 2]; 641 642 while(data.length) { 643 written = snd_pcm_writei(handle, data.ptr, data.length / 2); 644 if(written < 0) { 645 written = snd_pcm_recover(handle, cast(int)written, 0); 646 if (written < 0) throw new AlsaException("pcm write", cast(int)written); 647 } 648 data = data[written * 2 .. $]; 649 } 650 } 651 } 652 } else version(WinMM) { 653 654 enum numBuffers = 4; // use a lot of buffers to get smooth output with Sleep, see below 655 short[BUFFER_SIZE_SHORT][numBuffers] buffers; 656 657 WAVEHDR[numBuffers] headers; 658 659 foreach(i, ref header; headers) { 660 // since this is wave out, it promises not to write... 661 auto buffer = buffers[i][]; 662 header.lpData = cast(void*) buffer.ptr; 663 header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof; 664 header.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP; 665 header.dwLoops = 1; 666 667 if(auto err = waveOutPrepareHeader(handle, &header, header.sizeof)) 668 throw new WinMMException("prepare header", err); 669 670 // prime it 671 fillData(buffer[]); 672 673 // indicate that they are filled and good to go 674 header.dwUser = 1; 675 } 676 677 while(playing) { 678 // and queue both to be played, if they are ready 679 foreach(ref header; headers) 680 if(header.dwUser) { 681 if(auto err = waveOutWrite(handle, &header, header.sizeof)) 682 throw new WinMMException("wave out write", err); 683 header.dwUser = 0; 684 } 685 Sleep(1); 686 // the system resolution may be lower than this sleep. To avoid gaps 687 // in output, we use multiple buffers. Might introduce latency, not 688 // sure how best to fix. I don't want to busy loop... 689 } 690 691 // wait for the system to finish with our buffers 692 bool anyInUse = true; 693 694 while(anyInUse) { 695 anyInUse = false; 696 foreach(header; headers) { 697 if(!header.dwUser) { 698 anyInUse = true; 699 break; 700 } 701 } 702 if(anyInUse) 703 Sleep(1); 704 } 705 706 foreach(ref header; headers) 707 if(auto err = waveOutUnprepareHeader(handle, &header, header.sizeof)) 708 throw new WinMMException("unprepare", err); 709 } else static assert(0); 710 } 711 712 /// Breaks the play loop 713 void stop() { 714 playing = false; 715 } 716 717 version(WinMM) { 718 extern(Windows) 719 static void mmCallback(HWAVEOUT handle, UINT msg, void* userData, DWORD param1, DWORD param2) { 720 AudioOutput* ao = cast(AudioOutput*) userData; 721 if(msg == WOM_DONE) { 722 auto header = cast(WAVEHDR*) param1; 723 // we want to bounce back and forth between two buffers 724 // to keep the sound going all the time 725 if(ao.playing) { 726 ao.fillData((cast(short*) header.lpData)[0 .. header.dwBufferLength / short.sizeof]); 727 } 728 header.dwUser = 1; 729 } 730 } 731 } 732 733 // FIXME: add async function hooks 734 735 ~this() { 736 version(ALSA) { 737 snd_pcm_close(handle); 738 } else version(WinMM) { 739 waveOutClose(handle); 740 } else static assert(0); 741 } 742 } 743 744 /// Gives MIDI output access. 745 struct MidiOutput { 746 version(ALSA) { 747 snd_rawmidi_t* handle; 748 } else version(WinMM) { 749 HMIDIOUT handle; 750 } 751 752 @disable this(); 753 @disable this(this); 754 755 /// Always pass card == 0. 756 this(int card) { 757 assert(card == 0); 758 759 version(ALSA) { 760 if(auto err = snd_rawmidi_open(null, &handle, midiName, 0)) 761 throw new AlsaException("rawmidi open", err); 762 } else version(WinMM) { 763 if(auto err = midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL)) 764 throw new WinMMException("midi out open", err); 765 } else static assert(0); 766 } 767 768 void silenceAllNotes() { 769 foreach(a; 0 .. 16) 770 writeMidiMessage((0x0b << 4)|a /*MIDI_EVENT_CONTROLLER*/, 123, 0); 771 } 772 773 /// Send a reset message, silencing all notes 774 void reset() { 775 version(ALSA) { 776 static immutable ubyte[3] resetSequence = [0x0b << 4, 123, 0]; 777 // send a controller event to reset it 778 writeRawMessageData(resetSequence[]); 779 // and flush it immediately 780 snd_rawmidi_drain(handle); 781 } else version(WinMM) { 782 if(auto error = midiOutReset(handle)) 783 throw new WinMMException("midi reset", error); 784 } else static assert(0); 785 } 786 787 /// Writes a single low-level midi message 788 /// Timing and sending sane data is your responsibility! 789 void writeMidiMessage(int status, int param1, int param2) { 790 version(ALSA) { 791 ubyte[3] dataBuffer; 792 793 dataBuffer[0] = cast(ubyte) status; 794 dataBuffer[1] = cast(ubyte) param1; 795 dataBuffer[2] = cast(ubyte) param2; 796 797 auto msg = status >> 4; 798 ubyte[] data; 799 if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) 800 data = dataBuffer[0 .. 2]; 801 else 802 data = dataBuffer[]; 803 804 writeRawMessageData(data); 805 } else version(WinMM) { 806 DWORD word = (param2 << 16) | (param1 << 8) | status; 807 if(auto error = midiOutShortMsg(handle, word)) 808 throw new WinMMException("midi out", error); 809 } else static assert(0); 810 811 } 812 813 /// Writes a series of individual raw messages. 814 /// Timing and sending sane data is your responsibility! 815 /// The data should NOT include any timestamp bytes - each midi message should be 2 or 3 bytes. 816 void writeRawMessageData(scope const(ubyte)[] data) { 817 version(ALSA) { 818 ssize_t written; 819 820 while(data.length) { 821 written = snd_rawmidi_write(handle, data.ptr, data.length); 822 if(written < 0) 823 throw new AlsaException("midi write", cast(int) written); 824 data = data[cast(int) written .. $]; 825 } 826 } else version(WinMM) { 827 while(data.length) { 828 auto msg = data[0] >> 4; 829 if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) { 830 writeMidiMessage(data[0], data[1], 0); 831 data = data[2 .. $]; 832 } else { 833 writeMidiMessage(data[0], data[1], data[2]); 834 data = data[3 .. $]; 835 } 836 } 837 } else static assert(0); 838 } 839 840 ~this() { 841 version(ALSA) { 842 snd_rawmidi_close(handle); 843 } else version(WinMM) { 844 midiOutClose(handle); 845 } else static assert(0); 846 } 847 } 848 849 850 // FIXME: maybe add a PC speaker beep function for completeness 851 852 /// Interfaces with the default sound card. You should only have a single instance of this and it should 853 /// be stack allocated, so its destructor cleans up after it. 854 /// 855 /// A mixer gives access to things like volume controls and mute buttons. It should also give a 856 /// callback feature to alert you of when the settings are changed by another program. 857 version(ALSA) // FIXME 858 struct AudioMixer { 859 // To port to a new OS: put the data in the right version blocks 860 // then implement each function. Leave else static assert(0) at the 861 // end of each version group in a function so it is easier to implement elsewhere later. 862 // 863 // If a function is only relevant on your OS, put the whole function in a version block 864 // and give it an OS specific name of some sort. 865 // 866 // Feel free to do that btw without worrying about lowest common denominator: we want low level access when we want it. 867 // 868 // Put necessary bindings at the end of the file, or use an import if you like, but I prefer these files to be standalone. 869 version(ALSA) { 870 snd_mixer_t* handle; 871 snd_mixer_selem_id_t* sid; 872 snd_mixer_elem_t* selem; 873 874 c_long maxVolume, minVolume; // these are ok to use if you are writing ALSA specific code i guess 875 876 enum selemName = "Master"; 877 } 878 879 @disable this(); 880 @disable this(this); 881 882 /// Only cardId == 0 is supported 883 this(int cardId) { 884 assert(cardId == 0, "Pass 0 to use default sound card."); 885 886 version(ALSA) { 887 if(auto err = snd_mixer_open(&handle, 0)) 888 throw new AlsaException("open sound", err); 889 scope(failure) 890 snd_mixer_close(handle); 891 if(auto err = snd_mixer_attach(handle, cardName)) 892 throw new AlsaException("attach to sound card", err); 893 if(auto err = snd_mixer_selem_register(handle, null, null)) 894 throw new AlsaException("register mixer", err); 895 if(auto err = snd_mixer_load(handle)) 896 throw new AlsaException("load mixer", err); 897 898 if(auto err = snd_mixer_selem_id_malloc(&sid)) 899 throw new AlsaException("master channel open", err); 900 scope(failure) 901 snd_mixer_selem_id_free(sid); 902 snd_mixer_selem_id_set_index(sid, 0); 903 snd_mixer_selem_id_set_name(sid, selemName); 904 selem = snd_mixer_find_selem(handle, sid); 905 if(selem is null) 906 throw new AlsaException("find master element", 0); 907 908 if(auto err = snd_mixer_selem_get_playback_volume_range(selem, &minVolume, &maxVolume)) 909 throw new AlsaException("get volume range", err); 910 911 version(with_eventloop) { 912 import arsd.eventloop; 913 addFileEventListeners(getAlsaFileDescriptors()[0], &eventListener, null, null); 914 setAlsaElemCallback(&alsaCallback); 915 } 916 } else static assert(0); 917 } 918 919 ~this() { 920 version(ALSA) { 921 version(with_eventloop) { 922 import arsd.eventloop; 923 removeFileEventListeners(getAlsaFileDescriptors()[0]); 924 } 925 snd_mixer_selem_id_free(sid); 926 snd_mixer_close(handle); 927 } else static assert(0); 928 } 929 930 version(ALSA) 931 version(with_eventloop) { 932 static struct MixerEvent {} 933 nothrow @nogc 934 extern(C) static int alsaCallback(snd_mixer_elem_t*, uint) { 935 import arsd.eventloop; 936 try 937 send(MixerEvent()); 938 catch(Exception) 939 return 1; 940 941 return 0; 942 } 943 944 void eventListener(int fd) { 945 handleAlsaEvents(); 946 } 947 } 948 949 /// Gets the master channel's mute state 950 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 951 @property bool muteMaster() { 952 version(ALSA) { 953 int result; 954 if(auto err = snd_mixer_selem_get_playback_switch(selem, 0, &result)) 955 throw new AlsaException("get mute state", err); 956 return result == 0; 957 } else static assert(0); 958 } 959 960 /// Mutes or unmutes the master channel 961 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 962 @property void muteMaster(bool mute) { 963 version(ALSA) { 964 if(auto err = snd_mixer_selem_set_playback_switch_all(selem, mute ? 0 : 1)) 965 throw new AlsaException("set mute state", err); 966 } else static assert(0); 967 } 968 969 /// returns a percentage, between 0 and 100 (inclusive) 970 int getMasterVolume() { 971 version(ALSA) { 972 auto volume = getMasterVolumeExact(); 973 return cast(int)(volume * 100 / (maxVolume - minVolume)); 974 } else static assert(0); 975 } 976 977 /// Gets the exact value returned from the operating system. The range may vary. 978 int getMasterVolumeExact() { 979 version(ALSA) { 980 c_long volume; 981 snd_mixer_selem_get_playback_volume(selem, 0, &volume); 982 return cast(int)volume; 983 } else static assert(0); 984 } 985 986 /// sets a percentage on the volume, so it must be 0 <= volume <= 100 987 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 988 void setMasterVolume(int volume) { 989 version(ALSA) { 990 assert(volume >= 0 && volume <= 100); 991 setMasterVolumeExact(cast(int)(volume * (maxVolume - minVolume) / 100)); 992 } else static assert(0); 993 } 994 995 /// Sets an exact volume. Must be in range of the OS provided min and max. 996 void setMasterVolumeExact(int volume) { 997 version(ALSA) { 998 if(auto err = snd_mixer_selem_set_playback_volume_all(selem, volume)) 999 throw new AlsaException("set volume", err); 1000 } else static assert(0); 1001 } 1002 1003 version(ALSA) { 1004 /// Gets the ALSA descriptors which you can watch for events 1005 /// on using regular select, poll, epoll, etc. 1006 int[] getAlsaFileDescriptors() { 1007 import core.sys.posix.poll; 1008 pollfd[32] descriptors = void; 1009 int got = snd_mixer_poll_descriptors(handle, descriptors.ptr, descriptors.length); 1010 int[] result; 1011 result.length = got; 1012 foreach(i, desc; descriptors[0 .. got]) 1013 result[i] = desc.fd; 1014 return result; 1015 } 1016 1017 /// When the FD is ready, call this to let ALSA do its thing. 1018 void handleAlsaEvents() { 1019 snd_mixer_handle_events(handle); 1020 } 1021 1022 /// Set a callback for the master volume change events. 1023 void setAlsaElemCallback(snd_mixer_elem_callback_t dg) { 1024 snd_mixer_elem_set_callback(selem, dg); 1025 } 1026 } 1027 } 1028 1029 // **************** 1030 // Midi helpers 1031 // **************** 1032 1033 // FIXME: code the .mid file format, read and write 1034 1035 enum MidiEvent { 1036 NoteOff = 0x08, 1037 NoteOn = 0x09, 1038 NoteAftertouch = 0x0a, 1039 Controller = 0x0b, 1040 ProgramChange = 0x0c, // one param 1041 ChannelAftertouch = 0x0d, // one param 1042 PitchBend = 0x0e, 1043 } 1044 1045 enum MidiNote : ubyte { 1046 middleC = 60, 1047 A = 69, // 440 Hz 1048 As = 70, 1049 B = 71, 1050 C = 72, 1051 Cs = 73, 1052 D = 74, 1053 Ds = 75, 1054 E = 76, 1055 F = 77, 1056 Fs = 78, 1057 G = 79, 1058 Gs = 80, 1059 } 1060 1061 /// Puts a note on at the beginning of the passed slice, advancing it by the amount of the message size. 1062 /// Returns the message slice. 1063 /// 1064 /// See: http://www.midi.org/techspecs/midimessages.php 1065 ubyte[] midiNoteOn(ref ubyte[] where, ubyte channel, byte note, byte velocity) { 1066 where[0] = (MidiEvent.NoteOn << 4) | (channel&0x0f); 1067 where[1] = note; 1068 where[2] = velocity; 1069 auto it = where[0 .. 3]; 1070 where = where[3 .. $]; 1071 return it; 1072 } 1073 1074 /// Note off. 1075 ubyte[] midiNoteOff(ref ubyte[] where, ubyte channel, byte note, byte velocity) { 1076 where[0] = (MidiEvent.NoteOff << 4) | (channel&0x0f); 1077 where[1] = note; 1078 where[2] = velocity; 1079 auto it = where[0 .. 3]; 1080 where = where[3 .. $]; 1081 return it; 1082 } 1083 1084 /// Aftertouch. 1085 ubyte[] midiNoteAftertouch(ref ubyte[] where, ubyte channel, byte note, byte pressure) { 1086 where[0] = (MidiEvent.NoteAftertouch << 4) | (channel&0x0f); 1087 where[1] = note; 1088 where[2] = pressure; 1089 auto it = where[0 .. 3]; 1090 where = where[3 .. $]; 1091 return it; 1092 } 1093 1094 /// Controller. 1095 ubyte[] midiNoteController(ref ubyte[] where, ubyte channel, byte controllerNumber, byte controllerValue) { 1096 where[0] = (MidiEvent.Controller << 4) | (channel&0x0f); 1097 where[1] = controllerNumber; 1098 where[2] = controllerValue; 1099 auto it = where[0 .. 3]; 1100 where = where[3 .. $]; 1101 return it; 1102 } 1103 1104 /// Program change. 1105 ubyte[] midiProgramChange(ref ubyte[] where, ubyte channel, byte program) { 1106 where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f); 1107 where[1] = program; 1108 auto it = where[0 .. 2]; 1109 where = where[2 .. $]; 1110 return it; 1111 } 1112 1113 /// Channel aftertouch. 1114 ubyte[] midiChannelAftertouch(ref ubyte[] where, ubyte channel, byte amount) { 1115 where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f); 1116 where[1] = amount; 1117 auto it = where[0 .. 2]; 1118 where = where[2 .. $]; 1119 return it; 1120 } 1121 1122 /// Pitch bend. FIXME doesn't work right 1123 ubyte[] midiNotePitchBend(ref ubyte[] where, ubyte channel, short change) { 1124 /* 1125 first byte is llllll 1126 second byte is mmmmmm 1127 1128 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. 1129 */ 1130 where[0] = (MidiEvent.PitchBend << 4) | (channel&0x0f); 1131 // FIXME 1132 where[1] = 0; 1133 where[2] = 0; 1134 auto it = where[0 .. 3]; 1135 where = where[3 .. $]; 1136 return it; 1137 } 1138 1139 1140 // **************** 1141 // Wav helpers 1142 // **************** 1143 1144 // FIXME: the .wav file format should be here, read and write (at least basics) 1145 // as well as some kind helpers to generate some sounds. 1146 1147 // **************** 1148 // OS specific helper stuff follows 1149 // **************** 1150 1151 version(ALSA) 1152 // Opens the PCM device with default settings: stereo, 16 bit, 44.1 kHz, interleaved R/W. 1153 snd_pcm_t* openAlsaPcm(snd_pcm_stream_t direction) { 1154 snd_pcm_t* handle; 1155 snd_pcm_hw_params_t* hwParams; 1156 1157 /* Open PCM and initialize hardware */ 1158 1159 if (auto err = snd_pcm_open(&handle, cardName, direction, 0)) 1160 throw new AlsaException("open device", err); 1161 scope(failure) 1162 snd_pcm_close(handle); 1163 1164 1165 if (auto err = snd_pcm_hw_params_malloc(&hwParams)) 1166 throw new AlsaException("params malloc", err); 1167 scope(exit) 1168 snd_pcm_hw_params_free(hwParams); 1169 1170 if (auto err = snd_pcm_hw_params_any(handle, hwParams)) 1171 // can actually survive a failure here, we will just move forward 1172 {} // throw new AlsaException("params init", err); 1173 1174 if (auto err = snd_pcm_hw_params_set_access(handle, hwParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED)) 1175 throw new AlsaException("params access", err); 1176 1177 if (auto err = snd_pcm_hw_params_set_format(handle, hwParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE)) 1178 throw new AlsaException("params format", err); 1179 1180 uint rate = SampleRate; 1181 int dir = 0; 1182 if (auto err = snd_pcm_hw_params_set_rate_near(handle, hwParams, &rate, &dir)) 1183 throw new AlsaException("params rate", err); 1184 1185 assert(rate == SampleRate); // cheap me 1186 1187 if (auto err = snd_pcm_hw_params_set_channels(handle, hwParams, 2)) 1188 throw new AlsaException("params channels", err); 1189 1190 uint periods = 2; 1191 { 1192 auto err = snd_pcm_hw_params_set_periods_near(handle, hwParams, &periods, 0); 1193 if(err < 0) 1194 throw new AlsaException("periods", err); 1195 1196 snd_pcm_uframes_t sz = (BUFFER_SIZE_FRAMES * periods); 1197 err = snd_pcm_hw_params_set_buffer_size_near(handle, hwParams, &sz); 1198 if(err < 0) 1199 throw new AlsaException("buffer size", err); 1200 } 1201 1202 if (auto err = snd_pcm_hw_params(handle, hwParams)) 1203 throw new AlsaException("params install", err); 1204 1205 /* Setting up the callbacks */ 1206 1207 snd_pcm_sw_params_t* swparams; 1208 if(auto err = snd_pcm_sw_params_malloc(&swparams)) 1209 throw new AlsaException("sw malloc", err); 1210 scope(exit) 1211 snd_pcm_sw_params_free(swparams); 1212 if(auto err = snd_pcm_sw_params_current(handle, swparams)) 1213 throw new AlsaException("sw set", err); 1214 if(auto err = snd_pcm_sw_params_set_avail_min(handle, swparams, BUFFER_SIZE_FRAMES)) 1215 throw new AlsaException("sw min", err); 1216 if(auto err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0)) 1217 throw new AlsaException("sw threshold", err); 1218 if(auto err = snd_pcm_sw_params(handle, swparams)) 1219 throw new AlsaException("sw params", err); 1220 1221 /* finish setup */ 1222 1223 if (auto err = snd_pcm_prepare(handle)) 1224 throw new AlsaException("prepare", err); 1225 1226 assert(handle !is null); 1227 return handle; 1228 } 1229 1230 version(ALSA) 1231 class AlsaException : AudioException { 1232 this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1233 auto msg = snd_strerror(error); 1234 import core.stdc.string; 1235 super(cast(string) (message ~ ": " ~ msg[0 .. strlen(msg)]), file, line, next); 1236 } 1237 } 1238 1239 version(WinMM) 1240 class WinMMException : AudioException { 1241 this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1242 // FIXME: format the error 1243 // midiOutGetErrorText, etc. 1244 super(message, file, line, next); 1245 } 1246 } 1247 1248 // **************** 1249 // Bindings follow 1250 // **************** 1251 1252 version(ALSA) { 1253 extern(C): 1254 @nogc nothrow: 1255 pragma(lib, "asound"); 1256 private import core.sys.posix.poll; 1257 1258 const(char)* snd_strerror(int); 1259 1260 // pcm 1261 enum snd_pcm_stream_t { 1262 SND_PCM_STREAM_PLAYBACK, 1263 SND_PCM_STREAM_CAPTURE 1264 } 1265 1266 enum snd_pcm_access_t { 1267 /** mmap access with simple interleaved channels */ 1268 SND_PCM_ACCESS_MMAP_INTERLEAVED = 0, 1269 /** mmap access with simple non interleaved channels */ 1270 SND_PCM_ACCESS_MMAP_NONINTERLEAVED, 1271 /** mmap access with complex placement */ 1272 SND_PCM_ACCESS_MMAP_COMPLEX, 1273 /** snd_pcm_readi/snd_pcm_writei access */ 1274 SND_PCM_ACCESS_RW_INTERLEAVED, 1275 /** snd_pcm_readn/snd_pcm_writen access */ 1276 SND_PCM_ACCESS_RW_NONINTERLEAVED, 1277 SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED 1278 } 1279 1280 enum snd_pcm_format { 1281 /** Unknown */ 1282 SND_PCM_FORMAT_UNKNOWN = -1, 1283 /** Signed 8 bit */ 1284 SND_PCM_FORMAT_S8 = 0, 1285 /** Unsigned 8 bit */ 1286 SND_PCM_FORMAT_U8, 1287 /** Signed 16 bit Little Endian */ 1288 SND_PCM_FORMAT_S16_LE, 1289 /** Signed 16 bit Big Endian */ 1290 SND_PCM_FORMAT_S16_BE, 1291 /** Unsigned 16 bit Little Endian */ 1292 SND_PCM_FORMAT_U16_LE, 1293 /** Unsigned 16 bit Big Endian */ 1294 SND_PCM_FORMAT_U16_BE, 1295 /** Signed 24 bit Little Endian using low three bytes in 32-bit word */ 1296 SND_PCM_FORMAT_S24_LE, 1297 /** Signed 24 bit Big Endian using low three bytes in 32-bit word */ 1298 SND_PCM_FORMAT_S24_BE, 1299 /** Unsigned 24 bit Little Endian using low three bytes in 32-bit word */ 1300 SND_PCM_FORMAT_U24_LE, 1301 /** Unsigned 24 bit Big Endian using low three bytes in 32-bit word */ 1302 SND_PCM_FORMAT_U24_BE, 1303 /** Signed 32 bit Little Endian */ 1304 SND_PCM_FORMAT_S32_LE, 1305 /** Signed 32 bit Big Endian */ 1306 SND_PCM_FORMAT_S32_BE, 1307 /** Unsigned 32 bit Little Endian */ 1308 SND_PCM_FORMAT_U32_LE, 1309 /** Unsigned 32 bit Big Endian */ 1310 SND_PCM_FORMAT_U32_BE, 1311 /** Float 32 bit Little Endian, Range -1.0 to 1.0 */ 1312 SND_PCM_FORMAT_FLOAT_LE, 1313 /** Float 32 bit Big Endian, Range -1.0 to 1.0 */ 1314 SND_PCM_FORMAT_FLOAT_BE, 1315 /** Float 64 bit Little Endian, Range -1.0 to 1.0 */ 1316 SND_PCM_FORMAT_FLOAT64_LE, 1317 /** Float 64 bit Big Endian, Range -1.0 to 1.0 */ 1318 SND_PCM_FORMAT_FLOAT64_BE, 1319 /** IEC-958 Little Endian */ 1320 SND_PCM_FORMAT_IEC958_SUBFRAME_LE, 1321 /** IEC-958 Big Endian */ 1322 SND_PCM_FORMAT_IEC958_SUBFRAME_BE, 1323 /** Mu-Law */ 1324 SND_PCM_FORMAT_MU_LAW, 1325 /** A-Law */ 1326 SND_PCM_FORMAT_A_LAW, 1327 /** Ima-ADPCM */ 1328 SND_PCM_FORMAT_IMA_ADPCM, 1329 /** MPEG */ 1330 SND_PCM_FORMAT_MPEG, 1331 /** GSM */ 1332 SND_PCM_FORMAT_GSM, 1333 /** Special */ 1334 SND_PCM_FORMAT_SPECIAL = 31, 1335 /** Signed 24bit Little Endian in 3bytes format */ 1336 SND_PCM_FORMAT_S24_3LE = 32, 1337 /** Signed 24bit Big Endian in 3bytes format */ 1338 SND_PCM_FORMAT_S24_3BE, 1339 /** Unsigned 24bit Little Endian in 3bytes format */ 1340 SND_PCM_FORMAT_U24_3LE, 1341 /** Unsigned 24bit Big Endian in 3bytes format */ 1342 SND_PCM_FORMAT_U24_3BE, 1343 /** Signed 20bit Little Endian in 3bytes format */ 1344 SND_PCM_FORMAT_S20_3LE, 1345 /** Signed 20bit Big Endian in 3bytes format */ 1346 SND_PCM_FORMAT_S20_3BE, 1347 /** Unsigned 20bit Little Endian in 3bytes format */ 1348 SND_PCM_FORMAT_U20_3LE, 1349 /** Unsigned 20bit Big Endian in 3bytes format */ 1350 SND_PCM_FORMAT_U20_3BE, 1351 /** Signed 18bit Little Endian in 3bytes format */ 1352 SND_PCM_FORMAT_S18_3LE, 1353 /** Signed 18bit Big Endian in 3bytes format */ 1354 SND_PCM_FORMAT_S18_3BE, 1355 /** Unsigned 18bit Little Endian in 3bytes format */ 1356 SND_PCM_FORMAT_U18_3LE, 1357 /** Unsigned 18bit Big Endian in 3bytes format */ 1358 SND_PCM_FORMAT_U18_3BE, 1359 /* G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes */ 1360 SND_PCM_FORMAT_G723_24, 1361 /* G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte */ 1362 SND_PCM_FORMAT_G723_24_1B, 1363 /* G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes */ 1364 SND_PCM_FORMAT_G723_40, 1365 /* G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte */ 1366 SND_PCM_FORMAT_G723_40_1B, 1367 /* Direct Stream Digital (DSD) in 1-byte samples (x8) */ 1368 SND_PCM_FORMAT_DSD_U8, 1369 /* Direct Stream Digital (DSD) in 2-byte samples (x16) */ 1370 SND_PCM_FORMAT_DSD_U16_LE, 1371 SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U16_LE, 1372 1373 // I snipped a bunch of endian-specific ones! 1374 } 1375 1376 struct snd_pcm_t {} 1377 struct snd_pcm_hw_params_t {} 1378 struct snd_pcm_sw_params_t {} 1379 1380 int snd_pcm_open(snd_pcm_t**, const char*, snd_pcm_stream_t, int); 1381 int snd_pcm_close(snd_pcm_t*); 1382 int snd_pcm_prepare(snd_pcm_t*); 1383 int snd_pcm_hw_params(snd_pcm_t*, snd_pcm_hw_params_t*); 1384 int snd_pcm_hw_params_set_periods(snd_pcm_t*, snd_pcm_hw_params_t*, uint, int); 1385 int snd_pcm_hw_params_set_periods_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int); 1386 int snd_pcm_hw_params_set_buffer_size(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t); 1387 int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t*); 1388 int snd_pcm_hw_params_set_channels(snd_pcm_t*, snd_pcm_hw_params_t*, uint); 1389 int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t**); 1390 void snd_pcm_hw_params_free(snd_pcm_hw_params_t*); 1391 int snd_pcm_hw_params_any(snd_pcm_t*, snd_pcm_hw_params_t*); 1392 int snd_pcm_hw_params_set_access(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_access_t); 1393 int snd_pcm_hw_params_set_format(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_format); 1394 int snd_pcm_hw_params_set_rate_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int*); 1395 1396 int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t**); 1397 void snd_pcm_sw_params_free(snd_pcm_sw_params_t*); 1398 1399 int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params); 1400 int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params); 1401 int snd_pcm_sw_params_set_avail_min(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 1402 int snd_pcm_sw_params_set_start_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 1403 int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 1404 1405 alias snd_pcm_sframes_t = c_long; 1406 alias snd_pcm_uframes_t = c_ulong; 1407 snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t*, const void*, snd_pcm_uframes_t size); 1408 snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t*, void*, snd_pcm_uframes_t size); 1409 1410 int snd_pcm_wait(snd_pcm_t *pcm, int timeout); 1411 snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm); 1412 snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm); 1413 1414 int snd_pcm_recover (snd_pcm_t* pcm, int err, int silent); 1415 1416 alias snd_lib_error_handler_t = void function (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...); 1417 int snd_lib_error_set_handler (snd_lib_error_handler_t handler); 1418 1419 import core.stdc.stdarg; 1420 private void alsa_message_silencer (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...) {} 1421 //k8: ALSAlib loves to trash stderr; shut it up 1422 void silence_alsa_messages () { snd_lib_error_set_handler(&alsa_message_silencer); } 1423 shared static this () { silence_alsa_messages(); } 1424 1425 // raw midi 1426 1427 static if(is(size_t == uint)) 1428 alias ssize_t = int; 1429 else 1430 alias ssize_t = long; 1431 1432 1433 struct snd_rawmidi_t {} 1434 int snd_rawmidi_open(snd_rawmidi_t**, snd_rawmidi_t**, const char*, int); 1435 int snd_rawmidi_close(snd_rawmidi_t*); 1436 int snd_rawmidi_drain(snd_rawmidi_t*); 1437 ssize_t snd_rawmidi_write(snd_rawmidi_t*, const void*, size_t); 1438 ssize_t snd_rawmidi_read(snd_rawmidi_t*, void*, size_t); 1439 1440 // mixer 1441 1442 struct snd_mixer_t {} 1443 struct snd_mixer_elem_t {} 1444 struct snd_mixer_selem_id_t {} 1445 1446 alias snd_mixer_elem_callback_t = int function(snd_mixer_elem_t*, uint); 1447 1448 int snd_mixer_open(snd_mixer_t**, int mode); 1449 int snd_mixer_close(snd_mixer_t*); 1450 int snd_mixer_attach(snd_mixer_t*, const char*); 1451 int snd_mixer_load(snd_mixer_t*); 1452 1453 // FIXME: those aren't actually void* 1454 int snd_mixer_selem_register(snd_mixer_t*, void*, void*); 1455 int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t**); 1456 void snd_mixer_selem_id_free(snd_mixer_selem_id_t*); 1457 void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t*, uint); 1458 void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t*, const char*); 1459 snd_mixer_elem_t* snd_mixer_find_selem(snd_mixer_t*, in snd_mixer_selem_id_t*); 1460 1461 // FIXME: the int should be an enum for channel identifier 1462 int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t*, int, c_long*); 1463 1464 int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t*, c_long*, c_long*); 1465 1466 int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t*, c_long); 1467 1468 void snd_mixer_elem_set_callback(snd_mixer_elem_t*, snd_mixer_elem_callback_t); 1469 int snd_mixer_poll_descriptors(snd_mixer_t*, pollfd*, uint space); 1470 1471 int snd_mixer_handle_events(snd_mixer_t*); 1472 1473 // FIXME: the first int should be an enum for channel identifier 1474 int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t*, int, int* value); 1475 int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t*, int); 1476 } 1477 1478 version(WinMM) { 1479 extern(Windows): 1480 @nogc nothrow: 1481 pragma(lib, "winmm"); 1482 import core.sys.windows.windows; 1483 1484 /* 1485 Windows functions include: 1486 http://msdn.microsoft.com/en-us/library/ms713762%28VS.85%29.aspx 1487 http://msdn.microsoft.com/en-us/library/ms713504%28v=vs.85%29.aspx 1488 http://msdn.microsoft.com/en-us/library/windows/desktop/dd798480%28v=vs.85%29.aspx# 1489 http://msdn.microsoft.com/en-US/subscriptions/ms712109.aspx 1490 */ 1491 1492 // pcm 1493 1494 // midi 1495 1496 alias HMIDIOUT = HANDLE; 1497 alias MMRESULT = UINT; 1498 1499 MMRESULT midiOutOpen(HMIDIOUT*, UINT, DWORD, DWORD, DWORD); 1500 MMRESULT midiOutClose(HMIDIOUT); 1501 MMRESULT midiOutReset(HMIDIOUT); 1502 MMRESULT midiOutShortMsg(HMIDIOUT, DWORD); 1503 1504 alias HWAVEOUT = HANDLE; 1505 1506 struct WAVEFORMATEX { 1507 WORD wFormatTag; 1508 WORD nChannels; 1509 DWORD nSamplesPerSec; 1510 DWORD nAvgBytesPerSec; 1511 WORD nBlockAlign; 1512 WORD wBitsPerSample; 1513 WORD cbSize; 1514 } 1515 1516 struct WAVEHDR { 1517 void* lpData; 1518 DWORD dwBufferLength; 1519 DWORD dwBytesRecorded; 1520 DWORD dwUser; 1521 DWORD dwFlags; 1522 DWORD dwLoops; 1523 WAVEHDR *lpNext; 1524 DWORD reserved; 1525 } 1526 1527 enum UINT WAVE_MAPPER= -1; 1528 1529 MMRESULT waveOutOpen(HWAVEOUT*, UINT_PTR, WAVEFORMATEX*, void* callback, void*, DWORD); 1530 MMRESULT waveOutClose(HWAVEOUT); 1531 MMRESULT waveOutPrepareHeader(HWAVEOUT, WAVEHDR*, UINT); 1532 MMRESULT waveOutUnprepareHeader(HWAVEOUT, WAVEHDR*, UINT); 1533 MMRESULT waveOutWrite(HWAVEOUT, WAVEHDR*, UINT); 1534 1535 MMRESULT waveOutGetVolume(HWAVEOUT, PDWORD); 1536 MMRESULT waveOutSetVolume(HWAVEOUT, DWORD); 1537 1538 enum CALLBACK_TYPEMASK = 0x70000; 1539 enum CALLBACK_NULL = 0; 1540 enum CALLBACK_WINDOW = 0x10000; 1541 enum CALLBACK_TASK = 0x20000; 1542 enum CALLBACK_FUNCTION = 0x30000; 1543 enum CALLBACK_THREAD = CALLBACK_TASK; 1544 enum CALLBACK_EVENT = 0x50000; 1545 1546 enum WAVE_FORMAT_PCM = 1; 1547 1548 enum WHDR_PREPARED = 2; 1549 enum WHDR_BEGINLOOP = 4; 1550 enum WHDR_ENDLOOP = 8; 1551 enum WHDR_INQUEUE = 16; 1552 1553 enum WinMMMessage : UINT { 1554 MM_JOY1MOVE = 0x3A0, 1555 MM_JOY2MOVE, 1556 MM_JOY1ZMOVE, 1557 MM_JOY2ZMOVE, // = 0x3A3 1558 MM_JOY1BUTTONDOWN = 0x3B5, 1559 MM_JOY2BUTTONDOWN, 1560 MM_JOY1BUTTONUP, 1561 MM_JOY2BUTTONUP, 1562 MM_MCINOTIFY, // = 0x3B9 1563 MM_WOM_OPEN = 0x3BB, 1564 MM_WOM_CLOSE, 1565 MM_WOM_DONE, 1566 MM_WIM_OPEN, 1567 MM_WIM_CLOSE, 1568 MM_WIM_DATA, 1569 MM_MIM_OPEN, 1570 MM_MIM_CLOSE, 1571 MM_MIM_DATA, 1572 MM_MIM_LONGDATA, 1573 MM_MIM_ERROR, 1574 MM_MIM_LONGERROR, 1575 MM_MOM_OPEN, 1576 MM_MOM_CLOSE, 1577 MM_MOM_DONE, // = 0x3C9 1578 MM_DRVM_OPEN = 0x3D0, 1579 MM_DRVM_CLOSE, 1580 MM_DRVM_DATA, 1581 MM_DRVM_ERROR, 1582 MM_STREAM_OPEN, 1583 MM_STREAM_CLOSE, 1584 MM_STREAM_DONE, 1585 MM_STREAM_ERROR, // = 0x3D7 1586 MM_MOM_POSITIONCB = 0x3CA, 1587 MM_MCISIGNAL, 1588 MM_MIM_MOREDATA, // = 0x3CC 1589 MM_MIXM_LINE_CHANGE = 0x3D0, 1590 MM_MIXM_CONTROL_CHANGE = 0x3D1 1591 } 1592 1593 1594 enum WOM_OPEN = WinMMMessage.MM_WOM_OPEN; 1595 enum WOM_CLOSE = WinMMMessage.MM_WOM_CLOSE; 1596 enum WOM_DONE = WinMMMessage.MM_WOM_DONE; 1597 enum WIM_OPEN = WinMMMessage.MM_WIM_OPEN; 1598 enum WIM_CLOSE = WinMMMessage.MM_WIM_CLOSE; 1599 enum WIM_DATA = WinMMMessage.MM_WIM_DATA; 1600 1601 1602 uint mciSendStringA(in char*,char*,uint,void*); 1603 }