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