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