1 // FIXME: add a query devices thing 2 // FIXME: add the alsa sequencer interface cuz then i don't need the virtual raw midi sigh. or at elast load "virtual" and auto connect it somehow 3 // bindings: https://gist.github.com/pbackus/5eadddb1de8a8c5b24f5016a365c5942 4 // FIXME: 3d sound samples - basically you can assign a position to a thing you are playing in terms of a angle and distance from teh observe and do a bit of lag and left/right balance adjustments, then tell it its own speed for doppler shifts 5 /** 6 The purpose of this module is to provide audio functions for 7 things like playback, capture, and volume on both Windows 8 (via the mmsystem calls) and Linux (through ALSA). 9 10 It is only aimed at the basics, and will be filled in as I want 11 a particular feature. I don't generally need super configurability 12 and see it as a minus, since I don't generally care either, so I'm 13 going to be going for defaults that just work. If you need more though, 14 you can hack the source or maybe just use it for the operating system 15 bindings. 16 17 For example, I'm starting this because I want to write a volume 18 control program for my linux box, so that's what is going first. 19 That will consist of a listening callback for volume changes and 20 being able to get/set the volume. 21 22 TODO: 23 * pre-resampler that loads a clip and prepares it for repeated fast use 24 * controls so you can tell a particular thing to keep looping until you tell it to stop, or stop after the next loop, etc (think a phaser sound as long as you hold the button down) 25 * playFile function that detects automatically. basically: 26 if(args[1].endsWith("ogg")) 27 a.playOgg(args[1]); 28 else if(args[1].endsWith("wav")) 29 a.playWav(args[1]); 30 else if(mp3) 31 a.playMp3(args[1]); 32 33 34 * play audio high level with options to wait until completion or return immediately 35 * midi mid-level stuff but see [arsd.midi]! 36 37 * some kind of encoder??????? 38 39 I will probably NOT do OSS anymore, since my computer doesn't even work with it now. 40 Ditto for Macintosh, as I don't have one and don't really care about them. 41 42 License: 43 GPL3 unless you compile with `-version=without_resampler` and don't use the `playEmulatedOpl3Midi`, 44 in which case it is BSL-1.0. 45 */ 46 module arsd.simpleaudio; 47 48 // hacking around https://issues.dlang.org/show_bug.cgi?id=23595 49 import core.stdc.config; 50 version(Posix) 51 import core.sys.posix.sys.types; 52 // done with hack around compiler bug 53 54 // http://webcache.googleusercontent.com/search?q=cache:NqveBqL0AOUJ:https://www.alsa-project.org/alsa-doc/alsa-lib/group___p_c_m.html&hl=en&gl=us&strip=1&vwsrc=0 55 56 version(without_resampler) { 57 58 } else { 59 version(X86) 60 version=with_resampler; 61 version(X86_64) 62 version=with_resampler; 63 } 64 65 enum BUFFER_SIZE_FRAMES = 1024;//512;//2048; 66 enum BUFFER_SIZE_SHORT = BUFFER_SIZE_FRAMES * 2; 67 68 /// 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. 69 enum DEFAULT_VOLUME = 10; 70 71 private enum PI = 3.14159265358979323; 72 73 version(Demo_simpleaudio) 74 void main() { 75 /+ 76 77 version(none) { 78 import iv.stb.vorbis; 79 80 int channels; 81 short* decoded; 82 auto v = new VorbisDecoder("test.ogg"); 83 84 auto ao = AudioOutput(0); 85 ao.fillData = (short[] buffer) { 86 auto got = v.getSamplesShortInterleaved(2, buffer.ptr, buffer.length); 87 if(got == 0) { 88 ao.stop(); 89 } 90 }; 91 92 ao.play(); 93 return; 94 } 95 96 97 98 99 auto thread = new AudioPcmOutThread(); 100 thread.start(); 101 102 thread.playOgg("test.ogg"); 103 104 Thread.sleep(5.seconds); 105 106 //Thread.sleep(150.msecs); 107 thread.beep(); 108 Thread.sleep(250.msecs); 109 thread.blip(); 110 Thread.sleep(250.msecs); 111 thread.boop(); 112 Thread.sleep(1000.msecs); 113 /* 114 thread.beep(800, 500); 115 Thread.sleep(500.msecs); 116 thread.beep(366, 500); 117 Thread.sleep(600.msecs); 118 thread.beep(800, 500); 119 thread.beep(366, 500); 120 Thread.sleep(500.msecs); 121 Thread.sleep(150.msecs); 122 thread.beep(200); 123 Thread.sleep(150.msecs); 124 thread.beep(100); 125 Thread.sleep(150.msecs); 126 thread.noise(); 127 Thread.sleep(150.msecs); 128 */ 129 130 131 thread.stop(); 132 133 thread.join(); 134 135 return; 136 137 /* 138 auto aio = AudioMixer(0); 139 140 import std.stdio; 141 writeln(aio.muteMaster); 142 */ 143 144 /* 145 mciSendStringA("play test.wav", null, 0, null); 146 Sleep(3000); 147 import std.stdio; 148 if(auto err = mciSendStringA("play test2.wav", null, 0, null)) 149 writeln(err); 150 Sleep(6000); 151 return; 152 */ 153 154 // output about a second of random noise to demo PCM 155 auto ao = AudioOutput(0); 156 short[BUFFER_SIZE_SHORT] randomSpam = void; 157 import core.stdc.stdlib; 158 foreach(ref s; randomSpam) 159 s = cast(short)((cast(short) rand()) - short.max / 2); 160 161 int loopCount = 40; 162 163 //import std.stdio; 164 //writeln("Should be about ", loopCount * BUFFER_SIZE_FRAMES * 1000 / SampleRate, " microseconds"); 165 166 int loops = 0; 167 // only do simple stuff in here like fill the data, set simple 168 // variables, or call stop anything else might cause deadlock 169 ao.fillData = (short[] buffer) { 170 buffer[] = randomSpam[0 .. buffer.length]; 171 loops++; 172 if(loops == loopCount) 173 ao.stop(); 174 }; 175 176 ao.play(); 177 178 return; 179 +/ 180 // Play a C major scale on the piano to demonstrate midi 181 auto midi = MidiOutput(0); 182 183 ubyte[16] buffer = void; 184 ubyte[] where = buffer[]; 185 midi.writeRawMessageData(where.midiProgramChange(1, 1)); 186 for(ubyte note = MidiNote.C; note <= MidiNote.C + 12; note++) { 187 where = buffer[]; 188 midi.writeRawMessageData(where.midiNoteOn(1, note, 127)); 189 import core.thread; 190 Thread.sleep(dur!"msecs"(500)); 191 midi.writeRawMessageData(where.midiNoteOff(1, note, 127)); 192 193 if(note != 76 && note != 83) 194 note++; 195 } 196 import core.thread; 197 Thread.sleep(dur!"msecs"(500)); // give the last note a chance to finish 198 } 199 200 /++ 201 Provides an interface to control a sound. 202 203 All methods on this interface execute asynchronously 204 205 History: 206 Added December 23, 2020 207 +/ 208 interface SampleController { 209 /++ 210 Pauses playback, keeping its position. Use [resume] to pick up where it left off. 211 +/ 212 void pause(); 213 /++ 214 Resumes playback after a call to [pause]. 215 +/ 216 void resume(); 217 /++ 218 Stops playback. Once stopped, it cannot be restarted 219 except by creating a new sample from the [AudioOutputThread] 220 object. 221 +/ 222 void stop(); 223 /++ 224 Reports the current stream position, in seconds, if available (NaN if not). 225 +/ 226 float position(); 227 228 /++ 229 If the sample has finished playing. Happens when it runs out or if it is stopped. 230 +/ 231 bool finished(); 232 233 /++ 234 If the sample has been paused. 235 236 History: 237 Added May 26, 2021 (dub v10.0) 238 +/ 239 bool paused(); 240 241 /++ 242 Seeks to a point in the sample, if possible. If impossible, this function does nothing. 243 244 Params: 245 where = point to seek to, in seconds 246 247 History: 248 Added November 20, 2022 (dub v10.10) 249 Bugs: 250 Only implemented for mp3 and ogg at this time. 251 +/ 252 void seek(float where); 253 254 /++ 255 Duration of the sample, in seconds. Please note it may be nan if unknown or inf if infinite looping. 256 You should check for both conditions. 257 258 History: 259 Added November 20, 2022 (dub v10.10) 260 +/ 261 float duration(); 262 263 /++ 264 Controls the volume of this particular sample, as a multiplier of its 265 original perceptual volume. 266 267 If unimplemented, the setter will return `float.nan` and the getter will 268 always return 1.0. 269 270 History: 271 Added November 26, 2020 (dub v10.10) 272 273 Bugs: 274 Not implemented for any type in simpleaudio at this time. 275 +/ 276 float volume(); 277 /// ditto 278 float volume(float multiplierOfOriginal); 279 280 /++ 281 Controls the playback speed of this particular sample, as a multiplier 282 of its original speed. Setting it to 0.0 is liable to crash. 283 284 If unimplemented, the getter will always return 1.0. This is nearly always the 285 case if you compile with `-version=without_resampler`. 286 287 Please note that all members, [position], [duration], and any 288 others that relate to time will always return original times; 289 that is, as if `playbackSpeed == 1.0`. 290 291 Note that this is going to change the pitch of the sample; it 292 isn't a tempo change. 293 294 History: 295 Added November 26, 2020 (dub v10.10) 296 +/ 297 298 float playbackSpeed(); 299 /// ditto 300 void playbackSpeed(float multiplierOfOriginal); 301 302 /+ 303 304 /++ 305 Sets a delegate that will be called on the audio thread when the sample is finished 306 playing; immediately after [finished] becomes `true`. 307 308 $(PITFALL 309 Very important: your callback is called on the audio thread. The safest thing 310 to do in it is to simply send a message back to your main thread where it deals 311 with whatever you want to do. 312 ) 313 314 History: 315 Added November 26, 2020 (dub v10.10) 316 +/ 317 void onfinished(void delegate() shared callback); 318 319 /++ 320 Sets a delegate that will pre-process any buffer before it is passed to the audio device 321 when playing, or your waveform delegate when using [getWaveform]. You can modify data 322 in the buffer if you want, or copy it out somewhere else, but remember this may be called 323 on the audio thread. 324 325 I didn't mark the delegate param `scope` but I might. Copying the actual pointer is super 326 iffy because the buffer can be reused by the audio thread as soon as this function returns. 327 328 History: 329 Added November 27, 2020 (dub v10.10) 330 +/ 331 void setBufferDelegate(void delegate(short[] buffer, int sampleRate, int numberOfChannels) shared callback); 332 333 /++ 334 Plays the sample on the given audio device. You can only ever play it on one device at a time. 335 336 Returns: 337 `true` if it was able to play on the given device, `false` if not. 338 339 Among the reasons it may be unable to play is if it is already playing 340 elsewhere or if it is already used up. 341 342 History: 343 Added November 27, 2020 (dub v10.10) 344 +/ 345 bool playOn(AudioOutputThread where); 346 347 /++ 348 Plays it to your delegate which emulates an audio device with the given sample rate and number of channels. It will call your delegate with interleaved signed 16 bit samples. 349 350 Returns: 351 `true` if it called your delegate at least once. 352 353 Among the reasons it might be `false`: 354 $(LIST 355 * The sample is already playing on another device. 356 * You compiled with `-version=without_resampler` and the sample rate didn't match the sample's capabilities. 357 * The number of channels requested is incompatible with the implementation. 358 ) 359 360 History: 361 Added November 27, 2020 (dub v10.10) 362 +/ 363 bool getWaveform(int sampleRate, int numberOfChannels, scope void delegate(scope short[] buffer) dg); 364 365 +/ 366 } 367 368 class DummySample : SampleController { 369 void pause() {} 370 void resume() {} 371 void stop() {} 372 float position() { return float.nan; } 373 bool finished() { return true; } 374 bool paused() { return true; } 375 376 float duration() { return float.nan; } 377 float volume() { return 1.0; } 378 float volume(float v) { return float.nan; } 379 380 float playbackSpeed() { return 1.0; } 381 void playbackSpeed(float v) { } 382 383 void seek(float where) {} 384 } 385 386 private final class SampleControlFlags : SampleController { 387 import arsd.core : EnableSynchronization; 388 mixin EnableSynchronization; 389 390 void pause() { paused_ = true; } 391 void resume() { paused_ = false; } 392 void stop() { paused_ = false; stopped = true; } 393 394 bool paused_; 395 bool stopped; 396 bool finished_; 397 398 float position() { return currentPosition; } 399 bool finished() { return finished_; } 400 bool paused() { return paused_; } 401 402 void seek(float where) { synchronized(this) {if(where < 0) where = 0; requestedSeek = where;} } 403 404 float currentPosition = 0.0; 405 float requestedSeek = float.nan; 406 407 float detectedDuration = float.nan; 408 float duration() { return detectedDuration; } 409 410 // FIXME: these aren't implemented 411 float volume() { return 1.0; } 412 float volume(float v) { return float.nan; } 413 414 float playbackSpeed_ = 1.0; 415 416 float requestedPlaybackSpeed = float.nan; 417 418 float playbackSpeed() { return playbackSpeed_; } 419 void playbackSpeed(float v) { requestedPlaybackSpeed = v; } 420 421 422 void pollUserChanges( 423 scope bool delegate(float) executeSeek, 424 scope bool delegate(float) executePlaybackSpeed, 425 ) { 426 // should I synchronize it after all? 427 synchronized(this) { 428 if(this.requestedSeek !is float.nan) { 429 if(executeSeek !is null && executeSeek(this.requestedSeek)) { 430 this.currentPosition = this.requestedSeek; 431 } 432 433 this.requestedSeek = float.nan; 434 } 435 if(this.requestedPlaybackSpeed !is float.nan) { 436 if(executePlaybackSpeed !is null && executePlaybackSpeed(this.playbackSpeed_)) { 437 this.playbackSpeed_ = this.requestedPlaybackSpeed; 438 } 439 this.requestedPlaybackSpeed = float.nan; 440 } 441 } 442 443 } 444 } 445 446 /++ 447 Wraps [AudioPcmOutThreadImplementation] with RAII semantics for better 448 error handling and disposal than the old way. 449 450 DO NOT USE THE `new` OPERATOR ON THIS! Just construct it inline: 451 452 --- 453 auto audio = AudioOutputThread(true); 454 audio.beep(); 455 --- 456 457 History: 458 Added May 9, 2020 to replace the old [AudioPcmOutThread] class 459 that proved pretty difficult to use correctly. 460 +/ 461 struct AudioOutputThread { 462 @disable this(); 463 464 static if(__VERSION__ < 2098) 465 mixin(q{ @disable new(size_t); }); // gdc9 requires the arg fyi, but i mix it in because dmd deprecates before semantic so it can't be versioned out ugh 466 else 467 @disable new(); // but new dmd is strict about not allowing it 468 469 @disable void start() {} // you aren't supposed to control the thread yourself! 470 /++ 471 You should call `exit` instead of join. It will signal the thread to exit and then call join for you. 472 473 If you absolutely must call join, use [rawJoin] instead. 474 475 History: 476 Disabled on December 30, 2021 477 +/ 478 @disable void join(bool a = false) {} // you aren't supposed to control the thread yourself! 479 480 /++ 481 Don't call this unless you're sure you know what you're doing. 482 483 You should use `audioOutputThread.exit();` instead. 484 +/ 485 Throwable rawJoin(bool rethrow = true) { 486 if(impl is null) 487 return null; 488 return impl.join(rethrow); 489 } 490 491 /++ 492 Pass `true` to enable the audio thread. Otherwise, it will 493 just live as a dummy mock object that you should not actually 494 try to use. 495 496 History: 497 Parameter `default` added on Nov 8, 2020. 498 499 The sample rate parameter was not correctly applied to the device on Linux until December 24, 2020. 500 +/ 501 this(bool enable, int SampleRate = 44100, int channels = 2, string device = "default") { 502 if(enable) { 503 impl = new AudioPcmOutThreadImplementation(SampleRate, channels, device); 504 impl.refcount++; 505 impl.start(); 506 impl.waitForInitialization(); 507 impl.priority = Thread.PRIORITY_MAX; 508 } 509 } 510 511 /// ditto 512 this(bool enable, string device, int SampleRate = 44100, int channels = 2) { 513 this(enable, SampleRate, channels, device); 514 } 515 516 /// Keeps an internal refcount. 517 this(this) { 518 if(impl) 519 impl.refcount++; 520 } 521 522 /// 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). 523 ~this() { 524 if(impl) { 525 impl.refcount--; 526 if(impl.refcount == 0) { 527 impl.exit(true); 528 } 529 } 530 } 531 532 /++ 533 Returns true if the output is suspended. Use `suspend` and `unsuspend` to change this. 534 535 History: 536 Added December 21, 2021 (dub v10.5) 537 +/ 538 bool suspended() { 539 if(impl) 540 return impl.suspended(); 541 return true; 542 } 543 544 /++ 545 This allows you to check `if(audio)` to see if it is enabled. 546 +/ 547 bool opCast(T : bool)() { 548 return impl !is null; 549 } 550 551 /++ 552 Other methods are forwarded to the implementation of type 553 [AudioPcmOutThreadImplementation]. See that for more information 554 on what you can do. 555 556 This opDispatch template will forward all other methods directly 557 to that [AudioPcmOutThreadImplementation] if this is live, otherwise 558 it does nothing. 559 +/ 560 template opDispatch(string name) { 561 static if(is(typeof(__traits(getMember, impl, name)) Params == __parameters)) 562 auto opDispatch(Params params) { 563 if(impl) 564 return __traits(getMember, impl, name)(params); 565 static if(!is(typeof(return) == void)) 566 return typeof(return).init; 567 } 568 else static assert(0); 569 } 570 571 // manual forward of thse since the opDispatch doesn't do the variadic 572 alias Sample = AudioPcmOutThreadImplementation.Sample; 573 void addSample(Sample[] samples...) { 574 if(impl !is null) 575 impl.addSample(samples); 576 } 577 578 // since these are templates, the opDispatch won't trigger them, so I have to do it differently. 579 // the dummysample is good anyway. 580 581 SampleController playEmulatedOpl3Midi()(string filename) { 582 if(impl) 583 return impl.playEmulatedOpl3Midi(filename); 584 return new DummySample; 585 } 586 SampleController playEmulatedOpl3Midi()(immutable(ubyte)[] data) { 587 if(impl) 588 return impl.playEmulatedOpl3Midi(data); 589 return new DummySample; 590 } 591 SampleController playOgg()(string filename, bool loop = false) { 592 if(impl) 593 return impl.playOgg(filename, loop); 594 return new DummySample; 595 } 596 SampleController playOgg()(immutable(ubyte)[] data, bool loop = false) { 597 if(impl) 598 return impl.playOgg(data, loop); 599 return new DummySample; 600 } 601 SampleController playMp3()(string filename) { 602 if(impl) 603 return impl.playMp3(filename); 604 return new DummySample; 605 } 606 SampleController playMp3()(immutable(ubyte)[] data) { 607 if(impl) 608 return impl.playMp3(data); 609 return new DummySample; 610 } 611 SampleController playWav()(string filename) { 612 if(impl) 613 return impl.playWav(filename); 614 return new DummySample; 615 } 616 SampleController playWav()(immutable(ubyte)[] data) { 617 if(impl) 618 return impl.playWav(data); 619 return new DummySample; 620 } 621 622 623 /// provides automatic [arsd.jsvar] script wrapping capability. Make sure the 624 /// script also finishes before this goes out of scope or it may end up talking 625 /// to a dead object.... 626 auto toArsdJsvar() { 627 return impl; 628 } 629 630 /+ 631 alias getImpl this; 632 AudioPcmOutThreadImplementation getImpl() { 633 assert(impl !is null); 634 return impl; 635 } 636 +/ 637 private AudioPcmOutThreadImplementation impl; 638 } 639 640 /++ 641 Old thread implementation. I decided to deprecate it in favor of [AudioOutputThread] because 642 RAII semantics make it easier to get right at the usage point. See that to go forward. 643 644 History: 645 Deprecated on May 9, 2020. 646 +/ 647 deprecated("Use AudioOutputThread instead.") class AudioPcmOutThread {} 648 649 /+ 650 /++ 651 652 +/ 653 void mmsleep(Duration time) { 654 version(Windows) { 655 static HANDLE timerQueue; 656 657 static HANDLE event; 658 if(event is null) 659 event = CreateEvent(null, false, false, null); 660 661 extern(Windows) 662 static void cb(PVOID ev, BOOLEAN) { 663 HANDLE e = cast(HANDLE) ev; 664 SetEvent(e); 665 } 666 667 //if(timerQueue is null) 668 //timerQueue = CreateTimerQueue(); 669 670 // DeleteTimerQueueEx(timerQueue, null); 671 672 HANDLE nt; 673 auto ret = CreateTimerQueueTimer(&nt, timerQueue, &cb, event /+ param +/, cast(DWORD) time.total!"msecs", 0 /* period */, WT_EXECUTEDEFAULT); 674 if(!ret) 675 throw new Exception("fail"); 676 //DeleteTimerQueueTimer(timerQueue, nt, INVALID_HANDLE_VALUE); 677 678 WaitForSingleObject(event, 1000); 679 } 680 } 681 +/ 682 683 /++ 684 A clock you can use for multimedia applications. It compares time elapsed against 685 a position variable you pass in to figure out how long to wait to get to that point. 686 Very similar to Phobos' [std.datetime.stopwatch.StopWatch|StopWatch] but with built-in 687 wait capabilities. 688 689 690 For example, suppose you want something to happen 60 frames per second: 691 692 --- 693 MMClock clock; 694 Duration frame; 695 clock.restart(); 696 while(running) { 697 frame += 1.seconds / 60; 698 bool onSchedule = clock.waitUntil(frame); 699 700 do_essential_frame_work(); 701 702 if(onSchedule) { 703 // if we're on time, do other work too. 704 // but if we weren't on time, skipping this 705 // might help catch back up to where we're 706 // supposed to be. 707 708 do_would_be_nice_frame_work(); 709 } 710 } 711 --- 712 +/ 713 struct MMClock { 714 import core.time; 715 716 private Duration position; 717 private MonoTime lastPositionUpdate; 718 private bool paused; 719 int speed = 1000; /// 1000 = 1.0, 2000 = 2.0, 500 = 0.5, etc. 720 721 private void updatePosition() { 722 auto now = MonoTime.currTime; 723 position += (now - lastPositionUpdate) * speed / 1000; 724 lastPositionUpdate = now; 725 } 726 727 /++ 728 Restarts the clock from position zero. 729 +/ 730 void restart() { 731 position = Duration.init; 732 lastPositionUpdate = MonoTime.currTime; 733 } 734 735 /++ 736 Pauses the clock. 737 +/ 738 void pause() { 739 if(paused) return; 740 updatePosition(); 741 paused = true; 742 } 743 void unpause() { 744 if(!paused) return; 745 lastPositionUpdate = MonoTime.currTime; 746 paused = false; 747 } 748 /++ 749 Goes to sleep until the real clock catches up to the given 750 `position`. 751 752 Returns: `true` if you're on schedule, returns false if the 753 given `position` is already in the past. In that case, 754 you might want to consider skipping some work to get back 755 on time. 756 +/ 757 bool waitUntil(Duration position) { 758 auto diff = timeUntil(position); 759 if(diff < 0.msecs) 760 return false; 761 762 if(diff == 0.msecs) 763 return true; 764 765 import core.thread; 766 Thread.sleep(diff); 767 return true; 768 } 769 770 /++ 771 772 +/ 773 Duration timeUntil(Duration position) { 774 updatePosition(); 775 return (position - this.position) * 1000 / speed; 776 } 777 778 /++ 779 Returns the current time on the clock since the 780 last call to [restart], excluding times when the 781 clock was paused. 782 +/ 783 Duration currentPosition() { 784 updatePosition(); 785 return position; 786 } 787 } 788 789 import core.thread; 790 /++ 791 Makes an audio thread for you that you can make 792 various sounds on and it will mix them with good 793 enough latency for simple games. 794 795 DO NOT USE THIS DIRECTLY. Instead, access it through 796 [AudioOutputThread]. 797 798 --- 799 auto audio = AudioOutputThread(true); 800 audio.beep(); 801 802 // you need to keep the main program alive long enough 803 // to keep this thread going to hear anything 804 Thread.sleep(1.seconds); 805 --- 806 +/ 807 final class AudioPcmOutThreadImplementation : Thread { 808 private this(int SampleRate, int channels, string device = "default") { 809 this.isDaemon = true; 810 811 this.SampleRate = SampleRate; 812 this.channels = channels; 813 this.device = device; 814 815 super(&run); 816 } 817 818 private int SampleRate; 819 private int channels; 820 private int refcount; 821 private string device; 822 823 private void waitForInitialization() { 824 shared(AudioOutput*)* ao = cast(shared(AudioOutput*)*) &this.ao; 825 //int wait = 0; 826 while(isRunning && *ao is null) { 827 Thread.sleep(5.msecs); 828 //wait += 5; 829 } 830 831 //import std.stdio; writeln(wait); 832 833 if(*ao is null) { 834 exit(true); 835 } 836 } 837 838 /++ 839 Asks the device to pause/unpause. This may not actually do anything on some systems. 840 You should probably use [suspend] and [unsuspend] instead. 841 +/ 842 @scriptable 843 void pause() { 844 if(ao) { 845 ao.pause(); 846 } 847 } 848 849 /// ditto 850 @scriptable 851 void unpause() { 852 if(ao) { 853 ao.unpause(); 854 } 855 } 856 857 /++ 858 Stops the output thread. Using the object after it is stopped is not recommended which is why 859 this is now deprecated. 860 861 You probably want [suspend] or [exit] instead. Use [suspend] if you want to stop playing, and 862 close the output device, but keep the thread alive so you can [unsuspend] later. After calling 863 [suspend], you can call [unsuspend] and then continue using the other method normally again. 864 865 Use [exit] if you want to stop playing, close the output device, and terminate the worker thread. 866 After calling [exit], you may not call any methods on the thread again. 867 868 The one exception is if you are inside an audio callback and want to stop the thread and prepare 869 it to be [AudioOutputThread.rawJoin]ed. Preferably, you'd avoid doing this - the channels can 870 simply return false to indicate that they are done. But if you must do that, call [rawStop] instead. 871 872 History: 873 `stop` was deprecated and `rawStop` added on December 30, 2021 (dub v10.5) 874 +/ 875 deprecated("You want to use either suspend or exit instead, or rawStop if you must but see the docs.") 876 void stop() { 877 if(ao) { 878 ao.stop(); 879 } 880 } 881 882 /// ditto 883 void rawStop() { 884 if(ao) { ao.stop(); } 885 } 886 887 /++ 888 Makes some old-school style sound effects. Play with them to see what they actually sound like. 889 890 Params: 891 freq = frequency of the wave in hertz 892 dur = duration in milliseconds 893 volume = amplitude of the wave, between 0 and 100 894 balance = stereo balance. 50 = both speakers equally, 0 = all to the left, none to the right, 100 = all to the right, none to the left. 895 attack = a parameter to the change of frequency 896 freqBase = the base frequency in the sound effect algorithm 897 898 History: 899 The `balance` argument was added on December 13, 2021 (dub v10.5) 900 901 +/ 902 @scriptable 903 void beep(int freq = 900, int dur = 150, int volume = DEFAULT_VOLUME, int balance = 50) { 904 Sample s; 905 s.operation = 0; // square wave 906 s.frequency = SampleRate / freq; 907 s.duration = dur * SampleRate / 1000; 908 s.volume = volume; 909 s.balance = balance; 910 addSample(s); 911 } 912 913 /// ditto 914 @scriptable 915 void noise(int dur = 150, int volume = DEFAULT_VOLUME, int balance = 50) { 916 Sample s; 917 s.operation = 1; // noise 918 s.frequency = 0; 919 s.volume = volume; 920 s.duration = dur * SampleRate / 1000; 921 s.balance = balance; 922 addSample(s); 923 } 924 925 /// ditto 926 @scriptable 927 void boop(float attack = 8, int freqBase = 500, int dur = 150, int volume = DEFAULT_VOLUME, int balance = 50) { 928 Sample s; 929 s.operation = 5; // custom 930 s.volume = volume; 931 s.duration = dur * SampleRate / 1000; 932 s.balance = balance; 933 s.f = delegate short(int x) { 934 auto currentFrequency = cast(float) freqBase / (1 + cast(float) x / (cast(float) SampleRate / attack)); 935 import core.stdc.math; 936 auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency); 937 return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100); 938 }; 939 addSample(s); 940 } 941 942 /// ditto 943 @scriptable 944 void blip(float attack = 6, int freqBase = 800, int dur = 150, int volume = DEFAULT_VOLUME, int balance = 50) { 945 Sample s; 946 s.operation = 5; // custom 947 s.volume = volume; 948 s.duration = dur * SampleRate / 1000; 949 s.balance = balance; 950 s.f = delegate short(int x) { 951 auto currentFrequency = cast(float) freqBase * (1 + cast(float) x / (cast(float) SampleRate / attack)); 952 import core.stdc.math; 953 auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency); 954 return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100); 955 }; 956 addSample(s); 957 } 958 959 version(none) 960 void custom(int dur = 150, int volume = DEFAULT_VOLUME) { 961 Sample s; 962 s.operation = 5; // custom 963 s.volume = volume; 964 s.duration = dur * SampleRate / 1000; 965 s.f = delegate short(int x) { 966 auto currentFrequency = 500.0 / (1 + cast(float) x / (cast(float) SampleRate / 8)); 967 import core.stdc.math; 968 auto freq = 2 * PI / (cast(float) SampleRate / currentFrequency); 969 return cast(short) (sin(cast(float) freq * cast(float) x) * short.max * volume / 100); 970 }; 971 addSample(s); 972 } 973 974 /++ 975 Plays the given midi files with the nuked opl3 emulator. 976 977 Requires nukedopl3.d (module [arsd.nukedopl3]) to be compiled in, which is GPL. 978 979 History: 980 Added December 24, 2020. 981 License: 982 If you use this function, you are opting into the GPL version 2 or later. 983 Authors: 984 Based on ketmar's code. 985 Bugs: 986 The seek method is not yet implemented. 987 +/ 988 SampleController playEmulatedOpl3Midi()(string filename, bool loop = false) { 989 import arsd.core; 990 auto bytes = cast(immutable(ubyte)[]) readBinaryFile(filename); // cast(immutable(ubyte)[]) std.file.read(filename); 991 992 return playEmulatedOpl3Midi(bytes); 993 } 994 995 /// ditto 996 SampleController playEmulatedOpl3Midi()(immutable(ubyte)[] data, bool loop = false) { 997 import arsd.nukedopl3; 998 auto scf = new SampleControlFlags; 999 1000 auto player = new OPLPlayer(this.SampleRate, true, channels == 2); 1001 // FIXME: populate the duration, support seek etc. 1002 player.looped = loop; 1003 player.load(data); 1004 player.play(); 1005 1006 addChannel( 1007 delegate bool(short[] buffer) { 1008 if(scf.paused) { 1009 buffer[] = 0; 1010 return true; 1011 } 1012 1013 if(!player.playing) { 1014 scf.finished_ = true; 1015 return false; 1016 } 1017 1018 auto pos = player.generate(buffer[]); 1019 scf.currentPosition += cast(float) buffer.length / SampleRate/ channels; 1020 if(pos == 0 || scf.stopped) { 1021 scf.finished_ = true; 1022 return false; 1023 } 1024 return !scf.stopped; 1025 } 1026 ); 1027 1028 return scf; 1029 } 1030 1031 /++ 1032 Requires vorbis.d to be compiled in (module arsd.vorbis) 1033 1034 Returns: 1035 An implementation of [SampleController] which lets you pause, etc., the file. 1036 1037 Please note that the static type may change in the future. It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playMp3] and [playWav], though all three will share an ancestor in [SampleController]. Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible. 1038 History: 1039 Automatic resampling support added Nov 7, 2020. 1040 1041 Return value changed from `void` to a sample control object on December 23, 2020. 1042 +/ 1043 SampleController playOgg()(string filename, bool loop = false) { 1044 import arsd.vorbis; 1045 auto v = new VorbisDecoder(filename); 1046 return playOgg(v, loop); 1047 } 1048 1049 /// ditto 1050 SampleController playOgg()(immutable(ubyte)[] data, bool loop = false) { 1051 import arsd.vorbis; 1052 auto v = new VorbisDecoder(cast(int) data.length, delegate int(void[] buffer, uint ofs, VorbisDecoder vb) nothrow @nogc { 1053 if(buffer is null) 1054 return 0; 1055 ubyte[] buf = cast(ubyte[]) buffer; 1056 1057 if(ofs + buf.length <= data.length) { 1058 buf[] = data[ofs .. ofs + buf.length]; 1059 return cast(int) buf.length; 1060 } else { 1061 buf[0 .. data.length - ofs] = data[ofs .. $]; 1062 return cast(int) data.length - ofs; 1063 } 1064 }); 1065 return playOgg(v, loop); 1066 } 1067 1068 // no compatibility guarantees, I can change this overload at any time! 1069 /* private */ SampleController playOgg(VorbisDecoder)(VorbisDecoder v, bool loop = false) { 1070 1071 auto scf = new SampleControlFlags; 1072 scf.detectedDuration = v.streamLengthInSeconds; 1073 1074 /+ 1075 If you want 2 channels: 1076 if the file has 2+, use them. 1077 If the file has 1, duplicate it for the two outputs. 1078 If you want 1 channel: 1079 if the file has 1, use it 1080 if the file has 2, average them. 1081 +/ 1082 1083 void plainFallback() { 1084 //if(false && v.sampleRate == SampleRate && v.chans == channels) { 1085 addChannel( 1086 delegate bool(short[] buffer) { 1087 if(scf.paused) { 1088 buffer[] = 0; 1089 return true; 1090 } 1091 if(cast(int) buffer.length != buffer.length) 1092 throw new Exception("eeeek"); 1093 1094 scf.pollUserChanges( 1095 delegate bool(float requestedSeek) { 1096 return !!v.seek(cast(uint) (scf.requestedSeek * v.sampleRate)); 1097 }, 1098 null, // can't change speed without the resampler 1099 ); 1100 1101 plain: 1102 auto got = v.getSamplesShortInterleaved(2, buffer.ptr, cast(int) buffer.length); 1103 if(got == 0) { 1104 if(loop) { 1105 v.seekStart(); 1106 scf.currentPosition = 0; 1107 return true; 1108 } 1109 1110 scf.finished_ = true; 1111 return false; 1112 } else { 1113 scf.currentPosition += cast(float) got / v.sampleRate; 1114 } 1115 if(scf.stopped) 1116 scf.finished_ = true; 1117 return !scf.stopped; 1118 } 1119 ); 1120 } 1121 1122 void withResampler() { 1123 version(with_resampler) { 1124 auto resampleContext = new class ResamplingContext { 1125 this() { 1126 super(scf, v.sampleRate, SampleRate, v.chans, channels); 1127 } 1128 1129 override void loadMoreSamples() { 1130 float*[2] tmp; 1131 tmp[0] = buffersIn[0].ptr; 1132 tmp[1] = buffersIn[1].ptr; 1133 1134 1135 scf.pollUserChanges( 1136 delegate bool(float requestedSeek) { 1137 return !!v.seekFrame(cast(uint) (scf.requestedSeek * v.sampleRate)); 1138 }, 1139 delegate bool(float requestedPlaybackSpeed) { 1140 this.changePlaybackSpeed(requestedPlaybackSpeed); 1141 return true; 1142 }, 1143 ); 1144 1145 loop: 1146 auto actuallyGot = v.getSamplesFloat(v.chans, tmp.ptr, cast(int) buffersIn[0].length); 1147 if(actuallyGot == 0 && loop) { 1148 v.seekStart(); 1149 scf.currentPosition = 0; 1150 goto loop; 1151 } 1152 1153 resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot]; 1154 if(v.chans > 1) 1155 resamplerDataRight.dataIn = buffersIn[1][0 .. actuallyGot]; 1156 } 1157 }; 1158 1159 addChannel(&resampleContext.fillBuffer); 1160 } else plainFallback(); 1161 } 1162 1163 withResampler(); 1164 1165 return scf; 1166 } 1167 1168 /++ 1169 Requires mp3.d to be compiled in (module [arsd.mp3]). 1170 1171 Returns: 1172 An implementation of [SampleController] which lets you pause, etc., the file. 1173 1174 Please note that the static type may change in the future. It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playOgg] and [playWav], though all three will share an ancestor in [SampleController]. Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible. 1175 1176 History: 1177 Automatic resampling support added Nov 7, 2020. 1178 1179 Return value changed from `void` to a sample control object on December 23, 2020. 1180 1181 The `immutable(ubyte)[]` overload was added December 30, 2020. 1182 1183 The implementation of arsd.mp3 was completely changed on November 20, 2022, adding loop and seek support. 1184 +/ 1185 SampleController playMp3()(string filename) { 1186 import std.stdio; 1187 auto fi = new File(filename); // just let the GC close it... otherwise random segfaults happen... blargh 1188 auto reader = delegate(ubyte[] buf) { 1189 return cast(int) fi.rawRead(buf[]).length; 1190 }; 1191 1192 return playMp3(reader, (ulong pos) { 1193 fi.seek(pos); 1194 return 0; 1195 }); 1196 } 1197 1198 /// ditto 1199 SampleController playMp3()(immutable(ubyte)[] data) { 1200 auto originalData = data; 1201 return playMp3( (ubyte[] buffer) { 1202 ubyte[] buf = cast(ubyte[]) buffer; 1203 if(data.length >= buf.length) { 1204 buf[] = data[0 .. buf.length]; 1205 data = data[buf.length .. $]; 1206 return cast(int) buf.length; 1207 } else { 1208 auto it = data.length; 1209 buf[0 .. data.length] = data[]; 1210 buf[data.length .. $] = 0; 1211 data = data[$ .. $]; 1212 return cast(int) it; 1213 } 1214 }, (ulong pos) { 1215 data = originalData[pos .. $]; 1216 return 0; 1217 }); 1218 } 1219 1220 // no compatibility guarantees, I can change this overload at any time! 1221 /* private */ SampleController playMp3()(int delegate(ubyte[]) reader, int delegate(ulong) seeker) { 1222 import arsd.mp3; 1223 1224 auto mp3 = new MP3Decoder(reader, seeker); 1225 if(!mp3.valid) 1226 throw new Exception("file not valid"); 1227 1228 auto scf = new SampleControlFlags; 1229 scf.detectedDuration = mp3.duration; 1230 1231 void plainFallback() { 1232 // if these aren't true this will not work right but im not gonna require it per se 1233 // if(mp3.sampleRate == SampleRate && mp3.channels == channels) { ... } 1234 1235 auto next = mp3.frameSamples; 1236 1237 addChannel( 1238 delegate bool(short[] buffer) { 1239 if(scf.paused) { 1240 buffer[] = 0; 1241 return true; 1242 } 1243 1244 if(cast(int) buffer.length != buffer.length) 1245 throw new Exception("eeeek"); 1246 1247 scf.pollUserChanges( 1248 delegate bool(float requestedSeek) { 1249 return mp3.seek(cast(uint) (requestedSeek * mp3.sampleRate * mp3.channels)); 1250 }, 1251 null, // can't change speed without the resampler 1252 ); 1253 1254 more: 1255 if(next.length >= buffer.length) { 1256 buffer[] = next[0 .. buffer.length]; 1257 next = next[buffer.length .. $]; 1258 1259 scf.currentPosition += cast(float) buffer.length / mp3.sampleRate / mp3.channels * scf.playbackSpeed; 1260 } else { 1261 buffer[0 .. next.length] = next[]; 1262 buffer = buffer[next.length .. $]; 1263 1264 scf.currentPosition += cast(float) next.length / mp3.sampleRate / mp3.channels * scf.playbackSpeed; 1265 1266 next = next[$..$]; 1267 1268 if(buffer.length) { 1269 if(mp3.valid) { 1270 mp3.decodeNextFrame(); 1271 next = mp3.frameSamples; 1272 goto more; 1273 } else { 1274 buffer[] = 0; 1275 scf.finished_ = true; 1276 return false; 1277 } 1278 } 1279 } 1280 1281 if(scf.stopped) { 1282 scf.finished_ = true; 1283 } 1284 return !scf.stopped; 1285 } 1286 ); 1287 } 1288 1289 void resamplingVersion() { 1290 version(with_resampler) { 1291 mp3.decodeNextFrame(); 1292 auto next = mp3.frameSamples; 1293 1294 auto resampleContext = new class ResamplingContext { 1295 this() { 1296 super(scf, mp3.sampleRate, SampleRate, mp3.channels, channels); 1297 } 1298 1299 override void loadMoreSamples() { 1300 1301 scf.pollUserChanges( 1302 delegate bool(float requestedSeek) { 1303 return mp3.seek(cast(uint) (requestedSeek * mp3.sampleRate * mp3.channels)); 1304 }, 1305 delegate bool(float requestedPlaybackSpeed) { 1306 this.changePlaybackSpeed(requestedPlaybackSpeed); 1307 return true; 1308 }, 1309 ); 1310 1311 if(mp3.channels == 1) { 1312 int actuallyGot; 1313 1314 foreach(ref b; buffersIn[0]) { 1315 if(next.length == 0) break; 1316 b = cast(float) next[0] / short.max; 1317 next = next[1 .. $]; 1318 if(next.length == 0) { 1319 mp3.decodeNextFrame(); 1320 next = mp3.frameSamples; 1321 } 1322 actuallyGot++; 1323 } 1324 resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot]; 1325 } else { 1326 int actuallyGot; 1327 1328 foreach(idx, ref b; buffersIn[0]) { 1329 if(next.length == 0) break; 1330 b = cast(float) next[0] / short.max; 1331 next = next[1 .. $]; 1332 if(next.length == 0) { 1333 mp3.decodeNextFrame(); 1334 next = mp3.frameSamples; 1335 } 1336 buffersIn[1][idx] = cast(float) next[0] / short.max; 1337 next = next[1 .. $]; 1338 if(next.length == 0) { 1339 mp3.decodeNextFrame(); 1340 next = mp3.frameSamples; 1341 } 1342 actuallyGot++; 1343 } 1344 resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot]; 1345 resamplerDataRight.dataIn = buffersIn[1][0 .. actuallyGot]; 1346 } 1347 } 1348 }; 1349 1350 addChannel(&resampleContext.fillBuffer); 1351 1352 } else plainFallback(); 1353 } 1354 1355 resamplingVersion(); 1356 1357 return scf; 1358 } 1359 1360 /++ 1361 Requires [arsd.wav]. Only supports simple 8 or 16 bit wav files, no extensible or float formats at this time. 1362 1363 Also requires the resampler to be compiled in at this time, but that may change in the future, I was just lazy. 1364 1365 Returns: 1366 An implementation of [SampleController] which lets you pause, etc., the file. 1367 1368 Please note that the static type may change in the future. It will always be a subtype of [SampleController], but it may be more specialized as I add more features and this will not necessarily match its sister functions, [playMp3] and [playOgg], though all three will share an ancestor in [SampleController]. Therefore, if you use `auto`, there's no guarantee the static type won't change in future versions and I will NOT consider that a breaking change since the base interface will remain compatible. 1369 Bugs: 1370 The seek method is not yet implemented. 1371 History: 1372 Added Nov 8, 2020. 1373 1374 Return value changed from `void` to a sample control object on December 23, 2020. 1375 +/ 1376 SampleController playWav(R)(R filename_or_data) if(is(R == string) /* filename */ || is(R == immutable(ubyte)[]) /* data */ ) { 1377 auto scf = new SampleControlFlags; 1378 // FIXME: support seeking 1379 version(with_resampler) { 1380 auto resampleContext = new class ResamplingContext { 1381 import arsd.wav; 1382 1383 this() { 1384 reader = wavReader(filename_or_data); 1385 next = reader.front; 1386 1387 scf.detectedDuration = reader.duration; 1388 1389 super(scf, reader.sampleRate, SampleRate, reader.numberOfChannels, channels); 1390 } 1391 1392 typeof(wavReader(filename_or_data)) reader; 1393 const(ubyte)[] next; 1394 1395 override void loadMoreSamples() { 1396 1397 // FIXME: pollUserChanges once seek is implemented 1398 1399 bool moar() { 1400 if(next.length == 0) { 1401 if(reader.empty) 1402 return false; 1403 reader.popFront; 1404 next = reader.front; 1405 if(next.length == 0) 1406 return false; 1407 } 1408 return true; 1409 } 1410 1411 if(reader.numberOfChannels == 1) { 1412 int actuallyGot; 1413 1414 foreach(ref b; buffersIn[0]) { 1415 if(!moar) break; 1416 if(reader.bitsPerSample == 8) { 1417 b = (cast(float) next[0] - 128.0f) / 127.0f; 1418 next = next[1 .. $]; 1419 } else if(reader.bitsPerSample == 16) { 1420 short n = next[0]; 1421 next = next[1 .. $]; 1422 if(!moar) break; 1423 n |= cast(ushort)(next[0]) << 8; 1424 next = next[1 .. $]; 1425 1426 b = (cast(float) n) / short.max; 1427 } else assert(0); 1428 1429 actuallyGot++; 1430 } 1431 resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot]; 1432 } else { 1433 int actuallyGot; 1434 1435 foreach(idx, ref b; buffersIn[0]) { 1436 if(!moar) break; 1437 if(reader.bitsPerSample == 8) { 1438 b = (cast(float) next[0] - 128.0f) / 127.0f; 1439 next = next[1 .. $]; 1440 1441 if(!moar) break; 1442 buffersIn[1][idx] = (cast(float) next[0] - 128.0f) / 127.0f; 1443 next = next[1 .. $]; 1444 } else if(reader.bitsPerSample == 16) { 1445 short n = next[0]; 1446 next = next[1 .. $]; 1447 if(!moar) break; 1448 n |= cast(ushort)(next[0]) << 8; 1449 next = next[1 .. $]; 1450 1451 b = (cast(float) n) / short.max; 1452 1453 if(!moar) break; 1454 n = next[0]; 1455 next = next[1 .. $]; 1456 if(!moar) break; 1457 n |= cast(ushort)(next[0]) << 8; 1458 next = next[1 .. $]; 1459 1460 buffersIn[1][idx] = (cast(float) n) / short.max; 1461 } else assert(0); 1462 1463 1464 actuallyGot++; 1465 } 1466 resamplerDataLeft.dataIn = buffersIn[0][0 .. actuallyGot]; 1467 resamplerDataRight.dataIn = buffersIn[1][0 .. actuallyGot]; 1468 } 1469 } 1470 }; 1471 1472 addChannel(&resampleContext.fillBuffer); 1473 1474 } else static assert(0, "I was lazy and didn't implement straight-through playing"); 1475 1476 return scf; 1477 } 1478 1479 /++ 1480 A helper object to create synthesized sound samples. 1481 1482 Construct it with the [synth] function. 1483 1484 History: 1485 Added October 29, 2022 (dub v10.10) 1486 1487 Didn't actually work until November 29, 2025. 1488 1489 Examples: 1490 --- 1491 AudioOutputThread ao = AudioOutputThread(true); 1492 with(ao.synth) { 1493 squareWave(frequency: 600, duration: 200); 1494 sync; 1495 silence(duration: 200); 1496 sync; 1497 squareWave(frequency: 100, duration: 200); 1498 1499 startPlaying; 1500 --- 1501 +/ 1502 static struct SynthBuilder { 1503 private this(AudioPcmOutThreadImplementation ao) { 1504 this.ao = ao; 1505 this.buffer = staticBuffer[]; 1506 } 1507 // prolly want a tree of things that can be simultaneous sounds or sequential sounds 1508 1509 private AudioPcmOutThreadImplementation ao; 1510 private AudioPcmOutThreadImplementation.Sample[8] staticBuffer; 1511 private AudioPcmOutThreadImplementation.Sample[] buffer; 1512 private int bufferPos; 1513 1514 private int currentSyncPoint; 1515 private int currentDelay; 1516 1517 /++ 1518 1519 History: 1520 Added November 29, 2025 1521 +/ 1522 SampleController startPlaying() { 1523 sync(); 1524 return ao.addOverlappedSamples(buffer[0 .. bufferPos]); 1525 } 1526 1527 /++ 1528 Hacky way to test this, keeping the main thread alive until the synth finishes playing. 1529 1530 History: 1531 Added November 29, 2025 1532 +/ 1533 void sleepUntilComplete() { 1534 Thread.sleep((currentDelay * 1000 / ao.SampleRate).msecs); 1535 } 1536 1537 /++ 1538 1539 History: 1540 Added November 29, 2025 1541 +/ 1542 void sync() { 1543 int max; 1544 foreach(sample; buffer[currentSyncPoint .. bufferPos]) { 1545 auto d = sample.delay + sample.duration - currentDelay; 1546 if(d > max) 1547 max = d; 1548 } 1549 1550 currentSyncPoint = bufferPos; 1551 currentDelay += max; 1552 } 1553 1554 /++ 1555 The units here are mostly milliseconds and hertz. 1556 1557 History: 1558 Added November 29, 2025 1559 +/ 1560 ref SynthBuilder opDispatch(string op)(int delay = 0, int frequency = 600, int duration = 250, int volume = DEFAULT_VOLUME, int balance = 50, int attack = 0, int decay = 0, int sustainLevel = 100, int release = 0) { 1561 AudioPcmOutThreadImplementation.Sample sample; 1562 if(ao is null) 1563 return this; 1564 1565 sample.operation = __traits(getMember, AudioPcmOutThreadImplementation.Sample.Operation, op); 1566 sample.delay = currentDelay + delay * ao.SampleRate / 1000; 1567 sample.frequency = ao.SampleRate / frequency; 1568 sample.duration = duration * ao.SampleRate / 1000; 1569 sample.volume = volume; 1570 sample.balance = balance; 1571 1572 sample.attack = attack * ao.SampleRate / 1000; 1573 sample.decay = decay * ao.SampleRate / 1000; 1574 sample.sustainLevel = sustainLevel; 1575 sample.release = release * ao.SampleRate / 1000; 1576 1577 this.addSample(sample); 1578 1579 return this; 1580 } 1581 1582 private void addSample(AudioPcmOutThreadImplementation.Sample sample) { 1583 if(bufferPos == buffer.length) { 1584 buffer.length = buffer.length * 2; 1585 } 1586 buffer[bufferPos++] = sample; 1587 } 1588 } 1589 1590 /// ditto 1591 SynthBuilder synth() { 1592 return SynthBuilder(this); 1593 } 1594 1595 static struct Sample { 1596 enum Operation { 1597 squareWave = 0, 1598 noise = 1, 1599 triangleWave = 2, 1600 sawtoothWave = 3, 1601 sineWave = 4, 1602 customFunction = 5, 1603 silence = 6, 1604 } 1605 1606 /+ 1607 static Sample opDispatch(string operation)(int frequency) if(__traits(hasMember, Operation, operation)) { 1608 Sample s; 1609 s.operation = cast(int) __traits(getMember, Operation, operation); 1610 s.frequency = frequency; 1611 return s; 1612 } 1613 +/ 1614 1615 int operation; 1616 int delay; /* in samples */ 1617 int frequency; /* in samples */ 1618 int duration; /* in samples */ 1619 int volume = DEFAULT_VOLUME; /* between 1 and 100. You should generally shoot for something lowish, like 20. */ 1620 int balance = 50; /* between 0 and 100 */ 1621 1622 // volume envelope 1623 int attack; /* in samples */ 1624 int decay; /* in samples */ 1625 int sustainLevel = 100; // in percentage of the current volume. sustain time is duration - attack - decay - release. 1626 // while in sustain, it can add some vibrato to the amplitude 1627 //int volumeVibratoRange = 0; // amplitude of difference, in volume percentage points 1628 //int volumeVibratoSpeed = 0; // frequency of difference, in samples per period 1629 int release; /* in samples */ 1630 1631 /+ 1632 // change in frequency 1633 int frequencyAttack; 1634 1635 int vibratoRange; // change of frequency as it sustains. set to zero for no vibrato 1636 int vibratoSpeed; // how fast it cycles through the vibratoRange; a sine wave frequency. 1637 1638 // frequencySustain is always `duration - (frequencyAttack + frequencyRelease)`, floor of zero. 1639 1640 int frequencyRelease; 1641 +/ 1642 1643 short delegate(int x) f; 1644 1645 // internal info 1646 private { 1647 int x; // only for custom function 1648 1649 bool left; 1650 short val; 1651 int leftMultiplier; 1652 int rightMultiplier; 1653 int frequencyCounter; 1654 int ticker; 1655 1656 int volumeNow() const { 1657 auto maxVolume = short.max * volume * 100 / 100 / 100; 1658 auto minVolume = 0; 1659 auto getTo = short.max * volume * sustainLevel / 100 / 100; 1660 if(attack) { 1661 auto t = ticker; 1662 1663 if(t <= attack) { 1664 return maxVolume * t / attack; 1665 } 1666 } 1667 if(decay) { 1668 auto t = ticker - attack; 1669 1670 if(t <= decay) { 1671 return (maxVolume - getTo) * (decay-t) / decay + getTo; 1672 } 1673 } 1674 if(release) { 1675 //auto t = ticker - decay - attack; 1676 // note that duration has been mutated by now 1677 if(duration <= release) { 1678 return getTo * duration / release; 1679 } 1680 } 1681 1682 return getTo; // the sustain 1683 } 1684 } 1685 } 1686 1687 /++ 1688 These will destructively edit the passed array, using its memory for running status information in the audio thread. Once you pass it, you must abandon it. 1689 +/ 1690 final SampleController addSample(Sample[] samples...) { 1691 long place; 1692 foreach(ref sample; samples) { 1693 auto oldPlace = place; 1694 place += sample.delay + sample.duration; 1695 sample.delay += oldPlace; 1696 } 1697 1698 return addOverlappedSamples(samples); 1699 } 1700 1701 /// ditto 1702 final SampleController addOverlappedSamples(Sample[] samples...) { 1703 if(samples.length == 0) 1704 return null; 1705 1706 if(samples.length) 1707 samples = samples.dup; // ensure it isn't in stack memory that might get smashed when the delegate is passed to the other thread 1708 1709 enum divisor = 50; 1710 1711 int timer; 1712 int max; 1713 foreach(ref sample; samples) { 1714 sample.left = true; 1715 sample.val = 1; 1716 sample.leftMultiplier = 50 + (50 - sample.balance); 1717 sample.rightMultiplier = 50 + (sample.balance - 50); 1718 1719 auto end = sample.delay + sample.duration; 1720 if(end > max) 1721 max = end; 1722 } 1723 1724 auto scf = new SampleControlFlags; 1725 scf.detectedDuration = cast(float) max / SampleRate; 1726 1727 max *= 2; // cuz of stereo 1728 1729 addChannel( 1730 delegate bool (short[] buffer) { 1731 1732 buffer[] = 0; 1733 1734 if(scf.stopped) { 1735 scf.finished_ = true; 1736 return false; 1737 } 1738 1739 if(scf.paused_) 1740 return true; 1741 1742 foreach(ref currentSample; samples) with(currentSample) { 1743 int timerChange; 1744 if(currentSample.duration) { 1745 size_t i = 0; 1746 if(currentSample.delay) { 1747 if(buffer.length <= currentSample.delay * 2) { 1748 // whole buffer consumed by delay 1749 currentSample.delay -= buffer.length / 2; 1750 } else { 1751 i = currentSample.delay * 2; 1752 currentSample.delay = 0; 1753 } 1754 } 1755 if(currentSample.delay > 0) { 1756 continue; 1757 } 1758 1759 size_t sampleFinish; 1760 if(currentSample.duration * 2 <= buffer.length) { 1761 sampleFinish = currentSample.duration * 2; 1762 timerChange = currentSample.duration; 1763 currentSample.duration = 0; 1764 } else { 1765 sampleFinish = buffer.length; 1766 currentSample.duration -= buffer.length / 2; 1767 timerChange = cast(int) buffer.length / 2; 1768 } 1769 1770 switch(currentSample.operation) { 1771 case 0: // square wave 1772 for(; i < sampleFinish; i++) { 1773 buffer[i] += cast(short)( 1774 (val < 0 ? -1 : 1) * 1775 volumeNow() * 1776 (left ? leftMultiplier : rightMultiplier) / divisor); 1777 left = !left; 1778 // left and right do the same thing so we only count 1779 // every other sample 1780 if(i & 1) { 1781 if(frequencyCounter) 1782 frequencyCounter--; 1783 if(frequencyCounter == 0) { 1784 // are you kidding me dmd? random casts suck 1785 val = cast(short) -val; 1786 frequencyCounter = currentSample.frequency / 2; 1787 } 1788 } 1789 } 1790 break; 1791 case 1: // noise 1792 for(; i < sampleFinish; i++) { 1793 import std.random; 1794 if(volumeNow != 0) 1795 buffer[i] += cast(short)((left ? leftMultiplier : rightMultiplier) * uniform(cast(short) -volumeNow, volumeNow) / divisor); 1796 left = !left; 1797 } 1798 break; 1799 /+ 1800 case 2: // triangle wave 1801 1802 short[] tone; 1803 tone.length = 22050 * len / 1000; 1804 1805 short valmax = cast(short) (cast(int) volume * short.max / 100); 1806 int wavelength = 22050 / freq; 1807 wavelength /= 2; 1808 int da = valmax / wavelength; 1809 int val = 0; 1810 1811 for(int a = 0; a < tone.length; a++){ 1812 tone[a] = cast(short) val; 1813 val+= da; 1814 if(da > 0 && val >= valmax) 1815 da *= -1; 1816 if(da < 0 && val <= -valmax) 1817 da *= -1; 1818 } 1819 1820 data ~= tone; 1821 1822 1823 for(; i < sampleFinish; i++) { 1824 buffer[i] += val; 1825 // left and right do the same thing so we only count 1826 // every other sample 1827 if(i & 1) { 1828 if(frequencyCounter) 1829 frequencyCounter--; 1830 if(frequencyCounter == 0) { 1831 val = 0; 1832 frequencyCounter = currentSample.frequency / 2; 1833 } 1834 } 1835 } 1836 1837 break; 1838 case 3: // sawtooth wave 1839 short[] tone; 1840 tone.length = 22050 * len / 1000; 1841 1842 int valmax = volume * short.max / 100; 1843 int wavelength = 22050 / freq; 1844 int da = valmax / wavelength; 1845 short val = 0; 1846 1847 for(int a = 0; a < tone.length; a++){ 1848 tone[a] = val; 1849 val+= da; 1850 if(val >= valmax) 1851 val = 0; 1852 } 1853 1854 data ~= tone; 1855 case 4: // sine wave 1856 short[] tone; 1857 tone.length = 22050 * len / 1000; 1858 1859 int valmax = volume * short.max / 100; 1860 int val = 0; 1861 1862 float i = 2*PI / (22050/freq); 1863 1864 float f = 0; 1865 for(int a = 0; a < tone.length; a++){ 1866 tone[a] = cast(short) (valmax * sin(f)); 1867 f += i; 1868 if(f>= 2*PI) 1869 f -= 2*PI; 1870 } 1871 1872 data ~= tone; 1873 1874 +/ 1875 case 5: // custom function 1876 val = currentSample.f(currentSample.x); 1877 for(; i < sampleFinish; i++) { 1878 buffer[i] += cast(short)(val * (left ? leftMultiplier : rightMultiplier) / divisor); 1879 left = !left; 1880 if(i & 1) { 1881 currentSample.x++; 1882 val = currentSample.f(currentSample.x); 1883 } 1884 } 1885 break; 1886 case 6: // silence 1887 // buffer[i .. sampleFinish] += 0; 1888 i = sampleFinish; 1889 break; 1890 default: // unknown; use silence 1891 currentSample.duration = 0; 1892 } 1893 1894 // if(i < buffer.length) buffer[i .. $] += 0; 1895 } 1896 1897 currentSample.ticker += timerChange; 1898 } 1899 1900 timer += buffer.length; 1901 if(timer >= max) 1902 scf.finished_ = true; 1903 scf.currentPosition = cast(float) timer / max; 1904 1905 return timer < max; 1906 } 1907 ); 1908 1909 return scf; 1910 } 1911 1912 /++ 1913 The delegate returns false when it is finished (true means keep going). 1914 It must fill the buffer with waveform data on demand and must be latency 1915 sensitive; as fast as possible. 1916 +/ 1917 public void addChannel(bool delegate(short[] buffer) dg) { 1918 synchronized(this) { 1919 // silently drops info if we don't have room in the buffer... 1920 // don't do a lot of long running things lol 1921 if(fillDatasLength < fillDatas.length) 1922 fillDatas[fillDatasLength++] = dg; 1923 } 1924 } 1925 1926 private { 1927 AudioOutput* ao; 1928 1929 bool delegate(short[] buffer)[32] fillDatas; 1930 int fillDatasLength = 0; 1931 } 1932 1933 1934 private bool suspendWanted; 1935 private bool exiting; 1936 1937 private bool suspended_; 1938 1939 /++ 1940 Stops playing and closes the audio device, but keeps the worker thread 1941 alive and waiting for a call to [unsuspend], which will re-open everything 1942 and pick up (close to; a couple buffers may be discarded) where it left off. 1943 1944 This is more reliable than [pause] and [unpause] since it doesn't require 1945 the system/hardware to cooperate. 1946 1947 History: 1948 Added December 30, 2021 (dub v10.5) 1949 +/ 1950 public void suspend() { 1951 suspended_ = true; 1952 suspendWanted = true; 1953 if(ao) 1954 ao.stop(); 1955 } 1956 1957 /// ditto 1958 public void unsuspend() { 1959 suspended_ = false; 1960 suspendWanted = false; 1961 static if(__traits(hasMember, event, "setIfInitialized")) 1962 event.setIfInitialized(); 1963 else 1964 event.set(); 1965 } 1966 1967 /// ditto 1968 public bool suspended() { 1969 return suspended_; 1970 } 1971 1972 /++ 1973 Stops playback and unsupends if necessary and exits. 1974 1975 Call this instead of join. 1976 1977 Please note: you should never call this from inside an audio 1978 callback, as it will crash or deadlock. Instead, just return false 1979 from your buffer fill function to indicate that you are done. 1980 1981 History: 1982 Added December 30, 2021 (dub v10.5) 1983 +/ 1984 public Throwable exit(bool rethrow = false) { 1985 exiting = true; 1986 unsuspend(); 1987 if(ao) 1988 ao.stop(); 1989 1990 return join(rethrow); 1991 } 1992 1993 private void run() { 1994 try { 1995 runInsideTryCatch(); 1996 } catch(Throwable t) { 1997 import arsd.core; 1998 writelnStderr("audio thread crashed: ", t.toString); 1999 throw t; 2000 } 2001 } 2002 2003 private void runInsideTryCatch() { 2004 version(linux) { 2005 // this thread has no business intercepting signals from the main thread, 2006 // so gonna block a couple of them 2007 import core.sys.posix.signal; 2008 sigset_t sigset; 2009 auto err = sigemptyset(&sigset); 2010 assert(!err); 2011 2012 err = sigaddset(&sigset, SIGINT); assert(!err); 2013 err = sigaddset(&sigset, SIGCHLD); assert(!err); 2014 2015 err = sigprocmask(SIG_BLOCK, &sigset, null); 2016 assert(!err); 2017 } 2018 2019 AudioOutput ao = AudioOutput(device, SampleRate, channels); 2020 2021 this.ao = &ao; 2022 scope(exit) this.ao = null; 2023 auto omg = this; 2024 ao.fillData = (short[] buffer) { 2025 short[BUFFER_SIZE_SHORT] bfr; 2026 bool first = true; 2027 if(fillDatasLength) { 2028 for(int idx = 0; idx < fillDatasLength; idx++) { 2029 auto dg = fillDatas[idx]; 2030 auto ret = dg(bfr[0 .. buffer.length][]); 2031 foreach(i, v; bfr[0 .. buffer.length][]) { 2032 int val; 2033 if(first) 2034 val = 0; 2035 else 2036 val = buffer[i]; 2037 2038 int a = val; 2039 int b = v; 2040 int cap = a + b; 2041 if(cap > short.max) cap = short.max; 2042 else if(cap < short.min) cap = short.min; 2043 val = cast(short) cap; 2044 buffer[i] = cast(short) val; 2045 } 2046 if(!ret) { 2047 // it returned false meaning this one is finished... 2048 synchronized(omg) { 2049 fillDatas[idx] = fillDatas[fillDatasLength - 1]; 2050 fillDatasLength--; 2051 } 2052 idx--; 2053 } 2054 2055 first = false; 2056 } 2057 } else { 2058 buffer[] = 0; 2059 } 2060 }; 2061 //try 2062 resume_from_suspend: 2063 ao.play(); 2064 /+ 2065 catch(Throwable t) { 2066 import std.stdio; 2067 writeln(t); 2068 } 2069 +/ 2070 2071 if(suspendWanted) { 2072 ao.close(); 2073 2074 event.initialize(true, false); 2075 if(event.wait() && !exiting) { 2076 event.reset(); 2077 2078 ao.open(); 2079 goto resume_from_suspend; 2080 } 2081 } 2082 2083 event.terminate(); 2084 } 2085 2086 static if(__VERSION__ > 2080) { 2087 import core.sync.event; 2088 } else { 2089 // bad emulation of the Event but meh 2090 static struct Event { 2091 void terminate() {} 2092 void initialize(bool, bool) {} 2093 2094 bool isSet; 2095 2096 void set() { isSet = true; } 2097 void reset() { isSet = false; } 2098 bool wait() { 2099 while(!isSet) { 2100 Thread.sleep(500.msecs); 2101 } 2102 isSet = false; 2103 return true; 2104 } 2105 2106 } 2107 } 2108 2109 Event event; 2110 } 2111 2112 2113 import core.stdc.config; 2114 2115 version(linux) version=ALSA; 2116 version(Windows) version=WinMM; 2117 2118 version(ALSA) { 2119 // this is the virtual rawmidi device on my computer at least 2120 // maybe later i'll make it probe 2121 // 2122 // Getting midi to actually play on Linux is a bit of a pain. 2123 // Here's what I did: 2124 /* 2125 # load the kernel driver, if amidi -l gives ioctl error, 2126 # you haven't done this yet! 2127 modprobe snd-virmidi 2128 2129 # start a software synth. timidity -iA is also an option 2130 fluidsynth soundfont.sf2 2131 2132 # connect the virtual hardware port to the synthesizer 2133 aconnect 24:0 128:0 2134 2135 2136 I might also add a snd_seq client here which is a bit 2137 easier to setup but for now I'm using the rawmidi so you 2138 gotta get them connected somehow. 2139 */ 2140 2141 // fyi raw midi dump: amidi -d --port hw:4,0 2142 // connect my midi out to fluidsynth: aconnect 28:0 128:0 2143 // and my keyboard to it: aconnect 32:0 128:0 2144 } 2145 2146 /// Thrown on audio failures. 2147 /// Subclass this to provide OS-specific exceptions 2148 class AudioException : Exception { 2149 this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 2150 super(message, file, line, next); 2151 } 2152 } 2153 2154 /++ 2155 Gives PCM input access (such as a microphone). 2156 2157 History: 2158 Windows support added May 10, 2020 and the API got overhauled too. 2159 +/ 2160 struct AudioInput { 2161 version(ALSA) { 2162 snd_pcm_t* handle; 2163 } else version(WinMM) { 2164 HWAVEIN handle; 2165 HANDLE event; 2166 } else static assert(0); 2167 2168 @disable this(); 2169 @disable this(this); 2170 2171 int channels; 2172 int SampleRate; 2173 2174 /// Always pass card == 0. 2175 this(int card, int SampleRate = 44100, int channels = 2) { 2176 assert(card == 0); 2177 this("default", SampleRate, channels); 2178 } 2179 2180 /++ 2181 `device` is a device name. On Linux, it is the ALSA string. 2182 On Windows, it is currently ignored, so you should pass "default" 2183 or null so when it does get implemented your code won't break. 2184 2185 History: 2186 Added Nov 8, 2020. 2187 +/ 2188 this(string device, int SampleRate = 44100, int channels = 2) { 2189 assert(channels == 1 || channels == 2); 2190 2191 this.channels = channels; 2192 this.SampleRate = SampleRate; 2193 2194 version(ALSA) { 2195 handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_CAPTURE, SampleRate, channels, device); 2196 } else version(WinMM) { 2197 event = CreateEvent(null, false /* manual reset */, false /* initially triggered */, null); 2198 2199 WAVEFORMATEX format; 2200 format.wFormatTag = WAVE_FORMAT_PCM; 2201 format.nChannels = 2; 2202 format.nSamplesPerSec = SampleRate; 2203 format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample 2204 format.nBlockAlign = 4; 2205 format.wBitsPerSample = 16; 2206 format.cbSize = 0; 2207 if(auto err = waveInOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) event, cast(DWORD_PTR) &this, CALLBACK_EVENT)) 2208 throw new WinMMException("wave in open", err); 2209 2210 } else static assert(0); 2211 } 2212 2213 /// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz 2214 /// Each item in the array thus alternates between left and right channel 2215 /// and it takes a total of 88,200 items to make one second of sound. 2216 /// 2217 /// Returns the slice of the buffer actually read into 2218 /// 2219 /// LINUX ONLY. You should prolly use [record] instead 2220 version(ALSA) 2221 short[] read(short[] buffer) { 2222 snd_pcm_sframes_t read; 2223 2224 read = snd_pcm_readi(handle, buffer.ptr, buffer.length / channels /* div number of channels apparently */); 2225 if(read < 0) { 2226 read = snd_pcm_recover(handle, cast(int) read, 0); 2227 if(read < 0) 2228 throw new AlsaException("pcm read", cast(int)read); 2229 return null; 2230 } 2231 2232 return buffer[0 .. read * channels]; 2233 } 2234 2235 /// passes a buffer of data to fill 2236 /// 2237 /// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz 2238 /// Each item in the array thus alternates between left and right channel 2239 /// and it takes a total of 88,200 items to make one second of sound. 2240 void delegate(short[]) receiveData; 2241 2242 /// 2243 void stop() { 2244 recording = false; 2245 } 2246 2247 /// First, set [receiveData], then call this. 2248 void record() @system /* FIXME https://issues.dlang.org/show_bug.cgi?id=24782 */ { 2249 assert(receiveData !is null); 2250 recording = true; 2251 2252 version(ALSA) { 2253 short[BUFFER_SIZE_SHORT] buffer; 2254 while(recording) { 2255 auto got = read(buffer); 2256 receiveData(got); 2257 } 2258 } else version(WinMM) { 2259 2260 enum numBuffers = 2; // use a lot of buffers to get smooth output with Sleep, see below 2261 short[BUFFER_SIZE_SHORT][numBuffers] buffers; 2262 2263 WAVEHDR[numBuffers] headers; 2264 2265 foreach(i, ref header; headers) { 2266 auto buffer = buffers[i][]; 2267 header.lpData = cast(char*) buffer.ptr; 2268 header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof; 2269 header.dwFlags = 0;// WHDR_BEGINLOOP | WHDR_ENDLOOP; 2270 header.dwLoops = 0; 2271 2272 if(auto err = waveInPrepareHeader(handle, &header, header.sizeof)) 2273 throw new WinMMException("prepare header", err); 2274 2275 header.dwUser = 1; // mark that the driver is using it 2276 if(auto err = waveInAddBuffer(handle, &header, header.sizeof)) 2277 throw new WinMMException("wave in read", err); 2278 } 2279 2280 waveInStart(handle); 2281 scope(failure) waveInReset(handle); 2282 2283 while(recording) { 2284 if(auto err = WaitForSingleObject(event, INFINITE)) 2285 throw new Exception("WaitForSingleObject"); 2286 if(!recording) 2287 break; 2288 2289 foreach(ref header; headers) { 2290 if(!(header.dwFlags & WHDR_DONE)) continue; 2291 2292 receiveData((cast(short*) header.lpData)[0 .. header.dwBytesRecorded / short.sizeof]); 2293 if(!recording) break; 2294 header.dwUser = 1; // mark that the driver is using it 2295 if(auto err = waveInAddBuffer(handle, &header, header.sizeof)) { 2296 throw new WinMMException("waveInAddBuffer", err); 2297 } 2298 } 2299 } 2300 2301 /* 2302 if(auto err = waveInStop(handle)) 2303 throw new WinMMException("wave in stop", err); 2304 */ 2305 2306 if(auto err = waveInReset(handle)) { 2307 throw new WinMMException("wave in reset", err); 2308 } 2309 2310 still_in_use: 2311 foreach(idx, header; headers) 2312 if(!(header.dwFlags & WHDR_DONE)) { 2313 Sleep(1); 2314 goto still_in_use; 2315 } 2316 2317 foreach(ref header; headers) 2318 if(auto err = waveInUnprepareHeader(handle, &header, header.sizeof)) { 2319 throw new WinMMException("unprepare header", err); 2320 } 2321 2322 ResetEvent(event); 2323 } else static assert(0); 2324 } 2325 2326 private bool recording; 2327 2328 ~this() { 2329 receiveData = null; 2330 version(ALSA) { 2331 snd_pcm_close(handle); 2332 } else version(WinMM) { 2333 if(auto err = waveInClose(handle)) 2334 throw new WinMMException("close", err); 2335 2336 CloseHandle(event); 2337 // in wine (though not Windows nor winedbg as far as I can tell) 2338 // this randomly segfaults. the sleep prevents it. idk why. 2339 Sleep(5); 2340 } else static assert(0); 2341 } 2342 } 2343 2344 /// 2345 enum SampleRateFull = 44100; 2346 2347 /// Gives PCM output access (such as the speakers). 2348 struct AudioOutput { 2349 version(ALSA) { 2350 snd_pcm_t* handle; 2351 } else version(WinMM) { 2352 HWAVEOUT handle; 2353 } 2354 2355 @disable this(); 2356 // This struct must NEVER be moved or copied, a pointer to it may 2357 // be passed to a device driver and stored! 2358 @disable this(this); 2359 2360 private int SampleRate; 2361 private int channels; 2362 private string device; 2363 2364 /++ 2365 `device` is a device name. On Linux, it is the ALSA string. 2366 On Windows, it is currently ignored, so you should pass "default" 2367 or null so when it does get implemented your code won't break. 2368 2369 History: 2370 Added Nov 8, 2020. 2371 +/ 2372 this(string device, int SampleRate = 44100, int channels = 2) { 2373 assert(channels == 1 || channels == 2); 2374 2375 this.SampleRate = SampleRate; 2376 this.channels = channels; 2377 this.device = device; 2378 2379 open(); 2380 } 2381 2382 /// Always pass card == 0. 2383 this(int card, int SampleRate = 44100, int channels = 2) { 2384 assert(card == 0); 2385 2386 this("default", SampleRate, channels); 2387 } 2388 2389 /// passes a buffer of data to fill 2390 /// 2391 /// Data is assumed to be interleaved stereo, LE 16 bit, 44.1 kHz (unless you change that in the ctor) 2392 /// Each item in the array thus alternates between left and right channel (unless you change that in the ctor) 2393 /// and it takes a total of 88,200 items to make one second of sound. 2394 void delegate(short[]) fillData; 2395 2396 shared(bool) playing = false; // considered to be volatile 2397 2398 /// Starts playing, loops until stop is called 2399 void play() @system /* FIXME https://issues.dlang.org/show_bug.cgi?id=24782 */ { 2400 if(handle is null) 2401 open(); 2402 2403 assert(fillData !is null); 2404 playing = true; 2405 2406 version(ALSA) { 2407 short[BUFFER_SIZE_SHORT] buffer; 2408 while(playing) { 2409 auto err = snd_pcm_wait(handle, 500); 2410 if(err < 0) { 2411 // see: https://stackoverflow.com/a/59400592/1457000 2412 err = snd_pcm_recover(handle, err, 0); 2413 if(err) 2414 throw new AlsaException("pcm recover failed after pcm_wait did ", err); 2415 //throw new AlsaException("uh oh", err); 2416 continue; 2417 } 2418 if(err == 0) 2419 continue; 2420 // err == 0 means timeout 2421 // err == 1 means ready 2422 2423 auto ready = snd_pcm_avail_update(handle); 2424 if(ready < 0) { 2425 //import std.stdio; writeln("recover"); 2426 2427 // actually it seems ok to just try again.. 2428 2429 // err = snd_pcm_recover(handle, err, 0); 2430 //if(err) 2431 //throw new AlsaException("avail", cast(int)ready); 2432 continue; 2433 } 2434 if(ready > BUFFER_SIZE_FRAMES) 2435 ready = BUFFER_SIZE_FRAMES; 2436 //import std.stdio; writeln("filling ", ready); 2437 fillData(buffer[0 .. ready * channels]); 2438 if(playing) { 2439 snd_pcm_sframes_t written; 2440 auto data = buffer[0 .. ready * channels]; 2441 2442 while(data.length) { 2443 written = snd_pcm_writei(handle, data.ptr, data.length / channels); 2444 if(written < 0) { 2445 //import std.stdio; writeln(written); 2446 written = snd_pcm_recover(handle, cast(int)written, 0); 2447 //import std.stdio; writeln("recover ", written); 2448 if (written < 0) throw new AlsaException("pcm write", cast(int)written); 2449 } 2450 data = data[written * channels .. $]; 2451 } 2452 } 2453 } 2454 } else version(WinMM) { 2455 2456 enum numBuffers = 4; // use a lot of buffers to get smooth output with Sleep, see below 2457 short[BUFFER_SIZE_SHORT][numBuffers] buffers; 2458 2459 WAVEHDR[numBuffers] headers; 2460 2461 foreach(i, ref header; headers) { 2462 // since this is wave out, it promises not to write... 2463 auto buffer = buffers[i][]; 2464 header.lpData = cast(char*) buffer.ptr; 2465 header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof; 2466 header.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP; 2467 header.dwLoops = 1; 2468 2469 if(auto err = waveOutPrepareHeader(handle, &header, header.sizeof)) 2470 throw new WinMMException("prepare header", err); 2471 2472 // prime it 2473 fillData(buffer[]); 2474 2475 // indicate that they are filled and good to go 2476 header.dwUser = 1; 2477 } 2478 2479 while(playing) { 2480 // and queue both to be played, if they are ready 2481 foreach(ref header; headers) 2482 if(header.dwUser) { 2483 if(auto err = waveOutWrite(handle, &header, header.sizeof)) 2484 throw new WinMMException("wave out write", err); 2485 header.dwUser = 0; 2486 } 2487 Sleep(1); 2488 // the system resolution may be lower than this sleep. To avoid gaps 2489 // in output, we use multiple buffers. Might introduce latency, not 2490 // sure how best to fix. I don't want to busy loop... 2491 } 2492 2493 // wait for the system to finish with our buffers 2494 bool anyInUse = true; 2495 2496 while(anyInUse) { 2497 anyInUse = false; 2498 foreach(header; headers) { 2499 if(!header.dwUser) { 2500 anyInUse = true; 2501 break; 2502 } 2503 } 2504 if(anyInUse) 2505 Sleep(1); 2506 } 2507 2508 foreach(ref header; headers) 2509 if(auto err = waveOutUnprepareHeader(handle, &header, header.sizeof)) 2510 throw new WinMMException("unprepare", err); 2511 } else static assert(0); 2512 2513 close(); 2514 } 2515 2516 /// Breaks the play loop 2517 void stop() { 2518 playing = false; 2519 } 2520 2521 /// 2522 void pause() { 2523 version(WinMM) 2524 waveOutPause(handle); 2525 else version(ALSA) 2526 snd_pcm_pause(handle, 1); 2527 } 2528 2529 /// 2530 void unpause() { 2531 version(WinMM) 2532 waveOutRestart(handle); 2533 else version(ALSA) 2534 snd_pcm_pause(handle, 0); 2535 2536 } 2537 2538 version(WinMM) { 2539 extern(Windows) 2540 static void mmCallback(HWAVEOUT handle, UINT msg, void* userData, WAVEHDR* header, DWORD_PTR param2) { 2541 AudioOutput* ao = cast(AudioOutput*) userData; 2542 if(msg == WOM_DONE) { 2543 // we want to bounce back and forth between two buffers 2544 // to keep the sound going all the time 2545 if(ao.playing) { 2546 ao.fillData((cast(short*) header.lpData)[0 .. header.dwBufferLength / short.sizeof]); 2547 } 2548 header.dwUser = 1; 2549 } 2550 } 2551 } 2552 2553 2554 /++ 2555 Re-opens the audio device that you have previously [close]d. 2556 2557 History: 2558 Added December 30, 2021 2559 +/ 2560 void open() { 2561 assert(handle is null); 2562 assert(!playing); 2563 version(ALSA) { 2564 handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK, SampleRate, channels, device); 2565 } else version(WinMM) { 2566 WAVEFORMATEX format; 2567 format.wFormatTag = WAVE_FORMAT_PCM; 2568 format.nChannels = cast(ushort) channels; 2569 format.nSamplesPerSec = SampleRate; 2570 format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample 2571 format.nBlockAlign = cast(short)(channels * 2); 2572 format.wBitsPerSample = 16; 2573 format.cbSize = 0; 2574 if(auto err = waveOutOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION)) 2575 throw new WinMMException("wave out open", err); 2576 } else static assert(0); 2577 } 2578 2579 /++ 2580 Closes the audio device. You MUST call [stop] before calling this. 2581 2582 History: 2583 Added December 30, 2021 2584 +/ 2585 void close() { 2586 if(!handle) 2587 return; 2588 assert(!playing); 2589 version(ALSA) { 2590 snd_pcm_close(handle); 2591 handle = null; 2592 } else version(WinMM) { 2593 waveOutClose(handle); 2594 handle = null; 2595 } else static assert(0); 2596 } 2597 2598 // FIXME: add async function hooks 2599 2600 ~this() { 2601 close(); 2602 } 2603 } 2604 2605 /++ 2606 For reading midi events from hardware, for example, an electronic piano keyboard 2607 attached to the computer. 2608 +/ 2609 struct MidiInput { 2610 // reading midi devices... 2611 version(ALSA) { 2612 snd_rawmidi_t* handle; 2613 } else version(WinMM) { 2614 HMIDIIN handle; 2615 } 2616 2617 @disable this(); 2618 @disable this(this); 2619 2620 /+ 2621 B0 40 7F # pedal on 2622 B0 40 00 # sustain pedal off 2623 +/ 2624 2625 /// Always pass card == 0. 2626 this(int card) { 2627 assert(card == 0); 2628 2629 this("default"); // "hw:4,0" 2630 } 2631 2632 /++ 2633 `device` is a device name. On Linux, it is the ALSA string. 2634 On Windows, it is currently ignored, so you should pass "default" 2635 or null so when it does get implemented your code won't break. 2636 2637 History: 2638 Added Nov 8, 2020. 2639 +/ 2640 this(string device) { 2641 version(ALSA) { 2642 if(auto err = snd_rawmidi_open(&handle, null, device.toStringz, 0)) 2643 throw new AlsaException("rawmidi open", err); 2644 } else version(WinMM) { 2645 if(auto err = midiInOpen(&handle, 0, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION)) 2646 throw new WinMMException("midi in open", err); 2647 } else static assert(0); 2648 } 2649 2650 private bool recording = false; 2651 2652 /// 2653 void stop() { 2654 recording = false; 2655 } 2656 2657 /++ 2658 Records raw midi input data from the device. 2659 2660 The timestamp is given in milliseconds since recording 2661 began (if you keep this program running for 23ish days 2662 it might overflow! so... don't do that.). The other bytes 2663 are the midi messages. 2664 2665 $(PITFALL Do not call any other multimedia functions from the callback!) 2666 +/ 2667 void record(void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg) { 2668 version(ALSA) { 2669 recording = true; 2670 ubyte[1024] data; 2671 import core.time; 2672 auto start = MonoTime.currTime; 2673 while(recording) { 2674 auto read = snd_rawmidi_read(handle, data.ptr, data.length); 2675 if(read < 0) 2676 throw new AlsaException("midi read", cast(int) read); 2677 2678 auto got = data[0 .. read]; 2679 while(got.length) { 2680 // FIXME some messages are fewer bytes.... 2681 dg(cast(uint) (MonoTime.currTime - start).total!"msecs", got[0], got[1], got[2]); 2682 got = got[3 .. $]; 2683 } 2684 } 2685 } else version(WinMM) { 2686 recording = true; 2687 this.dg = dg; 2688 scope(exit) 2689 this.dg = null; 2690 midiInStart(handle); 2691 scope(exit) 2692 midiInReset(handle); 2693 2694 while(recording) { 2695 Sleep(1); 2696 } 2697 } else static assert(0); 2698 } 2699 2700 version(WinMM) 2701 private void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg; 2702 2703 2704 version(WinMM) 2705 extern(Windows) 2706 static 2707 void mmCallback(HMIDIIN handle, UINT msg, DWORD_PTR user, DWORD_PTR param1, DWORD_PTR param2) { 2708 MidiInput* mi = cast(MidiInput*) user; 2709 if(msg == MIM_DATA) { 2710 mi.dg( 2711 cast(uint) param2, 2712 param1 & 0xff, 2713 (param1 >> 8) & 0xff, 2714 (param1 >> 16) & 0xff 2715 ); 2716 } 2717 } 2718 2719 ~this() { 2720 version(ALSA) { 2721 snd_rawmidi_close(handle); 2722 } else version(WinMM) { 2723 midiInClose(handle); 2724 } else static assert(0); 2725 } 2726 } 2727 2728 /// Gives MIDI output access. 2729 struct MidiOutput { 2730 version(ALSA) { 2731 snd_rawmidi_t* handle; 2732 } else version(WinMM) { 2733 HMIDIOUT handle; 2734 } 2735 2736 @disable this(); 2737 @disable this(this); 2738 2739 /// Always pass card == 0. 2740 this(int card) { 2741 assert(card == 0); 2742 2743 this("default"); // "hw:3,0" 2744 } 2745 2746 /++ 2747 `device` is a device name. On Linux, it is the ALSA string. 2748 On Windows, it is currently ignored, so you should pass "default" 2749 or null so when it does get implemented your code won't break. 2750 2751 If you pass the string "DUMMY", it will not actually open a device 2752 and simply be a do-nothing mock object; 2753 2754 History: 2755 Added Nov 8, 2020. 2756 2757 Support for the "DUMMY" device was added on January 2, 2022. 2758 +/ 2759 this(string device) { 2760 if(device == "DUMMY") 2761 return; 2762 2763 version(ALSA) { 2764 if(auto err = snd_rawmidi_open(null, &handle, device.toStringz, 0)) 2765 throw new AlsaException("rawmidi open", err); 2766 } else version(WinMM) { 2767 if(auto err = midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL)) 2768 throw new WinMMException("midi out open", err); 2769 } else static assert(0); 2770 } 2771 2772 void silenceAllNotes() { 2773 foreach(a; 0 .. 16) 2774 writeMidiMessage((0x0b << 4)|a /*MIDI_EVENT_CONTROLLER*/, 123, 0); 2775 } 2776 2777 /// Send a reset message, silencing all notes 2778 void reset() { 2779 if(!handle) return; 2780 2781 version(ALSA) { 2782 silenceAllNotes(); 2783 static immutable ubyte[1] resetCmd = [0xff]; 2784 writeRawMessageData(resetCmd[]); 2785 // and flush it immediately 2786 snd_rawmidi_drain(handle); 2787 } else version(WinMM) { 2788 if(auto error = midiOutReset(handle)) 2789 throw new WinMMException("midi reset", error); 2790 } else static assert(0); 2791 } 2792 2793 /// Writes a single low-level midi message 2794 /// Timing and sending sane data is your responsibility! 2795 void writeMidiMessage(int status, int param1, int param2) { 2796 if(!handle) return; 2797 version(ALSA) { 2798 ubyte[3] dataBuffer; 2799 2800 dataBuffer[0] = cast(ubyte) status; 2801 dataBuffer[1] = cast(ubyte) param1; 2802 dataBuffer[2] = cast(ubyte) param2; 2803 2804 auto msg = status >> 4; 2805 ubyte[] data; 2806 if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) 2807 data = dataBuffer[0 .. 2]; 2808 else 2809 data = dataBuffer[]; 2810 2811 writeRawMessageData(data); 2812 } else version(WinMM) { 2813 DWORD word = (param2 << 16) | (param1 << 8) | status; 2814 if(auto error = midiOutShortMsg(handle, word)) 2815 throw new WinMMException("midi out", error); 2816 } else static assert(0); 2817 2818 } 2819 2820 /// Writes a series of individual raw messages. 2821 /// Timing and sending sane data is your responsibility! 2822 /// The data should NOT include any timestamp bytes - each midi message should be 2 or 3 bytes. 2823 void writeRawMessageData(scope const(ubyte)[] data) { 2824 if(!handle) return; 2825 if(data.length == 0) 2826 return; 2827 version(ALSA) { 2828 ssize_t written; 2829 2830 while(data.length) { 2831 written = snd_rawmidi_write(handle, data.ptr, data.length); 2832 if(written < 0) 2833 throw new AlsaException("midi write", cast(int) written); 2834 data = data[cast(int) written .. $]; 2835 } 2836 } else version(WinMM) { 2837 while(data.length) { 2838 auto msg = data[0] >> 4; 2839 if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) { 2840 writeMidiMessage(data[0], data[1], 0); 2841 data = data[2 .. $]; 2842 } else { 2843 writeMidiMessage(data[0], data[1], data[2]); 2844 data = data[3 .. $]; 2845 } 2846 } 2847 } else static assert(0); 2848 } 2849 2850 ~this() { 2851 if(!handle) return; 2852 version(ALSA) { 2853 snd_rawmidi_close(handle); 2854 } else version(WinMM) { 2855 midiOutClose(handle); 2856 } else static assert(0); 2857 } 2858 } 2859 2860 2861 // FIXME: maybe add a PC speaker beep function for completeness 2862 2863 /// Interfaces with the default sound card. You should only have a single instance of this and it should 2864 /// be stack allocated, so its destructor cleans up after it. 2865 /// 2866 /// A mixer gives access to things like volume controls and mute buttons. It should also give a 2867 /// callback feature to alert you of when the settings are changed by another program. 2868 version(ALSA) // FIXME 2869 struct AudioMixer { 2870 // To port to a new OS: put the data in the right version blocks 2871 // then implement each function. Leave else static assert(0) at the 2872 // end of each version group in a function so it is easier to implement elsewhere later. 2873 // 2874 // If a function is only relevant on your OS, put the whole function in a version block 2875 // and give it an OS specific name of some sort. 2876 // 2877 // Feel free to do that btw without worrying about lowest common denominator: we want low level access when we want it. 2878 // 2879 // Put necessary bindings at the end of the file, or use an import if you like, but I prefer these files to be standalone. 2880 version(ALSA) { 2881 snd_mixer_t* handle; 2882 snd_mixer_selem_id_t* sid; 2883 snd_mixer_elem_t* selem; 2884 2885 c_long maxVolume, minVolume; // these are ok to use if you are writing ALSA specific code i guess 2886 2887 enum selemName = "Master"; 2888 } 2889 2890 @disable this(); 2891 @disable this(this); 2892 2893 /// Only cardId == 0 is supported 2894 this(int cardId) { 2895 assert(cardId == 0, "Pass 0 to use default sound card."); 2896 2897 this("default"); 2898 } 2899 2900 /++ 2901 `device` is a device name. On Linux, it is the ALSA string. 2902 On Windows, it is currently ignored, so you should pass "default" 2903 or null so when it does get implemented your code won't break. 2904 2905 History: 2906 Added Nov 8, 2020. 2907 +/ 2908 this(string device) { 2909 version(ALSA) { 2910 if(auto err = snd_mixer_open(&handle, 0)) 2911 throw new AlsaException("open sound", err); 2912 scope(failure) 2913 snd_mixer_close(handle); 2914 if(auto err = snd_mixer_attach(handle, device.toStringz)) 2915 throw new AlsaException("attach to sound card", err); 2916 if(auto err = snd_mixer_selem_register(handle, null, null)) 2917 throw new AlsaException("register mixer", err); 2918 if(auto err = snd_mixer_load(handle)) 2919 throw new AlsaException("load mixer", err); 2920 2921 if(auto err = snd_mixer_selem_id_malloc(&sid)) 2922 throw new AlsaException("master channel open", err); 2923 scope(failure) 2924 snd_mixer_selem_id_free(sid); 2925 snd_mixer_selem_id_set_index(sid, 0); 2926 snd_mixer_selem_id_set_name(sid, selemName); 2927 selem = snd_mixer_find_selem(handle, sid); 2928 if(selem is null) 2929 throw new AlsaException("find master element", 0); 2930 2931 if(auto err = snd_mixer_selem_get_playback_volume_range(selem, &minVolume, &maxVolume)) 2932 throw new AlsaException("get volume range", err); 2933 2934 version(with_eventloop) { 2935 import arsd.eventloop; 2936 addFileEventListeners(getAlsaFileDescriptors()[0], &eventListener, null, null); 2937 setAlsaElemCallback(&alsaCallback); 2938 } 2939 } else static assert(0); 2940 } 2941 2942 ~this() { 2943 version(ALSA) { 2944 version(with_eventloop) { 2945 import arsd.eventloop; 2946 removeFileEventListeners(getAlsaFileDescriptors()[0]); 2947 } 2948 snd_mixer_selem_id_free(sid); 2949 snd_mixer_close(handle); 2950 } else static assert(0); 2951 } 2952 2953 version(ALSA) 2954 version(with_eventloop) { 2955 static struct MixerEvent {} 2956 nothrow @nogc 2957 extern(C) static int alsaCallback(snd_mixer_elem_t*, uint) { 2958 import arsd.eventloop; 2959 try 2960 send(MixerEvent()); 2961 catch(Exception) 2962 return 1; 2963 2964 return 0; 2965 } 2966 2967 void eventListener(int fd) { 2968 handleAlsaEvents(); 2969 } 2970 } 2971 2972 /// Gets the master channel's mute state 2973 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 2974 @property bool muteMaster() { 2975 version(ALSA) { 2976 int result; 2977 if(auto err = snd_mixer_selem_get_playback_switch(selem, 0, &result)) 2978 throw new AlsaException("get mute state", err); 2979 return result == 0; 2980 } else static assert(0); 2981 } 2982 2983 /// Mutes or unmutes the master channel 2984 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 2985 @property void muteMaster(bool mute) { 2986 version(ALSA) { 2987 if(auto err = snd_mixer_selem_set_playback_switch_all(selem, mute ? 0 : 1)) 2988 throw new AlsaException("set mute state", err); 2989 } else static assert(0); 2990 } 2991 2992 /// returns a percentage, between 0 and 100 (inclusive) 2993 int getMasterVolume() { 2994 version(ALSA) { 2995 auto volume = getMasterVolumeExact(); 2996 return cast(int)(volume * 100 / (maxVolume - minVolume)); 2997 } else static assert(0); 2998 } 2999 3000 /// Gets the exact value returned from the operating system. The range may vary. 3001 int getMasterVolumeExact() { 3002 version(ALSA) { 3003 c_long volume; 3004 snd_mixer_selem_get_playback_volume(selem, 0, &volume); 3005 return cast(int)volume; 3006 } else static assert(0); 3007 } 3008 3009 /// sets a percentage on the volume, so it must be 0 <= volume <= 100 3010 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 3011 void setMasterVolume(int volume) { 3012 version(ALSA) { 3013 assert(volume >= 0 && volume <= 100); 3014 setMasterVolumeExact(cast(int)(volume * (maxVolume - minVolume) / 100)); 3015 } else static assert(0); 3016 } 3017 3018 /// Sets an exact volume. Must be in range of the OS provided min and max. 3019 void setMasterVolumeExact(int volume) { 3020 version(ALSA) { 3021 if(auto err = snd_mixer_selem_set_playback_volume_all(selem, volume)) 3022 throw new AlsaException("set volume", err); 3023 } else static assert(0); 3024 } 3025 3026 version(ALSA) { 3027 /// Gets the ALSA descriptors which you can watch for events 3028 /// on using regular select, poll, epoll, etc. 3029 int[] getAlsaFileDescriptors() { 3030 import core.sys.posix.poll; 3031 pollfd[32] descriptors = void; 3032 int got = snd_mixer_poll_descriptors(handle, descriptors.ptr, descriptors.length); 3033 int[] result; 3034 result.length = got; 3035 foreach(i, desc; descriptors[0 .. got]) 3036 result[i] = desc.fd; 3037 return result; 3038 } 3039 3040 /// When the FD is ready, call this to let ALSA do its thing. 3041 void handleAlsaEvents() { 3042 snd_mixer_handle_events(handle); 3043 } 3044 3045 /// Set a callback for the master volume change events. 3046 void setAlsaElemCallback(snd_mixer_elem_callback_t dg) { 3047 snd_mixer_elem_set_callback(selem, dg); 3048 } 3049 } 3050 } 3051 3052 // **************** 3053 // Midi helpers 3054 // **************** 3055 3056 // FIXME: code the .mid file format, read and write 3057 3058 enum MidiEvent { 3059 NoteOff = 0x08, 3060 NoteOn = 0x09, 3061 NoteAftertouch = 0x0a, 3062 Controller = 0x0b, 3063 ProgramChange = 0x0c, // one param 3064 ChannelAftertouch = 0x0d, // one param 3065 PitchBend = 0x0e, 3066 } 3067 3068 enum MidiNote : ubyte { 3069 middleC = 60, 3070 A = 69, // 440 Hz 3071 As = 70, 3072 B = 71, 3073 C = 72, 3074 Cs = 73, 3075 D = 74, 3076 Ds = 75, 3077 E = 76, 3078 F = 77, 3079 Fs = 78, 3080 G = 79, 3081 Gs = 80, 3082 } 3083 3084 /// Puts a note on at the beginning of the passed slice, advancing it by the amount of the message size. 3085 /// Returns the message slice. 3086 /// 3087 /// See: http://www.midi.org/techspecs/midimessages.php 3088 ubyte[] midiNoteOn(ref ubyte[] where, ubyte channel, byte note, byte velocity) { 3089 where[0] = (MidiEvent.NoteOn << 4) | (channel&0x0f); 3090 where[1] = note; 3091 where[2] = velocity; 3092 auto it = where[0 .. 3]; 3093 where = where[3 .. $]; 3094 return it; 3095 } 3096 3097 /// Note off. 3098 ubyte[] midiNoteOff(ref ubyte[] where, ubyte channel, byte note, byte velocity) { 3099 where[0] = (MidiEvent.NoteOff << 4) | (channel&0x0f); 3100 where[1] = note; 3101 where[2] = velocity; 3102 auto it = where[0 .. 3]; 3103 where = where[3 .. $]; 3104 return it; 3105 } 3106 3107 /// Aftertouch. 3108 ubyte[] midiNoteAftertouch(ref ubyte[] where, ubyte channel, byte note, byte pressure) { 3109 where[0] = (MidiEvent.NoteAftertouch << 4) | (channel&0x0f); 3110 where[1] = note; 3111 where[2] = pressure; 3112 auto it = where[0 .. 3]; 3113 where = where[3 .. $]; 3114 return it; 3115 } 3116 3117 /// Controller. 3118 ubyte[] midiNoteController(ref ubyte[] where, ubyte channel, byte controllerNumber, byte controllerValue) { 3119 where[0] = (MidiEvent.Controller << 4) | (channel&0x0f); 3120 where[1] = controllerNumber; 3121 where[2] = controllerValue; 3122 auto it = where[0 .. 3]; 3123 where = where[3 .. $]; 3124 return it; 3125 } 3126 3127 /// Program change. 3128 ubyte[] midiProgramChange(ref ubyte[] where, ubyte channel, byte program) { 3129 where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f); 3130 where[1] = program; 3131 auto it = where[0 .. 2]; 3132 where = where[2 .. $]; 3133 return it; 3134 } 3135 3136 /// Channel aftertouch. 3137 ubyte[] midiChannelAftertouch(ref ubyte[] where, ubyte channel, byte amount) { 3138 where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f); 3139 where[1] = amount; 3140 auto it = where[0 .. 2]; 3141 where = where[2 .. $]; 3142 return it; 3143 } 3144 3145 /// Pitch bend. FIXME doesn't work right 3146 ubyte[] midiNotePitchBend(ref ubyte[] where, ubyte channel, short change) { 3147 /* 3148 first byte is llllll 3149 second byte is mmmmmm 3150 3151 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. 3152 */ 3153 where[0] = (MidiEvent.PitchBend << 4) | (channel&0x0f); 3154 // FIXME 3155 where[1] = 0; 3156 where[2] = 0; 3157 auto it = where[0 .. 3]; 3158 where = where[3 .. $]; 3159 return it; 3160 } 3161 3162 3163 // **************** 3164 // Wav helpers 3165 // **************** 3166 3167 // FIXME: the .wav file format should be here, read and write (at least basics) 3168 // as well as some kind helpers to generate some sounds. 3169 3170 // **************** 3171 // OS specific helper stuff follows 3172 // **************** 3173 3174 private const(char)* toStringz(string s) { 3175 return s.ptr; // FIXME jic 3176 } 3177 3178 version(ALSA) 3179 // Opens the PCM device with default settings: stereo, 16 bit, 44.1 kHz, interleaved R/W. 3180 snd_pcm_t* openAlsaPcm(snd_pcm_stream_t direction, int SampleRate, int channels, string cardName = "default") { 3181 snd_pcm_t* handle; 3182 snd_pcm_hw_params_t* hwParams; 3183 3184 /* Open PCM and initialize hardware */ 3185 3186 // import arsd.core; 3187 // writeln("before"); 3188 if (auto err = snd_pcm_open(&handle, cardName.toStringz, direction, 0)) 3189 throw new AlsaException("open device", err); 3190 // writeln("after"); 3191 scope(failure) 3192 snd_pcm_close(handle); 3193 3194 3195 if (auto err = snd_pcm_hw_params_malloc(&hwParams)) 3196 throw new AlsaException("params malloc", err); 3197 scope(exit) 3198 snd_pcm_hw_params_free(hwParams); 3199 3200 if (auto err = snd_pcm_hw_params_any(handle, hwParams)) 3201 // can actually survive a failure here, we will just move forward 3202 {} // throw new AlsaException("params init", err); 3203 3204 if (auto err = snd_pcm_hw_params_set_access(handle, hwParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED)) 3205 throw new AlsaException("params access", err); 3206 3207 if (auto err = snd_pcm_hw_params_set_format(handle, hwParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE)) 3208 throw new AlsaException("params format", err); 3209 3210 uint rate = SampleRate; 3211 int dir = 0; 3212 if (auto err = snd_pcm_hw_params_set_rate_near(handle, hwParams, &rate, &dir)) 3213 throw new AlsaException("params rate", err); 3214 3215 assert(rate == SampleRate); // cheap me 3216 3217 if (auto err = snd_pcm_hw_params_set_channels(handle, hwParams, channels)) 3218 throw new AlsaException("params channels", err); 3219 3220 uint periods = 4; 3221 { 3222 auto err = snd_pcm_hw_params_set_periods_near(handle, hwParams, &periods, 0); 3223 if(err < 0) 3224 throw new AlsaException("periods", err); 3225 3226 // import std.stdio; writeln(periods); 3227 snd_pcm_uframes_t sz = (BUFFER_SIZE_FRAMES * periods); 3228 err = snd_pcm_hw_params_set_buffer_size_near(handle, hwParams, &sz); 3229 if(err < 0) 3230 throw new AlsaException("buffer size", err); 3231 } 3232 3233 if (auto err = snd_pcm_hw_params(handle, hwParams)) 3234 throw new AlsaException("params install", err); 3235 3236 /* Setting up the callbacks */ 3237 3238 snd_pcm_sw_params_t* swparams; 3239 if(auto err = snd_pcm_sw_params_malloc(&swparams)) 3240 throw new AlsaException("sw malloc", err); 3241 scope(exit) 3242 snd_pcm_sw_params_free(swparams); 3243 if(auto err = snd_pcm_sw_params_current(handle, swparams)) 3244 throw new AlsaException("sw set", err); 3245 if(auto err = snd_pcm_sw_params_set_avail_min(handle, swparams, BUFFER_SIZE_FRAMES)) 3246 throw new AlsaException("sw min", err); 3247 if(auto err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0)) 3248 throw new AlsaException("sw threshold", err); 3249 if(auto err = snd_pcm_sw_params(handle, swparams)) 3250 throw new AlsaException("sw params", err); 3251 3252 /* finish setup */ 3253 3254 // writeln("prepare"); 3255 if (auto err = snd_pcm_prepare(handle)) 3256 throw new AlsaException("prepare", err); 3257 // writeln("done"); 3258 3259 assert(handle !is null); 3260 return handle; 3261 } 3262 3263 version(ALSA) 3264 class AlsaException : AudioException { 3265 this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3266 auto msg = snd_strerror(error); 3267 import core.stdc.string; 3268 super(cast(string) (message ~ ": " ~ msg[0 .. strlen(msg)]), file, line, next); 3269 } 3270 } 3271 3272 version(WinMM) 3273 class WinMMException : AudioException { 3274 this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3275 // FIXME: format the error 3276 // midiOutGetErrorText, etc. 3277 super(message, file, line, next); 3278 } 3279 } 3280 3281 // **************** 3282 // Bindings follow 3283 // **************** 3284 3285 version(ALSA) { 3286 extern(C): 3287 @nogc nothrow: 3288 pragma(lib, "asound"); 3289 private import core.sys.posix.poll; 3290 3291 const(char)* snd_strerror(int); 3292 3293 // pcm 3294 enum snd_pcm_stream_t { 3295 SND_PCM_STREAM_PLAYBACK, 3296 SND_PCM_STREAM_CAPTURE 3297 } 3298 3299 enum snd_pcm_access_t { 3300 /** mmap access with simple interleaved channels */ 3301 SND_PCM_ACCESS_MMAP_INTERLEAVED = 0, 3302 /** mmap access with simple non interleaved channels */ 3303 SND_PCM_ACCESS_MMAP_NONINTERLEAVED, 3304 /** mmap access with complex placement */ 3305 SND_PCM_ACCESS_MMAP_COMPLEX, 3306 /** snd_pcm_readi/snd_pcm_writei access */ 3307 SND_PCM_ACCESS_RW_INTERLEAVED, 3308 /** snd_pcm_readn/snd_pcm_writen access */ 3309 SND_PCM_ACCESS_RW_NONINTERLEAVED, 3310 SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED 3311 } 3312 3313 enum snd_pcm_format { 3314 /** Unknown */ 3315 SND_PCM_FORMAT_UNKNOWN = -1, 3316 /** Signed 8 bit */ 3317 SND_PCM_FORMAT_S8 = 0, 3318 /** Unsigned 8 bit */ 3319 SND_PCM_FORMAT_U8, 3320 /** Signed 16 bit Little Endian */ 3321 SND_PCM_FORMAT_S16_LE, 3322 /** Signed 16 bit Big Endian */ 3323 SND_PCM_FORMAT_S16_BE, 3324 /** Unsigned 16 bit Little Endian */ 3325 SND_PCM_FORMAT_U16_LE, 3326 /** Unsigned 16 bit Big Endian */ 3327 SND_PCM_FORMAT_U16_BE, 3328 /** Signed 24 bit Little Endian using low three bytes in 32-bit word */ 3329 SND_PCM_FORMAT_S24_LE, 3330 /** Signed 24 bit Big Endian using low three bytes in 32-bit word */ 3331 SND_PCM_FORMAT_S24_BE, 3332 /** Unsigned 24 bit Little Endian using low three bytes in 32-bit word */ 3333 SND_PCM_FORMAT_U24_LE, 3334 /** Unsigned 24 bit Big Endian using low three bytes in 32-bit word */ 3335 SND_PCM_FORMAT_U24_BE, 3336 /** Signed 32 bit Little Endian */ 3337 SND_PCM_FORMAT_S32_LE, 3338 /** Signed 32 bit Big Endian */ 3339 SND_PCM_FORMAT_S32_BE, 3340 /** Unsigned 32 bit Little Endian */ 3341 SND_PCM_FORMAT_U32_LE, 3342 /** Unsigned 32 bit Big Endian */ 3343 SND_PCM_FORMAT_U32_BE, 3344 /** Float 32 bit Little Endian, Range -1.0 to 1.0 */ 3345 SND_PCM_FORMAT_FLOAT_LE, 3346 /** Float 32 bit Big Endian, Range -1.0 to 1.0 */ 3347 SND_PCM_FORMAT_FLOAT_BE, 3348 /** Float 64 bit Little Endian, Range -1.0 to 1.0 */ 3349 SND_PCM_FORMAT_FLOAT64_LE, 3350 /** Float 64 bit Big Endian, Range -1.0 to 1.0 */ 3351 SND_PCM_FORMAT_FLOAT64_BE, 3352 /** IEC-958 Little Endian */ 3353 SND_PCM_FORMAT_IEC958_SUBFRAME_LE, 3354 /** IEC-958 Big Endian */ 3355 SND_PCM_FORMAT_IEC958_SUBFRAME_BE, 3356 /** Mu-Law */ 3357 SND_PCM_FORMAT_MU_LAW, 3358 /** A-Law */ 3359 SND_PCM_FORMAT_A_LAW, 3360 /** Ima-ADPCM */ 3361 SND_PCM_FORMAT_IMA_ADPCM, 3362 /** MPEG */ 3363 SND_PCM_FORMAT_MPEG, 3364 /** GSM */ 3365 SND_PCM_FORMAT_GSM, 3366 /** Special */ 3367 SND_PCM_FORMAT_SPECIAL = 31, 3368 /** Signed 24bit Little Endian in 3bytes format */ 3369 SND_PCM_FORMAT_S24_3LE = 32, 3370 /** Signed 24bit Big Endian in 3bytes format */ 3371 SND_PCM_FORMAT_S24_3BE, 3372 /** Unsigned 24bit Little Endian in 3bytes format */ 3373 SND_PCM_FORMAT_U24_3LE, 3374 /** Unsigned 24bit Big Endian in 3bytes format */ 3375 SND_PCM_FORMAT_U24_3BE, 3376 /** Signed 20bit Little Endian in 3bytes format */ 3377 SND_PCM_FORMAT_S20_3LE, 3378 /** Signed 20bit Big Endian in 3bytes format */ 3379 SND_PCM_FORMAT_S20_3BE, 3380 /** Unsigned 20bit Little Endian in 3bytes format */ 3381 SND_PCM_FORMAT_U20_3LE, 3382 /** Unsigned 20bit Big Endian in 3bytes format */ 3383 SND_PCM_FORMAT_U20_3BE, 3384 /** Signed 18bit Little Endian in 3bytes format */ 3385 SND_PCM_FORMAT_S18_3LE, 3386 /** Signed 18bit Big Endian in 3bytes format */ 3387 SND_PCM_FORMAT_S18_3BE, 3388 /** Unsigned 18bit Little Endian in 3bytes format */ 3389 SND_PCM_FORMAT_U18_3LE, 3390 /** Unsigned 18bit Big Endian in 3bytes format */ 3391 SND_PCM_FORMAT_U18_3BE, 3392 /* G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes */ 3393 SND_PCM_FORMAT_G723_24, 3394 /* G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte */ 3395 SND_PCM_FORMAT_G723_24_1B, 3396 /* G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes */ 3397 SND_PCM_FORMAT_G723_40, 3398 /* G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte */ 3399 SND_PCM_FORMAT_G723_40_1B, 3400 /* Direct Stream Digital (DSD) in 1-byte samples (x8) */ 3401 SND_PCM_FORMAT_DSD_U8, 3402 /* Direct Stream Digital (DSD) in 2-byte samples (x16) */ 3403 SND_PCM_FORMAT_DSD_U16_LE, 3404 SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U16_LE, 3405 3406 // I snipped a bunch of endian-specific ones! 3407 } 3408 3409 struct snd_pcm_t {} 3410 struct snd_pcm_hw_params_t {} 3411 struct snd_pcm_sw_params_t {} 3412 3413 int snd_pcm_open(snd_pcm_t**, const char*, snd_pcm_stream_t, int); 3414 int snd_pcm_close(snd_pcm_t*); 3415 int snd_pcm_pause(snd_pcm_t*, int); 3416 int snd_pcm_prepare(snd_pcm_t*); 3417 int snd_pcm_hw_params(snd_pcm_t*, snd_pcm_hw_params_t*); 3418 int snd_pcm_hw_params_set_periods(snd_pcm_t*, snd_pcm_hw_params_t*, uint, int); 3419 int snd_pcm_hw_params_set_periods_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int); 3420 int snd_pcm_hw_params_set_buffer_size(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t); 3421 int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t*); 3422 int snd_pcm_hw_params_set_channels(snd_pcm_t*, snd_pcm_hw_params_t*, uint); 3423 int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t**); 3424 void snd_pcm_hw_params_free(snd_pcm_hw_params_t*); 3425 int snd_pcm_hw_params_any(snd_pcm_t*, snd_pcm_hw_params_t*); 3426 int snd_pcm_hw_params_set_access(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_access_t); 3427 int snd_pcm_hw_params_set_format(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_format); 3428 int snd_pcm_hw_params_set_rate_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int*); 3429 3430 int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t**); 3431 void snd_pcm_sw_params_free(snd_pcm_sw_params_t*); 3432 3433 int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params); 3434 int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params); 3435 int snd_pcm_sw_params_set_avail_min(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 3436 int snd_pcm_sw_params_set_start_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 3437 int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 3438 3439 alias snd_pcm_sframes_t = c_long; 3440 alias snd_pcm_uframes_t = c_ulong; 3441 snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t*, const void*, snd_pcm_uframes_t size); 3442 snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t*, void*, snd_pcm_uframes_t size); 3443 3444 int snd_pcm_wait(snd_pcm_t *pcm, int timeout); 3445 snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm); 3446 snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm); 3447 3448 int snd_pcm_recover (snd_pcm_t* pcm, int err, int silent); 3449 3450 alias snd_lib_error_handler_t = void function (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...); 3451 int snd_lib_error_set_handler (snd_lib_error_handler_t handler); 3452 3453 import core.stdc.stdarg; 3454 private void alsa_message_silencer (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...) @system {} 3455 //k8: ALSAlib loves to trash stderr; shut it up 3456 void silence_alsa_messages () { snd_lib_error_set_handler(&alsa_message_silencer); } 3457 extern(D) shared static this () { silence_alsa_messages(); } 3458 3459 // raw midi 3460 3461 static if(is(size_t == uint)) 3462 alias ssize_t = int; 3463 else 3464 alias ssize_t = long; 3465 3466 3467 struct snd_rawmidi_t {} 3468 int snd_rawmidi_open(snd_rawmidi_t**, snd_rawmidi_t**, const char*, int); 3469 int snd_rawmidi_close(snd_rawmidi_t*); 3470 int snd_rawmidi_drain(snd_rawmidi_t*); 3471 ssize_t snd_rawmidi_write(snd_rawmidi_t*, const void*, size_t); 3472 ssize_t snd_rawmidi_read(snd_rawmidi_t*, void*, size_t); 3473 3474 // mixer 3475 3476 struct snd_mixer_t {} 3477 struct snd_mixer_elem_t {} 3478 struct snd_mixer_selem_id_t {} 3479 3480 alias snd_mixer_elem_callback_t = int function(snd_mixer_elem_t*, uint); 3481 3482 int snd_mixer_open(snd_mixer_t**, int mode); 3483 int snd_mixer_close(snd_mixer_t*); 3484 int snd_mixer_attach(snd_mixer_t*, const char*); 3485 int snd_mixer_load(snd_mixer_t*); 3486 3487 // FIXME: those aren't actually void* 3488 int snd_mixer_selem_register(snd_mixer_t*, void*, void*); 3489 int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t**); 3490 void snd_mixer_selem_id_free(snd_mixer_selem_id_t*); 3491 void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t*, uint); 3492 void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t*, const char*); 3493 snd_mixer_elem_t* snd_mixer_find_selem(snd_mixer_t*, const scope snd_mixer_selem_id_t*); 3494 3495 // FIXME: the int should be an enum for channel identifier 3496 int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t*, int, c_long*); 3497 3498 int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t*, c_long*, c_long*); 3499 3500 int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t*, c_long); 3501 3502 void snd_mixer_elem_set_callback(snd_mixer_elem_t*, snd_mixer_elem_callback_t); 3503 int snd_mixer_poll_descriptors(snd_mixer_t*, pollfd*, uint space); 3504 3505 int snd_mixer_handle_events(snd_mixer_t*); 3506 3507 // FIXME: the first int should be an enum for channel identifier 3508 int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t*, int, int* value); 3509 int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t*, int); 3510 } 3511 3512 version(WinMM) { 3513 extern(Windows): 3514 @nogc nothrow: 3515 pragma(lib, "winmm"); 3516 import core.sys.windows.windows; 3517 3518 /* 3519 Windows functions include: 3520 http://msdn.microsoft.com/en-us/library/ms713762%28VS.85%29.aspx 3521 http://msdn.microsoft.com/en-us/library/ms713504%28v=vs.85%29.aspx 3522 http://msdn.microsoft.com/en-us/library/windows/desktop/dd798480%28v=vs.85%29.aspx# 3523 http://msdn.microsoft.com/en-US/subscriptions/ms712109.aspx 3524 */ 3525 3526 // pcm 3527 3528 // midi 3529 /+ 3530 alias HMIDIOUT = HANDLE; 3531 alias MMRESULT = UINT; 3532 3533 MMRESULT midiOutOpen(HMIDIOUT*, UINT, DWORD, DWORD, DWORD); 3534 MMRESULT midiOutClose(HMIDIOUT); 3535 MMRESULT midiOutReset(HMIDIOUT); 3536 MMRESULT midiOutShortMsg(HMIDIOUT, DWORD); 3537 3538 alias HWAVEOUT = HANDLE; 3539 3540 struct WAVEFORMATEX { 3541 WORD wFormatTag; 3542 WORD nChannels; 3543 DWORD nSamplesPerSec; 3544 DWORD nAvgBytesPerSec; 3545 WORD nBlockAlign; 3546 WORD wBitsPerSample; 3547 WORD cbSize; 3548 } 3549 3550 struct WAVEHDR { 3551 void* lpData; 3552 DWORD dwBufferLength; 3553 DWORD dwBytesRecorded; 3554 DWORD dwUser; 3555 DWORD dwFlags; 3556 DWORD dwLoops; 3557 WAVEHDR *lpNext; 3558 DWORD reserved; 3559 } 3560 3561 enum UINT WAVE_MAPPER= -1; 3562 3563 MMRESULT waveOutOpen(HWAVEOUT*, UINT_PTR, WAVEFORMATEX*, void* callback, void*, DWORD); 3564 MMRESULT waveOutClose(HWAVEOUT); 3565 MMRESULT waveOutPrepareHeader(HWAVEOUT, WAVEHDR*, UINT); 3566 MMRESULT waveOutUnprepareHeader(HWAVEOUT, WAVEHDR*, UINT); 3567 MMRESULT waveOutWrite(HWAVEOUT, WAVEHDR*, UINT); 3568 3569 MMRESULT waveOutGetVolume(HWAVEOUT, PDWORD); 3570 MMRESULT waveOutSetVolume(HWAVEOUT, DWORD); 3571 3572 enum CALLBACK_TYPEMASK = 0x70000; 3573 enum CALLBACK_NULL = 0; 3574 enum CALLBACK_WINDOW = 0x10000; 3575 enum CALLBACK_TASK = 0x20000; 3576 enum CALLBACK_FUNCTION = 0x30000; 3577 enum CALLBACK_THREAD = CALLBACK_TASK; 3578 enum CALLBACK_EVENT = 0x50000; 3579 3580 enum WAVE_FORMAT_PCM = 1; 3581 3582 enum WHDR_PREPARED = 2; 3583 enum WHDR_BEGINLOOP = 4; 3584 enum WHDR_ENDLOOP = 8; 3585 enum WHDR_INQUEUE = 16; 3586 3587 enum WinMMMessage : UINT { 3588 MM_JOY1MOVE = 0x3A0, 3589 MM_JOY2MOVE, 3590 MM_JOY1ZMOVE, 3591 MM_JOY2ZMOVE, // = 0x3A3 3592 MM_JOY1BUTTONDOWN = 0x3B5, 3593 MM_JOY2BUTTONDOWN, 3594 MM_JOY1BUTTONUP, 3595 MM_JOY2BUTTONUP, 3596 MM_MCINOTIFY, // = 0x3B9 3597 MM_WOM_OPEN = 0x3BB, 3598 MM_WOM_CLOSE, 3599 MM_WOM_DONE, 3600 MM_WIM_OPEN, 3601 MM_WIM_CLOSE, 3602 MM_WIM_DATA, 3603 MM_MIM_OPEN, 3604 MM_MIM_CLOSE, 3605 MM_MIM_DATA, 3606 MM_MIM_LONGDATA, 3607 MM_MIM_ERROR, 3608 MM_MIM_LONGERROR, 3609 MM_MOM_OPEN, 3610 MM_MOM_CLOSE, 3611 MM_MOM_DONE, // = 0x3C9 3612 MM_DRVM_OPEN = 0x3D0, 3613 MM_DRVM_CLOSE, 3614 MM_DRVM_DATA, 3615 MM_DRVM_ERROR, 3616 MM_STREAM_OPEN, 3617 MM_STREAM_CLOSE, 3618 MM_STREAM_DONE, 3619 MM_STREAM_ERROR, // = 0x3D7 3620 MM_MOM_POSITIONCB = 0x3CA, 3621 MM_MCISIGNAL, 3622 MM_MIM_MOREDATA, // = 0x3CC 3623 MM_MIXM_LINE_CHANGE = 0x3D0, 3624 MM_MIXM_CONTROL_CHANGE = 0x3D1 3625 } 3626 3627 3628 enum WOM_OPEN = WinMMMessage.MM_WOM_OPEN; 3629 enum WOM_CLOSE = WinMMMessage.MM_WOM_CLOSE; 3630 enum WOM_DONE = WinMMMessage.MM_WOM_DONE; 3631 enum WIM_OPEN = WinMMMessage.MM_WIM_OPEN; 3632 enum WIM_CLOSE = WinMMMessage.MM_WIM_CLOSE; 3633 enum WIM_DATA = WinMMMessage.MM_WIM_DATA; 3634 3635 3636 uint mciSendStringA(const scope char*,char*,uint,void*); 3637 3638 +/ 3639 } 3640 3641 version(with_resampler) { 3642 /* Copyright (C) 2007-2008 Jean-Marc Valin 3643 * Copyright (C) 2008 Thorvald Natvig 3644 * D port by Ketmar // Invisible Vector 3645 * 3646 * Arbitrary resampling code 3647 * 3648 * Redistribution and use in source and binary forms, with or without 3649 * modification, are permitted provided that the following conditions are 3650 * met: 3651 * 3652 * 1. Redistributions of source code must retain the above copyright notice, 3653 * this list of conditions and the following disclaimer. 3654 * 3655 * 2. Redistributions in binary form must reproduce the above copyright 3656 * notice, this list of conditions and the following disclaimer in the 3657 * documentation and/or other materials provided with the distribution. 3658 * 3659 * 3. The name of the author may not be used to endorse or promote products 3660 * derived from this software without specific prior written permission. 3661 * 3662 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 3663 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 3664 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 3665 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 3666 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 3667 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 3668 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 3669 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 3670 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 3671 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 3672 * POSSIBILITY OF SUCH DAMAGE. 3673 */ 3674 3675 /* A-a-a-and now... D port is covered by the following license! 3676 * 3677 * This program is free software: you can redistribute it and/or modify 3678 * it under the terms of the GNU General Public License as published by 3679 * the Free Software Foundation, either version 3 of the License, or 3680 * (at your option) any later version. 3681 * 3682 * This program is distributed in the hope that it will be useful, 3683 * but WITHOUT ANY WARRANTY; without even the implied warranty of 3684 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 3685 * GNU General Public License for more details. 3686 * 3687 * You should have received a copy of the GNU General Public License 3688 * along with this program. If not, see <http://www.gnu.org/licenses/>. 3689 */ 3690 //module iv.follin.resampler /*is aliced*/; 3691 //import iv.alice; 3692 3693 /* 3694 The design goals of this code are: 3695 - Very fast algorithm 3696 - SIMD-friendly algorithm 3697 - Low memory requirement 3698 - Good *perceptual* quality (and not best SNR) 3699 3700 Warning: This resampler is relatively new. Although I think I got rid of 3701 all the major bugs and I don't expect the API to change anymore, there 3702 may be something I've missed. So use with caution. 3703 3704 This algorithm is based on this original resampling algorithm: 3705 Smith, Julius O. Digital Audio Resampling Home Page 3706 Center for Computer Research in Music and Acoustics (CCRMA), 3707 Stanford University, 2007. 3708 Web published at http://www-ccrma.stanford.edu/~jos/resample/. 3709 3710 There is one main difference, though. This resampler uses cubic 3711 interpolation instead of linear interpolation in the above paper. This 3712 makes the table much smaller and makes it possible to compute that table 3713 on a per-stream basis. In turn, being able to tweak the table for each 3714 stream makes it possible to both reduce complexity on simple ratios 3715 (e.g. 2/3), and get rid of the rounding operations in the inner loop. 3716 The latter both reduces CPU time and makes the algorithm more SIMD-friendly. 3717 */ 3718 version = sincresample_use_full_table; 3719 version(X86) { 3720 version(sincresample_disable_sse) { 3721 } else { 3722 version(D_PIC) {} else version = sincresample_use_sse; 3723 } 3724 } 3725 3726 3727 // ////////////////////////////////////////////////////////////////////////// // 3728 public struct SpeexResampler { 3729 public: 3730 alias Quality = int; 3731 enum : uint { 3732 Fastest = 0, 3733 Voip = 3, 3734 Default = 4, 3735 Desktop = 5, 3736 Music = 8, 3737 Best = 10, 3738 } 3739 3740 enum Error { 3741 OK = 0, 3742 NoMemory, 3743 BadState, 3744 BadArgument, 3745 BadData, 3746 } 3747 3748 private: 3749 nothrow @trusted @nogc: 3750 alias ResamplerFn = int function (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen); 3751 3752 private: 3753 uint inRate; 3754 uint outRate; 3755 uint numRate; // from 3756 uint denRate; // to 3757 3758 Quality srQuality; 3759 uint chanCount; 3760 uint filterLen; 3761 uint memAllocSize; 3762 uint bufferSize; 3763 int intAdvance; 3764 int fracAdvance; 3765 float cutoff; 3766 uint oversample; 3767 bool started; 3768 3769 // these are per-channel 3770 int[64] lastSample; 3771 uint[64] sampFracNum; 3772 uint[64] magicSamples; 3773 3774 float* mem; 3775 uint realMemLen; // how much memory really allocated 3776 float* sincTable; 3777 uint sincTableLen; 3778 uint realSincTableLen; // how much memory really allocated 3779 ResamplerFn resampler; 3780 3781 int inStride; 3782 int outStride; 3783 3784 public: 3785 static string errorStr (int err) { 3786 switch (err) with (Error) { 3787 case OK: return "success"; 3788 case NoMemory: return "memory allocation failed"; 3789 case BadState: return "bad resampler state"; 3790 case BadArgument: return "invalid argument"; 3791 case BadData: return "bad data passed"; 3792 default: 3793 } 3794 return "unknown error"; 3795 } 3796 3797 public: 3798 @disable this (this); 3799 ~this () { deinit(); } 3800 3801 @property bool inited () const pure { return (resampler !is null); } 3802 3803 void deinit () { 3804 import core.stdc.stdlib : free; 3805 if (mem !is null) { free(mem); mem = null; } 3806 if (sincTable !is null) { free(sincTable); sincTable = null; } 3807 /* 3808 memAllocSize = realMemLen = 0; 3809 sincTableLen = realSincTableLen = 0; 3810 resampler = null; 3811 started = false; 3812 */ 3813 inRate = outRate = numRate = denRate = 0; 3814 srQuality = cast(Quality)666; 3815 chanCount = 0; 3816 filterLen = 0; 3817 memAllocSize = 0; 3818 bufferSize = 0; 3819 intAdvance = 0; 3820 fracAdvance = 0; 3821 cutoff = 0; 3822 oversample = 0; 3823 started = 0; 3824 3825 mem = null; 3826 realMemLen = 0; // how much memory really allocated 3827 sincTable = null; 3828 sincTableLen = 0; 3829 realSincTableLen = 0; // how much memory really allocated 3830 resampler = null; 3831 3832 inStride = outStride = 0; 3833 } 3834 3835 /** Create a new resampler with integer input and output rates. 3836 * 3837 * Params: 3838 * chans = Number of channels to be processed 3839 * inRate = Input sampling rate (integer number of Hz). 3840 * outRate = Output sampling rate (integer number of Hz). 3841 * aquality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality. 3842 * 3843 * Returns: 3844 * 0 or error code 3845 */ 3846 Error setup (uint chans, uint ainRate, uint aoutRate, Quality aquality/*, usize line=__LINE__*/) { 3847 //{ import core.stdc.stdio; printf("init: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); } 3848 import core.stdc.stdlib : malloc, free; 3849 3850 deinit(); 3851 if (aquality < 0) aquality = 0; 3852 if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best; 3853 if (chans < 1 || chans > 16) return Error.BadArgument; 3854 3855 started = false; 3856 inRate = 0; 3857 outRate = 0; 3858 numRate = 0; 3859 denRate = 0; 3860 srQuality = cast(Quality)666; // it's ok 3861 sincTableLen = 0; 3862 memAllocSize = 0; 3863 filterLen = 0; 3864 mem = null; 3865 resampler = null; 3866 3867 cutoff = 1.0f; 3868 chanCount = chans; 3869 inStride = 1; 3870 outStride = 1; 3871 3872 bufferSize = 160; 3873 3874 // per channel data 3875 lastSample[] = 0; 3876 magicSamples[] = 0; 3877 sampFracNum[] = 0; 3878 3879 setQuality(aquality); 3880 setRate(ainRate, aoutRate); 3881 3882 if (auto filterErr = updateFilter()) { deinit(); return filterErr; } 3883 skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros 3884 3885 return Error.OK; 3886 } 3887 3888 /** Set (change) the input/output sampling rates (integer value). 3889 * 3890 * Params: 3891 * ainRate = Input sampling rate (integer number of Hz). 3892 * aoutRate = Output sampling rate (integer number of Hz). 3893 * 3894 * Returns: 3895 * 0 or error code 3896 */ 3897 Error setRate (uint ainRate, uint aoutRate/*, usize line=__LINE__*/) { 3898 //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); } 3899 if (inRate == ainRate && outRate == aoutRate) return Error.OK; 3900 //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ratioNum, ratioDen, cast(uint)line); } 3901 3902 uint oldDen = denRate; 3903 inRate = ainRate; 3904 outRate = aoutRate; 3905 auto div = gcd(ainRate, aoutRate); 3906 numRate = ainRate/div; 3907 denRate = aoutRate/div; 3908 3909 if (oldDen > 0) { 3910 foreach (ref v; sampFracNum.ptr[0..chanCount]) { 3911 v = v*denRate/oldDen; 3912 // safety net 3913 if (v >= denRate) v = denRate-1; 3914 } 3915 } 3916 3917 return (inited ? updateFilter() : Error.OK); 3918 } 3919 3920 /** Get the current input/output sampling rates (integer value). 3921 * 3922 * Params: 3923 * ainRate = Input sampling rate (integer number of Hz) copied. 3924 * aoutRate = Output sampling rate (integer number of Hz) copied. 3925 */ 3926 void getRate (out uint ainRate, out uint aoutRate) { 3927 ainRate = inRate; 3928 aoutRate = outRate; 3929 } 3930 3931 @property uint getInRate () { return inRate; } 3932 @property uint getOutRate () { return outRate; } 3933 3934 @property uint getChans () { return chanCount; } 3935 3936 /** Get the current resampling ratio. This will be reduced to the least common denominator. 3937 * 3938 * Params: 3939 * ratioNum = Numerator of the sampling rate ratio copied 3940 * ratioDen = Denominator of the sampling rate ratio copied 3941 */ 3942 void getRatio (out uint ratioNum, out uint ratioDen) { 3943 ratioNum = numRate; 3944 ratioDen = denRate; 3945 } 3946 3947 /** Set (change) the conversion quality. 3948 * 3949 * Params: 3950 * quality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality. 3951 * 3952 * Returns: 3953 * 0 or error code 3954 */ 3955 Error setQuality (Quality aquality) { 3956 if (aquality < 0) aquality = 0; 3957 if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best; 3958 if (srQuality == aquality) return Error.OK; 3959 srQuality = aquality; 3960 return (inited ? updateFilter() : Error.OK); 3961 } 3962 3963 /** Get the conversion quality. 3964 * 3965 * Returns: 3966 * Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality. 3967 */ 3968 int getQuality () { return srQuality; } 3969 3970 /** Get the latency introduced by the resampler measured in input samples. 3971 * 3972 * Returns: 3973 * Input latency; 3974 */ 3975 int inputLatency () { return filterLen/2; } 3976 3977 /** Get the latency introduced by the resampler measured in output samples. 3978 * 3979 * Returns: 3980 * Output latency. 3981 */ 3982 int outputLatency () { return ((filterLen/2)*denRate+(numRate>>1))/numRate; } 3983 3984 /* Make sure that the first samples to go out of the resamplers don't have 3985 * leading zeros. This is only useful before starting to use a newly created 3986 * resampler. It is recommended to use that when resampling an audio file, as 3987 * it will generate a file with the same length. For real-time processing, 3988 * it is probably easier not to use this call (so that the output duration 3989 * is the same for the first frame). 3990 * 3991 * Setup/reset sequence will automatically call this, so it is private. 3992 */ 3993 private void skipZeros () { foreach (immutable i; 0..chanCount) lastSample.ptr[i] = filterLen/2; } 3994 3995 static struct Data { 3996 const(float)[] dataIn; 3997 float[] dataOut; 3998 uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count) 3999 uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count) 4000 } 4001 4002 /** Resample (an interleaved) float array. The input and output buffers must *not* overlap. 4003 * `data.dataIn` can be empty, but `data.dataOut` can't. 4004 * Function will return number of consumed samples (*not* *frames*!) in `data.inputSamplesUsed`, 4005 * and number of produced samples in `data.outputSamplesUsed`. 4006 * You should provide enough samples for all channels, and all channels will be processed. 4007 * 4008 * Params: 4009 * data = input and output buffers, number of frames consumed and produced 4010 * 4011 * Returns: 4012 * 0 or error code 4013 */ 4014 Error process(string mode="interleaved") (ref Data data) { 4015 static assert(mode == "interleaved" || mode == "sequential"); 4016 4017 data.inputSamplesUsed = data.outputSamplesUsed = 0; 4018 if (!inited) return Error.BadState; 4019 4020 if (data.dataIn.length%chanCount || data.dataOut.length < 1 || data.dataOut.length%chanCount) return Error.BadData; 4021 if (data.dataIn.length > uint.max/4 || data.dataOut.length > uint.max/4) return Error.BadData; 4022 4023 static if (mode == "interleaved") { 4024 inStride = outStride = chanCount; 4025 } else { 4026 inStride = outStride = 1; 4027 } 4028 uint iofs = 0, oofs = 0; 4029 immutable uint idclen = cast(uint)(data.dataIn.length/chanCount); 4030 immutable uint odclen = cast(uint)(data.dataOut.length/chanCount); 4031 foreach (immutable i; 0..chanCount) { 4032 data.inputSamplesUsed = idclen; 4033 data.outputSamplesUsed = odclen; 4034 if (data.dataIn.length) { 4035 processOneChannel(i, data.dataIn.ptr+iofs, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed); 4036 } else { 4037 processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed); 4038 } 4039 static if (mode == "interleaved") { 4040 ++iofs; 4041 ++oofs; 4042 } else { 4043 iofs += idclen; 4044 oofs += odclen; 4045 } 4046 } 4047 data.inputSamplesUsed *= chanCount; 4048 data.outputSamplesUsed *= chanCount; 4049 return Error.OK; 4050 } 4051 4052 4053 //HACK for libswresample 4054 // return -1 or number of outframes 4055 int swrconvert (float** outbuf, int outframes, const(float)**inbuf, int inframes) { 4056 if (!inited || outframes < 1 || inframes < 0) return -1; 4057 inStride = outStride = 1; 4058 Data data; 4059 foreach (immutable i; 0..chanCount) { 4060 data.dataIn = (inframes ? inbuf[i][0..inframes] : null); 4061 data.dataOut = (outframes ? outbuf[i][0..outframes] : null); 4062 data.inputSamplesUsed = inframes; 4063 data.outputSamplesUsed = outframes; 4064 if (inframes > 0) { 4065 processOneChannel(i, data.dataIn.ptr, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed); 4066 } else { 4067 processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed); 4068 } 4069 } 4070 return data.outputSamplesUsed; 4071 } 4072 4073 /// Reset a resampler so a new (unrelated) stream can be processed. 4074 void reset () { 4075 lastSample[] = 0; 4076 magicSamples[] = 0; 4077 sampFracNum[] = 0; 4078 //foreach (immutable i; 0..chanCount*(filterLen-1)) mem[i] = 0; 4079 if (mem !is null) mem[0..chanCount*(filterLen-1)] = 0; 4080 skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros 4081 } 4082 4083 private: 4084 Error processOneChannel (uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen) { 4085 uint ilen = *indataLen; 4086 uint olen = *outdataLen; 4087 float* x = mem+chanIdx*memAllocSize; 4088 immutable int filterOfs = filterLen-1; 4089 immutable uint xlen = memAllocSize-filterOfs; 4090 immutable int istride = inStride; 4091 if (magicSamples.ptr[chanIdx]) olen -= magic(chanIdx, &outdata, olen); 4092 if (!magicSamples.ptr[chanIdx]) { 4093 while (ilen && olen) { 4094 uint ichunk = (ilen > xlen ? xlen : ilen); 4095 uint ochunk = olen; 4096 if (indata !is null) { 4097 //foreach (immutable j; 0..ichunk) x[j+filterOfs] = indata[j*istride]; 4098 if (istride == 1) { 4099 x[filterOfs..filterOfs+ichunk] = indata[0..ichunk]; 4100 } else { 4101 auto sp = indata; 4102 auto dp = x+filterOfs; 4103 foreach (immutable j; 0..ichunk) { *dp++ = *sp; sp += istride; } 4104 } 4105 } else { 4106 //foreach (immutable j; 0..ichunk) x[j+filterOfs] = 0; 4107 x[filterOfs..filterOfs+ichunk] = 0; 4108 } 4109 processNative(chanIdx, &ichunk, outdata, &ochunk); 4110 ilen -= ichunk; 4111 olen -= ochunk; 4112 outdata += ochunk*outStride; 4113 if (indata !is null) indata += ichunk*istride; 4114 } 4115 } 4116 *indataLen -= ilen; 4117 *outdataLen -= olen; 4118 return Error.OK; 4119 } 4120 4121 Error processNative (uint chanIdx, uint* indataLen, float* outdata, uint* outdataLen) { 4122 immutable N = filterLen; 4123 int outSample = 0; 4124 float* x = mem+chanIdx*memAllocSize; 4125 uint ilen; 4126 started = true; 4127 // call the right resampler through the function ptr 4128 outSample = resampler(this, chanIdx, x, indataLen, outdata, outdataLen); 4129 if (lastSample.ptr[chanIdx] < cast(int)*indataLen) *indataLen = lastSample.ptr[chanIdx]; 4130 *outdataLen = outSample; 4131 lastSample.ptr[chanIdx] -= *indataLen; 4132 ilen = *indataLen; 4133 foreach (immutable j; 0..N-1) x[j] = x[j+ilen]; 4134 return Error.OK; 4135 } 4136 4137 int magic (uint chanIdx, float **outdata, uint outdataLen) { 4138 uint tempInLen = magicSamples.ptr[chanIdx]; 4139 float* x = mem+chanIdx*memAllocSize; 4140 processNative(chanIdx, &tempInLen, *outdata, &outdataLen); 4141 magicSamples.ptr[chanIdx] -= tempInLen; 4142 // if we couldn't process all "magic" input samples, save the rest for next time 4143 if (magicSamples.ptr[chanIdx]) { 4144 immutable N = filterLen; 4145 foreach (immutable i; 0..magicSamples.ptr[chanIdx]) x[N-1+i] = x[N-1+i+tempInLen]; 4146 } 4147 *outdata += outdataLen*outStride; 4148 return outdataLen; 4149 } 4150 4151 Error updateFilter () { 4152 uint oldFilterLen = filterLen; 4153 uint oldAllocSize = memAllocSize; 4154 bool useDirect; 4155 uint minSincTableLen; 4156 uint minAllocSize; 4157 4158 intAdvance = numRate/denRate; 4159 fracAdvance = numRate%denRate; 4160 oversample = qualityMap.ptr[srQuality].oversample; 4161 filterLen = qualityMap.ptr[srQuality].baseLength; 4162 4163 if (numRate > denRate) { 4164 // down-sampling 4165 cutoff = qualityMap.ptr[srQuality].downsampleBandwidth*denRate/numRate; 4166 // FIXME: divide the numerator and denominator by a certain amount if they're too large 4167 filterLen = filterLen*numRate/denRate; 4168 // round up to make sure we have a multiple of 8 for SSE 4169 filterLen = ((filterLen-1)&(~0x7))+8; 4170 if (2*denRate < numRate) oversample >>= 1; 4171 if (4*denRate < numRate) oversample >>= 1; 4172 if (8*denRate < numRate) oversample >>= 1; 4173 if (16*denRate < numRate) oversample >>= 1; 4174 if (oversample < 1) oversample = 1; 4175 } else { 4176 // up-sampling 4177 cutoff = qualityMap.ptr[srQuality].upsampleBandwidth; 4178 } 4179 4180 // choose the resampling type that requires the least amount of memory 4181 version(sincresample_use_full_table) { 4182 useDirect = true; 4183 if (int.max/float.sizeof/denRate < filterLen) goto fail; 4184 } else { 4185 useDirect = (filterLen*denRate <= filterLen*oversample+8 && int.max/float.sizeof/denRate >= filterLen); 4186 } 4187 4188 if (useDirect) { 4189 minSincTableLen = filterLen*denRate; 4190 } else { 4191 if ((int.max/float.sizeof-8)/oversample < filterLen) goto fail; 4192 minSincTableLen = filterLen*oversample+8; 4193 } 4194 4195 if (sincTableLen < minSincTableLen) { 4196 import core.stdc.stdlib : realloc; 4197 auto nslen = cast(uint)(minSincTableLen*float.sizeof); 4198 if (nslen > realSincTableLen) { 4199 if (nslen < 512*1024) nslen = 512*1024; // inc to 3 mb? 4200 auto x = cast(float*)realloc(sincTable, nslen); 4201 if (!x) goto fail; 4202 sincTable = x; 4203 realSincTableLen = nslen; 4204 } 4205 sincTableLen = minSincTableLen; 4206 } 4207 4208 if (useDirect) { 4209 foreach (int i; 0..denRate) { 4210 foreach (int j; 0..filterLen) { 4211 sincTable[i*filterLen+j] = sinc(cutoff, ((j-cast(int)filterLen/2+1)-(cast(float)i)/denRate), filterLen, qualityMap.ptr[srQuality].windowFunc); 4212 } 4213 } 4214 if (srQuality > 8) { 4215 resampler = &resamplerBasicDirect!double; 4216 } else { 4217 resampler = &resamplerBasicDirect!float; 4218 } 4219 } else { 4220 foreach (immutable int i; -4..cast(int)(oversample*filterLen+4)) { 4221 sincTable[i+4] = sinc(cutoff, (i/cast(float)oversample-filterLen/2), filterLen, qualityMap.ptr[srQuality].windowFunc); 4222 } 4223 if (srQuality > 8) { 4224 resampler = &resamplerBasicInterpolate!double; 4225 } else { 4226 resampler = &resamplerBasicInterpolate!float; 4227 } 4228 } 4229 4230 /* Here's the place where we update the filter memory to take into account 4231 the change in filter length. It's probably the messiest part of the code 4232 due to handling of lots of corner cases. */ 4233 4234 // adding bufferSize to filterLen won't overflow here because filterLen could be multiplied by float.sizeof above 4235 minAllocSize = filterLen-1+bufferSize; 4236 if (minAllocSize > memAllocSize) { 4237 import core.stdc.stdlib : realloc; 4238 if (int.max/float.sizeof/chanCount < minAllocSize) goto fail; 4239 auto nslen = cast(uint)(chanCount*minAllocSize*mem[0].sizeof); 4240 if (nslen > realMemLen) { 4241 if (nslen < 16384) nslen = 16384; 4242 auto x = cast(float*)realloc(mem, nslen); 4243 if (x is null) goto fail; 4244 mem = x; 4245 realMemLen = nslen; 4246 } 4247 memAllocSize = minAllocSize; 4248 } 4249 if (!started) { 4250 //foreach (i=0;i<chanCount*memAllocSize;i++) mem[i] = 0; 4251 mem[0..chanCount*memAllocSize] = 0; 4252 } else if (filterLen > oldFilterLen) { 4253 // increase the filter length 4254 foreach_reverse (uint i; 0..chanCount) { 4255 uint j; 4256 uint olen = oldFilterLen; 4257 { 4258 // try and remove the magic samples as if nothing had happened 4259 //FIXME: this is wrong but for now we need it to avoid going over the array bounds 4260 olen = oldFilterLen+2*magicSamples.ptr[i]; 4261 for (j = oldFilterLen-1+magicSamples.ptr[i]; j--; ) mem[i*memAllocSize+j+magicSamples.ptr[i]] = mem[i*oldAllocSize+j]; 4262 //for (j = 0; j < magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = 0; 4263 mem[i*memAllocSize..i*memAllocSize+magicSamples.ptr[i]] = 0; 4264 magicSamples.ptr[i] = 0; 4265 } 4266 if (filterLen > olen) { 4267 // if the new filter length is still bigger than the "augmented" length 4268 // copy data going backward 4269 for (j = 0; j < olen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = mem[i*memAllocSize+(olen-2-j)]; 4270 // then put zeros for lack of anything better 4271 for (; j < filterLen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = 0; 4272 // adjust lastSample 4273 lastSample.ptr[i] += (filterLen-olen)/2; 4274 } else { 4275 // put back some of the magic! 4276 magicSamples.ptr[i] = (olen-filterLen)/2; 4277 for (j = 0; j < filterLen-1+magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]]; 4278 } 4279 } 4280 } else if (filterLen < oldFilterLen) { 4281 // reduce filter length, this a bit tricky 4282 // we need to store some of the memory as "magic" samples so they can be used directly as input the next time(s) 4283 foreach (immutable i; 0..chanCount) { 4284 uint j; 4285 uint oldMagic = magicSamples.ptr[i]; 4286 magicSamples.ptr[i] = (oldFilterLen-filterLen)/2; 4287 // we must copy some of the memory that's no longer used 4288 // copy data going backward 4289 for (j = 0; j < filterLen-1+magicSamples.ptr[i]+oldMagic; ++j) { 4290 mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]]; 4291 } 4292 magicSamples.ptr[i] += oldMagic; 4293 } 4294 } 4295 return Error.OK; 4296 4297 fail: 4298 resampler = null; 4299 /* mem may still contain consumed input samples for the filter. 4300 Restore filterLen so that filterLen-1 still points to the position after 4301 the last of these samples. */ 4302 filterLen = oldFilterLen; 4303 return Error.NoMemory; 4304 } 4305 } 4306 4307 4308 // ////////////////////////////////////////////////////////////////////////// // 4309 static immutable double[68] kaiser12Table = [ 4310 0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076, 4311 0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014, 4312 0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601, 4313 0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014, 4314 0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490, 4315 0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546, 4316 0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178, 4317 0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947, 4318 0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058, 4319 0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438, 4320 0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734, 4321 0.00001000, 0.00000000]; 4322 4323 static immutable double[36] kaiser10Table = [ 4324 0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446, 4325 0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347, 4326 0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962, 4327 0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451, 4328 0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739, 4329 0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000]; 4330 4331 static immutable double[36] kaiser8Table = [ 4332 0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200, 4333 0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126, 4334 0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272, 4335 0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758, 4336 0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490, 4337 0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000]; 4338 4339 static immutable double[36] kaiser6Table = [ 4340 0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003, 4341 0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565, 4342 0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561, 4343 0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058, 4344 0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600, 4345 0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000]; 4346 4347 struct FuncDef { 4348 immutable(double)* table; 4349 int oversample; 4350 } 4351 4352 static immutable FuncDef Kaiser12 = FuncDef(kaiser12Table.ptr, 64); 4353 static immutable FuncDef Kaiser10 = FuncDef(kaiser10Table.ptr, 32); 4354 static immutable FuncDef Kaiser8 = FuncDef(kaiser8Table.ptr, 32); 4355 static immutable FuncDef Kaiser6 = FuncDef(kaiser6Table.ptr, 32); 4356 4357 4358 struct QualityMapping { 4359 int baseLength; 4360 int oversample; 4361 float downsampleBandwidth; 4362 float upsampleBandwidth; 4363 immutable FuncDef* windowFunc; 4364 } 4365 4366 4367 /* This table maps conversion quality to internal parameters. There are two 4368 reasons that explain why the up-sampling bandwidth is larger than the 4369 down-sampling bandwidth: 4370 1) When up-sampling, we can assume that the spectrum is already attenuated 4371 close to the Nyquist rate (from an A/D or a previous resampling filter) 4372 2) Any aliasing that occurs very close to the Nyquist rate will be masked 4373 by the sinusoids/noise just below the Nyquist rate (guaranteed only for 4374 up-sampling). 4375 */ 4376 static immutable QualityMapping[11] qualityMap = [ 4377 QualityMapping( 8, 4, 0.830f, 0.860f, &Kaiser6 ), /* Q0 */ 4378 QualityMapping( 16, 4, 0.850f, 0.880f, &Kaiser6 ), /* Q1 */ 4379 QualityMapping( 32, 4, 0.882f, 0.910f, &Kaiser6 ), /* Q2 */ /* 82.3% cutoff ( ~60 dB stop) 6 */ 4380 QualityMapping( 48, 8, 0.895f, 0.917f, &Kaiser8 ), /* Q3 */ /* 84.9% cutoff ( ~80 dB stop) 8 */ 4381 QualityMapping( 64, 8, 0.921f, 0.940f, &Kaiser8 ), /* Q4 */ /* 88.7% cutoff ( ~80 dB stop) 8 */ 4382 QualityMapping( 80, 16, 0.922f, 0.940f, &Kaiser10), /* Q5 */ /* 89.1% cutoff (~100 dB stop) 10 */ 4383 QualityMapping( 96, 16, 0.940f, 0.945f, &Kaiser10), /* Q6 */ /* 91.5% cutoff (~100 dB stop) 10 */ 4384 QualityMapping(128, 16, 0.950f, 0.950f, &Kaiser10), /* Q7 */ /* 93.1% cutoff (~100 dB stop) 10 */ 4385 QualityMapping(160, 16, 0.960f, 0.960f, &Kaiser10), /* Q8 */ /* 94.5% cutoff (~100 dB stop) 10 */ 4386 QualityMapping(192, 32, 0.968f, 0.968f, &Kaiser12), /* Q9 */ /* 95.5% cutoff (~100 dB stop) 10 */ 4387 QualityMapping(256, 32, 0.975f, 0.975f, &Kaiser12), /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */ 4388 ]; 4389 4390 4391 nothrow @trusted @nogc: 4392 /*8, 24, 40, 56, 80, 104, 128, 160, 200, 256, 320*/ 4393 double computeFunc (float x, immutable FuncDef* func) { 4394 version(Posix) import core.stdc.math : lrintf; 4395 import core.stdc.math; 4396 //double[4] interp; 4397 float y = x*func.oversample; 4398 version(Posix) { 4399 int ind = cast(int)lrintf(floor(y)); 4400 } else { 4401 int ind = cast(int)(floor(y)); 4402 } 4403 float frac = (y-ind); 4404 immutable f2 = frac*frac; 4405 immutable f3 = f2*frac; 4406 double interp3 = -0.1666666667*frac+0.1666666667*(f3); 4407 double interp2 = frac+0.5*(f2)-0.5*(f3); 4408 //double interp2 = 1.0f-0.5f*frac-f2+0.5f*f3; 4409 double interp0 = -0.3333333333*frac+0.5*(f2)-0.1666666667*(f3); 4410 // just to make sure we don't have rounding problems 4411 double interp1 = 1.0f-interp3-interp2-interp0; 4412 //sum = frac*accum[1]+(1-frac)*accum[2]; 4413 return interp0*func.table[ind]+interp1*func.table[ind+1]+interp2*func.table[ind+2]+interp3*func.table[ind+3]; 4414 } 4415 4416 4417 // the slow way of computing a sinc for the table; should improve that some day 4418 float sinc (float cutoff, float x, int N, immutable FuncDef *windowFunc) { 4419 version(LittleEndian) { 4420 align(1) union temp_float { align(1): float f; uint n; } 4421 } else { 4422 static T fabs(T) (T n) pure { static if (__VERSION__ > 2067) pragma(inline, true); return (n < 0 ? -n : n); } 4423 } 4424 import core.stdc.math; 4425 version(LittleEndian) { 4426 temp_float txx = void; 4427 txx.f = x; 4428 txx.n &= 0x7fff_ffff; // abs 4429 if (txx.f < 1.0e-6f) return cutoff; 4430 if (txx.f > 0.5f*N) return 0; 4431 } else { 4432 if (fabs(x) < 1.0e-6f) return cutoff; 4433 if (fabs(x) > 0.5f*N) return 0; 4434 } 4435 //FIXME: can it really be any slower than this? 4436 immutable float xx = x*cutoff; 4437 immutable pixx = PI*xx; 4438 version(LittleEndian) { 4439 return cutoff*sin(pixx)/pixx*computeFunc(2.0*txx.f/N, windowFunc); 4440 } else { 4441 return cutoff*sin(pixx)/pixx*computeFunc(fabs(2.0*x/N), windowFunc); 4442 } 4443 } 4444 4445 4446 void cubicCoef (in float frac, float* interp) { 4447 immutable f2 = frac*frac; 4448 immutable f3 = f2*frac; 4449 // compute interpolation coefficients; i'm not sure whether this corresponds to cubic interpolation but I know it's MMSE-optimal on a sinc 4450 interp[0] = -0.16667f*frac+0.16667f*f3; 4451 interp[1] = frac+0.5f*f2-0.5f*f3; 4452 //interp[2] = 1.0f-0.5f*frac-f2+0.5f*f3; 4453 interp[3] = -0.33333f*frac+0.5f*f2-0.16667f*f3; 4454 // just to make sure we don't have rounding problems 4455 interp[2] = 1.0-interp[0]-interp[1]-interp[3]; 4456 } 4457 4458 4459 // ////////////////////////////////////////////////////////////////////////// // 4460 int resamplerBasicDirect(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen) 4461 if (is(T == float) || is(T == double)) 4462 { 4463 auto N = st.filterLen; 4464 static if (is(T == double)) assert(N%4 == 0); 4465 int outSample = 0; 4466 int lastSample = st.lastSample.ptr[chanIdx]; 4467 uint sampFracNum = st.sampFracNum.ptr[chanIdx]; 4468 const(float)* sincTable = st.sincTable; 4469 immutable outStride = st.outStride; 4470 immutable intAdvance = st.intAdvance; 4471 immutable fracAdvance = st.fracAdvance; 4472 immutable denRate = st.denRate; 4473 T sum = void; 4474 while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) { 4475 const(float)* sinct = &sincTable[sampFracNum*N]; 4476 const(float)* iptr = &indata[lastSample]; 4477 static if (is(T == float)) { 4478 // at least 2x speedup with SSE here (but for unrolled loop) 4479 if (N%4 == 0) { 4480 version(sincresample_use_sse) { 4481 //align(64) __gshared float[4] zero = 0; 4482 align(64) __gshared float[4+128] zeroesBuf = 0; // dmd cannot into such aligns, alas 4483 __gshared uint zeroesptr = 0; 4484 if (zeroesptr == 0) { 4485 zeroesptr = cast(uint)zeroesBuf.ptr; 4486 if (zeroesptr&0x3f) zeroesptr = (zeroesptr|0x3f)+1; 4487 } 4488 //assert((zeroesptr&0x3f) == 0, "wtf?!"); 4489 asm nothrow @safe @nogc { 4490 mov ECX,[N]; 4491 shr ECX,2; 4492 mov EAX,[zeroesptr]; 4493 movaps XMM0,[EAX]; 4494 mov EAX,[sinct]; 4495 mov EBX,[iptr]; 4496 mov EDX,16; 4497 align 8; 4498 rbdseeloop: 4499 movups XMM1,[EAX]; 4500 movups XMM2,[EBX]; 4501 mulps XMM1,XMM2; 4502 addps XMM0,XMM1; 4503 add EAX,EDX; 4504 add EBX,EDX; 4505 dec ECX; 4506 jnz rbdseeloop; 4507 // store result in sum 4508 movhlps XMM1,XMM0; // now low part of XMM1 contains high part of XMM0 4509 addps XMM0,XMM1; // low part of XMM0 is ok 4510 movaps XMM1,XMM0; 4511 shufps XMM1,XMM0,0b_01_01_01_01; // 2nd float of XMM0 goes to the 1st float of XMM1 4512 addss XMM0,XMM1; 4513 movss [sum],XMM0; 4514 } 4515 /* 4516 float sum1 = 0; 4517 foreach (immutable j; 0..N) sum1 += sinct[j]*iptr[j]; 4518 import std.math; 4519 if (fabs(sum-sum1) > 0.000001f) { 4520 import core.stdc.stdio; 4521 printf("sum=%f; sum1=%f\n", sum, sum1); 4522 assert(0); 4523 } 4524 */ 4525 } else { 4526 // no SSE; for my i3 unrolled loop is almost of the speed of SSE code 4527 T[4] accum = 0; 4528 foreach (immutable j; 0..N/4) { 4529 accum.ptr[0] += *sinct++ * *iptr++; 4530 accum.ptr[1] += *sinct++ * *iptr++; 4531 accum.ptr[2] += *sinct++ * *iptr++; 4532 accum.ptr[3] += *sinct++ * *iptr++; 4533 } 4534 sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3]; 4535 } 4536 } else { 4537 sum = 0; 4538 foreach (immutable j; 0..N) sum += *sinct++ * *iptr++; 4539 } 4540 outdata[outStride*outSample++] = sum; 4541 } else { 4542 if (N%4 == 0) { 4543 //TODO: write SSE code here! 4544 // for my i3 unrolled loop is ~2 times faster 4545 T[4] accum = 0; 4546 foreach (immutable j; 0..N/4) { 4547 accum.ptr[0] += cast(double)*sinct++ * cast(double)*iptr++; 4548 accum.ptr[1] += cast(double)*sinct++ * cast(double)*iptr++; 4549 accum.ptr[2] += cast(double)*sinct++ * cast(double)*iptr++; 4550 accum.ptr[3] += cast(double)*sinct++ * cast(double)*iptr++; 4551 } 4552 sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3]; 4553 } else { 4554 sum = 0; 4555 foreach (immutable j; 0..N) sum += cast(double)*sinct++ * cast(double)*iptr++; 4556 } 4557 outdata[outStride*outSample++] = cast(float)sum; 4558 } 4559 lastSample += intAdvance; 4560 sampFracNum += fracAdvance; 4561 if (sampFracNum >= denRate) { 4562 sampFracNum -= denRate; 4563 ++lastSample; 4564 } 4565 } 4566 st.lastSample.ptr[chanIdx] = lastSample; 4567 st.sampFracNum.ptr[chanIdx] = sampFracNum; 4568 return outSample; 4569 } 4570 4571 4572 int resamplerBasicInterpolate(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen) 4573 if (is(T == float) || is(T == double)) 4574 { 4575 immutable N = st.filterLen; 4576 assert(N%4 == 0); 4577 int outSample = 0; 4578 int lastSample = st.lastSample.ptr[chanIdx]; 4579 uint sampFracNum = st.sampFracNum.ptr[chanIdx]; 4580 immutable outStride = st.outStride; 4581 immutable intAdvance = st.intAdvance; 4582 immutable fracAdvance = st.fracAdvance; 4583 immutable denRate = st.denRate; 4584 float sum; 4585 4586 float[4] interp = void; 4587 T[4] accum = void; 4588 while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) { 4589 const(float)* iptr = &indata[lastSample]; 4590 const int offset = sampFracNum*st.oversample/st.denRate; 4591 const float frac = (cast(float)((sampFracNum*st.oversample)%st.denRate))/st.denRate; 4592 accum[] = 0; 4593 //TODO: optimize! 4594 foreach (immutable j; 0..N) { 4595 immutable T currIn = iptr[j]; 4596 accum.ptr[0] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-2]); 4597 accum.ptr[1] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-1]); 4598 accum.ptr[2] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset]); 4599 accum.ptr[3] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset+1]); 4600 } 4601 4602 cubicCoef(frac, interp.ptr); 4603 sum = (interp.ptr[0]*accum.ptr[0])+(interp.ptr[1]*accum.ptr[1])+(interp.ptr[2]*accum.ptr[2])+(interp.ptr[3]*accum.ptr[3]); 4604 4605 outdata[outStride*outSample++] = sum; 4606 lastSample += intAdvance; 4607 sampFracNum += fracAdvance; 4608 if (sampFracNum >= denRate) { 4609 sampFracNum -= denRate; 4610 ++lastSample; 4611 } 4612 } 4613 4614 st.lastSample.ptr[chanIdx] = lastSample; 4615 st.sampFracNum.ptr[chanIdx] = sampFracNum; 4616 return outSample; 4617 } 4618 4619 4620 // ////////////////////////////////////////////////////////////////////////// // 4621 uint gcd (uint a, uint b) pure { 4622 if (a == 0) return b; 4623 if (b == 0) return a; 4624 for (;;) { 4625 if (a > b) { 4626 a %= b; 4627 if (a == 0) return b; 4628 if (a == 1) return 1; 4629 } else { 4630 b %= a; 4631 if (b == 0) return a; 4632 if (b == 1) return 1; 4633 } 4634 } 4635 } 4636 4637 4638 // ////////////////////////////////////////////////////////////////////////// // 4639 // very simple and cheap cubic upsampler 4640 struct CubicUpsampler { 4641 public: 4642 nothrow @trusted @nogc: 4643 float[2] curposfrac; // current position offset [0..1) 4644 float step; // how long we should move on one step? 4645 float[4][2] data; // -1..3 4646 uint[2] drain; 4647 4648 void reset () { 4649 curposfrac[] = 0.0f; 4650 foreach (ref d; data) d[] = 0.0f; 4651 drain[] = 0; 4652 } 4653 4654 bool setup (float astep) { 4655 if (astep >= 1.0f) return false; 4656 step = astep; 4657 return true; 4658 } 4659 4660 /* 4661 static struct Data { 4662 const(float)[] dataIn; 4663 float[] dataOut; 4664 uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count) 4665 uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count) 4666 } 4667 */ 4668 4669 SpeexResampler.Error process (ref SpeexResampler.Data d) { 4670 d.inputSamplesUsed = d.outputSamplesUsed = 0; 4671 if (d.dataOut.length < 2) return SpeexResampler.Error.OK; 4672 foreach (uint cidx; 0..2) { 4673 uint inleft = cast(uint)d.dataIn.length/2; 4674 uint outleft = cast(uint)d.dataOut.length/2; 4675 processChannel(inleft, outleft, (d.dataIn.length ? d.dataIn.ptr+cidx : null), (d.dataOut.length ? d.dataOut.ptr+cidx : null), cidx); 4676 d.outputSamplesUsed += cast(uint)(d.dataOut.length/2)-outleft; 4677 d.inputSamplesUsed += cast(uint)(d.dataIn.length/2)-inleft; 4678 } 4679 return SpeexResampler.Error.OK; 4680 } 4681 4682 private void processChannel (ref uint inleft, ref uint outleft, const(float)* dataIn, float* dataOut, uint cidx) { 4683 if (outleft == 0) return; 4684 if (inleft == 0 && drain.ptr[cidx] <= 1) return; 4685 auto dt = data.ptr[cidx].ptr; 4686 auto drn = drain.ptr+cidx; 4687 auto cpf = curposfrac.ptr+cidx; 4688 immutable float st = step; 4689 for (;;) { 4690 // fill buffer 4691 while ((*drn) < 4) { 4692 if (inleft == 0) return; 4693 dt[(*drn)++] = *dataIn; 4694 dataIn += 2; 4695 --inleft; 4696 } 4697 if (outleft == 0) return; 4698 --outleft; 4699 // cubic interpolation 4700 /*version(none)*/ { 4701 // interpolate between y1 and y2 4702 immutable float mu = (*cpf); // how far we are moved from y1 to y2 4703 immutable float mu2 = mu*mu; // wow 4704 immutable float y0 = dt[0], y1 = dt[1], y2 = dt[2], y3 = dt[3]; 4705 version(complex_cubic) { 4706 immutable float z0 = 0.5*y3; 4707 immutable float z1 = 0.5*y0; 4708 immutable float a0 = 1.5*y1-z1-1.5*y2+z0; 4709 immutable float a1 = y0-2.5*y1+2*y2-z0; 4710 immutable float a2 = 0.5*y2-z1; 4711 } else { 4712 immutable float a0 = y3-y2-y0+y1; 4713 immutable float a1 = y0-y1-a0; 4714 immutable float a2 = y2-y0; 4715 } 4716 *dataOut = a0*mu*mu2+a1*mu2+a2*mu+y1; 4717 }// else *dataOut = dt[1]; 4718 dataOut += 2; 4719 if (((*cpf) += st) >= 1.0f) { 4720 (*cpf) -= 1.0f; 4721 dt[0] = dt[1]; 4722 dt[1] = dt[2]; 4723 dt[2] = dt[3]; 4724 dt[3] = 0.0f; 4725 --(*drn); // will request more input bytes 4726 } 4727 } 4728 } 4729 } 4730 } 4731 4732 version(with_resampler) 4733 abstract class ResamplingContext { 4734 int inputSampleRate; 4735 int outputSampleRate; 4736 4737 int inputChannels; 4738 int outputChannels; 4739 4740 SpeexResampler resamplerLeft; 4741 SpeexResampler resamplerRight; 4742 4743 SpeexResampler.Data resamplerDataLeft; 4744 SpeexResampler.Data resamplerDataRight; 4745 4746 float[][2] buffersIn; 4747 float[][2] buffersOut; 4748 4749 uint rateNum; 4750 uint rateDem; 4751 4752 float[][2] dataReady; 4753 4754 SampleControlFlags scflags; 4755 4756 this(SampleControlFlags scflags, int inputSampleRate, int outputSampleRate, int inputChannels, int outputChannels) { 4757 this.scflags = scflags; 4758 this.inputSampleRate = inputSampleRate; 4759 this.outputSampleRate = outputSampleRate; 4760 this.inputChannels = inputChannels; 4761 this.outputChannels = outputChannels; 4762 4763 4764 if(auto err = resamplerLeft.setup(1, inputSampleRate, outputSampleRate, 5)) 4765 throw new Exception("ugh"); 4766 resamplerRight.setup(1, inputSampleRate, outputSampleRate, 5); 4767 4768 processNewRate(); 4769 } 4770 4771 void changePlaybackSpeed(float newMultiplier) { 4772 resamplerLeft.setRate(cast(uint) (inputSampleRate * newMultiplier), outputSampleRate); 4773 resamplerRight.setRate(cast(uint) (inputSampleRate * newMultiplier), outputSampleRate); 4774 4775 processNewRate(); 4776 } 4777 4778 void processNewRate() { 4779 resamplerLeft.getRatio(rateNum, rateDem); 4780 4781 int add = (rateNum % rateDem) ? 1 : 0; 4782 4783 buffersIn[0] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add); 4784 buffersOut[0] = new float[](BUFFER_SIZE_FRAMES); 4785 if(inputChannels > 1) { 4786 buffersIn[1] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add); 4787 buffersOut[1] = new float[](BUFFER_SIZE_FRAMES); 4788 } 4789 4790 } 4791 4792 /+ 4793 float*[2] tmp; 4794 tmp[0] = buffersIn[0].ptr; 4795 tmp[1] = buffersIn[1].ptr; 4796 4797 auto actuallyGot = v.getSamplesFloat(v.chans, tmp.ptr, cast(int) buffersIn[0].length); 4798 4799 resamplerDataLeft.dataIn should be a slice of buffersIn[0] that is filled up 4800 ditto for resamplerDataRight if the source has two channels 4801 +/ 4802 abstract void loadMoreSamples(); 4803 4804 bool loadMore() { 4805 resamplerDataLeft.dataIn = buffersIn[0]; 4806 resamplerDataLeft.dataOut = buffersOut[0]; 4807 4808 resamplerDataRight.dataIn = buffersIn[1]; 4809 resamplerDataRight.dataOut = buffersOut[1]; 4810 4811 loadMoreSamples(); 4812 4813 //resamplerLeft.reset(); 4814 4815 if(auto err = resamplerLeft.process(resamplerDataLeft)) 4816 throw new Exception("ugh"); 4817 if(inputChannels > 1) 4818 //resamplerRight.reset(); 4819 resamplerRight.process(resamplerDataRight); 4820 4821 resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[0 .. resamplerDataLeft.outputSamplesUsed]; 4822 resamplerDataRight.dataOut = resamplerDataRight.dataOut[0 .. resamplerDataRight.outputSamplesUsed]; 4823 4824 if(resamplerDataLeft.dataOut.length == 0) { 4825 return true; 4826 } 4827 return false; 4828 } 4829 4830 4831 bool fillBuffer(short[] buffer) { 4832 if(cast(int) buffer.length != buffer.length) 4833 throw new Exception("eeeek"); 4834 4835 if(scflags.paused) { 4836 buffer[] = 0; 4837 return true; 4838 } 4839 4840 if(outputChannels == 1) { 4841 foreach(ref s; buffer) { 4842 if(resamplerDataLeft.dataOut.length == 0) { 4843 if(loadMore()) { 4844 scflags.finished_ = true; 4845 return false; 4846 } 4847 } 4848 4849 if(inputChannels == 1) { 4850 s = cast(short) (resamplerDataLeft.dataOut[0] * short.max); 4851 resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $]; 4852 } else { 4853 s = cast(short) ((resamplerDataLeft.dataOut[0] + resamplerDataRight.dataOut[0]) * short.max / 2); 4854 4855 resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $]; 4856 resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $]; 4857 } 4858 } 4859 4860 scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels * scflags.playbackSpeed; 4861 } else if(outputChannels == 2) { 4862 foreach(idx, ref s; buffer) { 4863 if(resamplerDataLeft.dataOut.length == 0) { 4864 if(loadMore()) { 4865 scflags.finished_ = true; 4866 return false; 4867 } 4868 } 4869 4870 if(inputChannels == 1) { 4871 s = cast(short) (resamplerDataLeft.dataOut[0] * short.max); 4872 if(idx & 1) 4873 resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $]; 4874 } else { 4875 if(idx & 1) { 4876 s = cast(short) (resamplerDataRight.dataOut[0] * short.max); 4877 resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $]; 4878 } else { 4879 s = cast(short) (resamplerDataLeft.dataOut[0] * short.max); 4880 resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $]; 4881 } 4882 } 4883 } 4884 4885 scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels * scflags.playbackSpeed; 4886 } else assert(0); 4887 4888 if(scflags.stopped) 4889 scflags.finished_ = true; 4890 return !scflags.stopped; 4891 } 4892 } 4893 4894 private enum scriptable = "arsd_jsvar_compatible";