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