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 static if(__traits(hasMember, event, "setIfInitialized")) 1781 event.setIfInitialized(); 1782 else 1783 event.set(); 1784 } 1785 1786 /// ditto 1787 public bool suspended() { 1788 return suspended_; 1789 } 1790 1791 /++ 1792 Stops playback and unsupends if necessary and exits. 1793 1794 Call this instead of join. 1795 1796 Please note: you should never call this from inside an audio 1797 callback, as it will crash or deadlock. Instead, just return false 1798 from your buffer fill function to indicate that you are done. 1799 1800 History: 1801 Added December 30, 2021 (dub v10.5) 1802 +/ 1803 public Throwable exit(bool rethrow = false) { 1804 exiting = true; 1805 unsuspend(); 1806 if(ao) 1807 ao.stop(); 1808 1809 return join(rethrow); 1810 } 1811 1812 1813 private void run() { 1814 version(linux) { 1815 // this thread has no business intercepting signals from the main thread, 1816 // so gonna block a couple of them 1817 import core.sys.posix.signal; 1818 sigset_t sigset; 1819 auto err = sigemptyset(&sigset); 1820 assert(!err); 1821 1822 err = sigaddset(&sigset, SIGINT); assert(!err); 1823 err = sigaddset(&sigset, SIGCHLD); assert(!err); 1824 1825 err = sigprocmask(SIG_BLOCK, &sigset, null); 1826 assert(!err); 1827 } 1828 1829 AudioOutput ao = AudioOutput(device, SampleRate, channels); 1830 1831 this.ao = &ao; 1832 scope(exit) this.ao = null; 1833 auto omg = this; 1834 ao.fillData = (short[] buffer) { 1835 short[BUFFER_SIZE_SHORT] bfr; 1836 bool first = true; 1837 if(fillDatasLength) { 1838 for(int idx = 0; idx < fillDatasLength; idx++) { 1839 auto dg = fillDatas[idx]; 1840 auto ret = dg(bfr[0 .. buffer.length][]); 1841 foreach(i, v; bfr[0 .. buffer.length][]) { 1842 int val; 1843 if(first) 1844 val = 0; 1845 else 1846 val = buffer[i]; 1847 1848 int a = val; 1849 int b = v; 1850 int cap = a + b; 1851 if(cap > short.max) cap = short.max; 1852 else if(cap < short.min) cap = short.min; 1853 val = cast(short) cap; 1854 buffer[i] = cast(short) val; 1855 } 1856 if(!ret) { 1857 // it returned false meaning this one is finished... 1858 synchronized(omg) { 1859 fillDatas[idx] = fillDatas[fillDatasLength - 1]; 1860 fillDatasLength--; 1861 } 1862 idx--; 1863 } 1864 1865 first = false; 1866 } 1867 } else { 1868 buffer[] = 0; 1869 } 1870 }; 1871 //try 1872 resume_from_suspend: 1873 ao.play(); 1874 /+ 1875 catch(Throwable t) { 1876 import std.stdio; 1877 writeln(t); 1878 } 1879 +/ 1880 1881 if(suspendWanted) { 1882 ao.close(); 1883 1884 event.initialize(true, false); 1885 if(event.wait() && !exiting) { 1886 event.reset(); 1887 1888 ao.open(); 1889 goto resume_from_suspend; 1890 } 1891 } 1892 1893 event.terminate(); 1894 } 1895 1896 static if(__VERSION__ > 2080) { 1897 import core.sync.event; 1898 } else { 1899 // bad emulation of the Event but meh 1900 static struct Event { 1901 void terminate() {} 1902 void initialize(bool, bool) {} 1903 1904 bool isSet; 1905 1906 void set() { isSet = true; } 1907 void reset() { isSet = false; } 1908 bool wait() { 1909 while(!isSet) { 1910 Thread.sleep(500.msecs); 1911 } 1912 isSet = false; 1913 return true; 1914 } 1915 1916 } 1917 } 1918 1919 Event event; 1920 } 1921 1922 1923 import core.stdc.config; 1924 1925 version(linux) version=ALSA; 1926 version(Windows) version=WinMM; 1927 1928 version(ALSA) { 1929 // this is the virtual rawmidi device on my computer at least 1930 // maybe later i'll make it probe 1931 // 1932 // Getting midi to actually play on Linux is a bit of a pain. 1933 // Here's what I did: 1934 /* 1935 # load the kernel driver, if amidi -l gives ioctl error, 1936 # you haven't done this yet! 1937 modprobe snd-virmidi 1938 1939 # start a software synth. timidity -iA is also an option 1940 fluidsynth soundfont.sf2 1941 1942 # connect the virtual hardware port to the synthesizer 1943 aconnect 24:0 128:0 1944 1945 1946 I might also add a snd_seq client here which is a bit 1947 easier to setup but for now I'm using the rawmidi so you 1948 gotta get them connected somehow. 1949 */ 1950 1951 // fyi raw midi dump: amidi -d --port hw:4,0 1952 // connect my midi out to fluidsynth: aconnect 28:0 128:0 1953 // and my keyboard to it: aconnect 32:0 128:0 1954 } 1955 1956 /// Thrown on audio failures. 1957 /// Subclass this to provide OS-specific exceptions 1958 class AudioException : Exception { 1959 this(string message, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 1960 super(message, file, line, next); 1961 } 1962 } 1963 1964 /++ 1965 Gives PCM input access (such as a microphone). 1966 1967 History: 1968 Windows support added May 10, 2020 and the API got overhauled too. 1969 +/ 1970 struct AudioInput { 1971 version(ALSA) { 1972 snd_pcm_t* handle; 1973 } else version(WinMM) { 1974 HWAVEIN handle; 1975 HANDLE event; 1976 } else static assert(0); 1977 1978 @disable this(); 1979 @disable this(this); 1980 1981 int channels; 1982 int SampleRate; 1983 1984 /// Always pass card == 0. 1985 this(int card, int SampleRate = 44100, int channels = 2) { 1986 assert(card == 0); 1987 this("default", SampleRate, channels); 1988 } 1989 1990 /++ 1991 `device` is a device name. On Linux, it is the ALSA string. 1992 On Windows, it is currently ignored, so you should pass "default" 1993 or null so when it does get implemented your code won't break. 1994 1995 History: 1996 Added Nov 8, 2020. 1997 +/ 1998 this(string device, int SampleRate = 44100, int channels = 2) { 1999 assert(channels == 1 || channels == 2); 2000 2001 this.channels = channels; 2002 this.SampleRate = SampleRate; 2003 2004 version(ALSA) { 2005 handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_CAPTURE, SampleRate, channels, device); 2006 } else version(WinMM) { 2007 event = CreateEvent(null, false /* manual reset */, false /* initially triggered */, null); 2008 2009 WAVEFORMATEX format; 2010 format.wFormatTag = WAVE_FORMAT_PCM; 2011 format.nChannels = 2; 2012 format.nSamplesPerSec = SampleRate; 2013 format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample 2014 format.nBlockAlign = 4; 2015 format.wBitsPerSample = 16; 2016 format.cbSize = 0; 2017 if(auto err = waveInOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) event, cast(DWORD_PTR) &this, CALLBACK_EVENT)) 2018 throw new WinMMException("wave in open", err); 2019 2020 } else static assert(0); 2021 } 2022 2023 /// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz 2024 /// Each item in the array thus alternates between left and right channel 2025 /// and it takes a total of 88,200 items to make one second of sound. 2026 /// 2027 /// Returns the slice of the buffer actually read into 2028 /// 2029 /// LINUX ONLY. You should prolly use [record] instead 2030 version(ALSA) 2031 short[] read(short[] buffer) { 2032 snd_pcm_sframes_t read; 2033 2034 read = snd_pcm_readi(handle, buffer.ptr, buffer.length / channels /* div number of channels apparently */); 2035 if(read < 0) { 2036 read = snd_pcm_recover(handle, cast(int) read, 0); 2037 if(read < 0) 2038 throw new AlsaException("pcm read", cast(int)read); 2039 return null; 2040 } 2041 2042 return buffer[0 .. read * channels]; 2043 } 2044 2045 /// passes a buffer of data to fill 2046 /// 2047 /// Data is delivered as interleaved stereo, LE 16 bit, 44.1 kHz 2048 /// Each item in the array thus alternates between left and right channel 2049 /// and it takes a total of 88,200 items to make one second of sound. 2050 void delegate(short[]) receiveData; 2051 2052 /// 2053 void stop() { 2054 recording = false; 2055 } 2056 2057 /// First, set [receiveData], then call this. 2058 void record() @system /* FIXME https://issues.dlang.org/show_bug.cgi?id=24782 */ { 2059 assert(receiveData !is null); 2060 recording = true; 2061 2062 version(ALSA) { 2063 short[BUFFER_SIZE_SHORT] buffer; 2064 while(recording) { 2065 auto got = read(buffer); 2066 receiveData(got); 2067 } 2068 } else version(WinMM) { 2069 2070 enum numBuffers = 2; // use a lot of buffers to get smooth output with Sleep, see below 2071 short[BUFFER_SIZE_SHORT][numBuffers] buffers; 2072 2073 WAVEHDR[numBuffers] headers; 2074 2075 foreach(i, ref header; headers) { 2076 auto buffer = buffers[i][]; 2077 header.lpData = cast(char*) buffer.ptr; 2078 header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof; 2079 header.dwFlags = 0;// WHDR_BEGINLOOP | WHDR_ENDLOOP; 2080 header.dwLoops = 0; 2081 2082 if(auto err = waveInPrepareHeader(handle, &header, header.sizeof)) 2083 throw new WinMMException("prepare header", err); 2084 2085 header.dwUser = 1; // mark that the driver is using it 2086 if(auto err = waveInAddBuffer(handle, &header, header.sizeof)) 2087 throw new WinMMException("wave in read", err); 2088 } 2089 2090 waveInStart(handle); 2091 scope(failure) waveInReset(handle); 2092 2093 while(recording) { 2094 if(auto err = WaitForSingleObject(event, INFINITE)) 2095 throw new Exception("WaitForSingleObject"); 2096 if(!recording) 2097 break; 2098 2099 foreach(ref header; headers) { 2100 if(!(header.dwFlags & WHDR_DONE)) continue; 2101 2102 receiveData((cast(short*) header.lpData)[0 .. header.dwBytesRecorded / short.sizeof]); 2103 if(!recording) break; 2104 header.dwUser = 1; // mark that the driver is using it 2105 if(auto err = waveInAddBuffer(handle, &header, header.sizeof)) { 2106 throw new WinMMException("waveInAddBuffer", err); 2107 } 2108 } 2109 } 2110 2111 /* 2112 if(auto err = waveInStop(handle)) 2113 throw new WinMMException("wave in stop", err); 2114 */ 2115 2116 if(auto err = waveInReset(handle)) { 2117 throw new WinMMException("wave in reset", err); 2118 } 2119 2120 still_in_use: 2121 foreach(idx, header; headers) 2122 if(!(header.dwFlags & WHDR_DONE)) { 2123 Sleep(1); 2124 goto still_in_use; 2125 } 2126 2127 foreach(ref header; headers) 2128 if(auto err = waveInUnprepareHeader(handle, &header, header.sizeof)) { 2129 throw new WinMMException("unprepare header", err); 2130 } 2131 2132 ResetEvent(event); 2133 } else static assert(0); 2134 } 2135 2136 private bool recording; 2137 2138 ~this() { 2139 receiveData = null; 2140 version(ALSA) { 2141 snd_pcm_close(handle); 2142 } else version(WinMM) { 2143 if(auto err = waveInClose(handle)) 2144 throw new WinMMException("close", err); 2145 2146 CloseHandle(event); 2147 // in wine (though not Windows nor winedbg as far as I can tell) 2148 // this randomly segfaults. the sleep prevents it. idk why. 2149 Sleep(5); 2150 } else static assert(0); 2151 } 2152 } 2153 2154 /// 2155 enum SampleRateFull = 44100; 2156 2157 /// Gives PCM output access (such as the speakers). 2158 struct AudioOutput { 2159 version(ALSA) { 2160 snd_pcm_t* handle; 2161 } else version(WinMM) { 2162 HWAVEOUT handle; 2163 } 2164 2165 @disable this(); 2166 // This struct must NEVER be moved or copied, a pointer to it may 2167 // be passed to a device driver and stored! 2168 @disable this(this); 2169 2170 private int SampleRate; 2171 private int channels; 2172 private string device; 2173 2174 /++ 2175 `device` is a device name. On Linux, it is the ALSA string. 2176 On Windows, it is currently ignored, so you should pass "default" 2177 or null so when it does get implemented your code won't break. 2178 2179 History: 2180 Added Nov 8, 2020. 2181 +/ 2182 this(string device, int SampleRate = 44100, int channels = 2) { 2183 assert(channels == 1 || channels == 2); 2184 2185 this.SampleRate = SampleRate; 2186 this.channels = channels; 2187 this.device = device; 2188 2189 open(); 2190 } 2191 2192 /// Always pass card == 0. 2193 this(int card, int SampleRate = 44100, int channels = 2) { 2194 assert(card == 0); 2195 2196 this("default", SampleRate, channels); 2197 } 2198 2199 /// passes a buffer of data to fill 2200 /// 2201 /// Data is assumed to be interleaved stereo, LE 16 bit, 44.1 kHz (unless you change that in the ctor) 2202 /// Each item in the array thus alternates between left and right channel (unless you change that in the ctor) 2203 /// and it takes a total of 88,200 items to make one second of sound. 2204 void delegate(short[]) fillData; 2205 2206 shared(bool) playing = false; // considered to be volatile 2207 2208 /// Starts playing, loops until stop is called 2209 void play() @system /* FIXME https://issues.dlang.org/show_bug.cgi?id=24782 */ { 2210 if(handle is null) 2211 open(); 2212 2213 assert(fillData !is null); 2214 playing = true; 2215 2216 version(ALSA) { 2217 short[BUFFER_SIZE_SHORT] buffer; 2218 while(playing) { 2219 auto err = snd_pcm_wait(handle, 500); 2220 if(err < 0) { 2221 // see: https://stackoverflow.com/a/59400592/1457000 2222 err = snd_pcm_recover(handle, err, 0); 2223 if(err) 2224 throw new AlsaException("pcm recover failed after pcm_wait did ", err); 2225 //throw new AlsaException("uh oh", err); 2226 continue; 2227 } 2228 if(err == 0) 2229 continue; 2230 // err == 0 means timeout 2231 // err == 1 means ready 2232 2233 auto ready = snd_pcm_avail_update(handle); 2234 if(ready < 0) { 2235 //import std.stdio; writeln("recover"); 2236 2237 // actually it seems ok to just try again.. 2238 2239 // err = snd_pcm_recover(handle, err, 0); 2240 //if(err) 2241 //throw new AlsaException("avail", cast(int)ready); 2242 continue; 2243 } 2244 if(ready > BUFFER_SIZE_FRAMES) 2245 ready = BUFFER_SIZE_FRAMES; 2246 //import std.stdio; writeln("filling ", ready); 2247 fillData(buffer[0 .. ready * channels]); 2248 if(playing) { 2249 snd_pcm_sframes_t written; 2250 auto data = buffer[0 .. ready * channels]; 2251 2252 while(data.length) { 2253 written = snd_pcm_writei(handle, data.ptr, data.length / channels); 2254 if(written < 0) { 2255 //import std.stdio; writeln(written); 2256 written = snd_pcm_recover(handle, cast(int)written, 0); 2257 //import std.stdio; writeln("recover ", written); 2258 if (written < 0) throw new AlsaException("pcm write", cast(int)written); 2259 } 2260 data = data[written * channels .. $]; 2261 } 2262 } 2263 } 2264 } else version(WinMM) { 2265 2266 enum numBuffers = 4; // use a lot of buffers to get smooth output with Sleep, see below 2267 short[BUFFER_SIZE_SHORT][numBuffers] buffers; 2268 2269 WAVEHDR[numBuffers] headers; 2270 2271 foreach(i, ref header; headers) { 2272 // since this is wave out, it promises not to write... 2273 auto buffer = buffers[i][]; 2274 header.lpData = cast(char*) buffer.ptr; 2275 header.dwBufferLength = cast(int) buffer.length * cast(int) short.sizeof; 2276 header.dwFlags = WHDR_BEGINLOOP | WHDR_ENDLOOP; 2277 header.dwLoops = 1; 2278 2279 if(auto err = waveOutPrepareHeader(handle, &header, header.sizeof)) 2280 throw new WinMMException("prepare header", err); 2281 2282 // prime it 2283 fillData(buffer[]); 2284 2285 // indicate that they are filled and good to go 2286 header.dwUser = 1; 2287 } 2288 2289 while(playing) { 2290 // and queue both to be played, if they are ready 2291 foreach(ref header; headers) 2292 if(header.dwUser) { 2293 if(auto err = waveOutWrite(handle, &header, header.sizeof)) 2294 throw new WinMMException("wave out write", err); 2295 header.dwUser = 0; 2296 } 2297 Sleep(1); 2298 // the system resolution may be lower than this sleep. To avoid gaps 2299 // in output, we use multiple buffers. Might introduce latency, not 2300 // sure how best to fix. I don't want to busy loop... 2301 } 2302 2303 // wait for the system to finish with our buffers 2304 bool anyInUse = true; 2305 2306 while(anyInUse) { 2307 anyInUse = false; 2308 foreach(header; headers) { 2309 if(!header.dwUser) { 2310 anyInUse = true; 2311 break; 2312 } 2313 } 2314 if(anyInUse) 2315 Sleep(1); 2316 } 2317 2318 foreach(ref header; headers) 2319 if(auto err = waveOutUnprepareHeader(handle, &header, header.sizeof)) 2320 throw new WinMMException("unprepare", err); 2321 } else static assert(0); 2322 2323 close(); 2324 } 2325 2326 /// Breaks the play loop 2327 void stop() { 2328 playing = false; 2329 } 2330 2331 /// 2332 void pause() { 2333 version(WinMM) 2334 waveOutPause(handle); 2335 else version(ALSA) 2336 snd_pcm_pause(handle, 1); 2337 } 2338 2339 /// 2340 void unpause() { 2341 version(WinMM) 2342 waveOutRestart(handle); 2343 else version(ALSA) 2344 snd_pcm_pause(handle, 0); 2345 2346 } 2347 2348 version(WinMM) { 2349 extern(Windows) 2350 static void mmCallback(HWAVEOUT handle, UINT msg, void* userData, WAVEHDR* header, DWORD_PTR param2) { 2351 AudioOutput* ao = cast(AudioOutput*) userData; 2352 if(msg == WOM_DONE) { 2353 // we want to bounce back and forth between two buffers 2354 // to keep the sound going all the time 2355 if(ao.playing) { 2356 ao.fillData((cast(short*) header.lpData)[0 .. header.dwBufferLength / short.sizeof]); 2357 } 2358 header.dwUser = 1; 2359 } 2360 } 2361 } 2362 2363 2364 /++ 2365 Re-opens the audio device that you have previously [close]d. 2366 2367 History: 2368 Added December 30, 2021 2369 +/ 2370 void open() { 2371 assert(handle is null); 2372 assert(!playing); 2373 version(ALSA) { 2374 handle = openAlsaPcm(snd_pcm_stream_t.SND_PCM_STREAM_PLAYBACK, SampleRate, channels, device); 2375 } else version(WinMM) { 2376 WAVEFORMATEX format; 2377 format.wFormatTag = WAVE_FORMAT_PCM; 2378 format.nChannels = cast(ushort) channels; 2379 format.nSamplesPerSec = SampleRate; 2380 format.nAvgBytesPerSec = SampleRate * channels * 2; // two channels, two bytes per sample 2381 format.nBlockAlign = cast(short)(channels * 2); 2382 format.wBitsPerSample = 16; 2383 format.cbSize = 0; 2384 if(auto err = waveOutOpen(&handle, WAVE_MAPPER, &format, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION)) 2385 throw new WinMMException("wave out open", err); 2386 } else static assert(0); 2387 } 2388 2389 /++ 2390 Closes the audio device. You MUST call [stop] before calling this. 2391 2392 History: 2393 Added December 30, 2021 2394 +/ 2395 void close() { 2396 if(!handle) 2397 return; 2398 assert(!playing); 2399 version(ALSA) { 2400 snd_pcm_close(handle); 2401 handle = null; 2402 } else version(WinMM) { 2403 waveOutClose(handle); 2404 handle = null; 2405 } else static assert(0); 2406 } 2407 2408 // FIXME: add async function hooks 2409 2410 ~this() { 2411 close(); 2412 } 2413 } 2414 2415 /++ 2416 For reading midi events from hardware, for example, an electronic piano keyboard 2417 attached to the computer. 2418 +/ 2419 struct MidiInput { 2420 // reading midi devices... 2421 version(ALSA) { 2422 snd_rawmidi_t* handle; 2423 } else version(WinMM) { 2424 HMIDIIN handle; 2425 } 2426 2427 @disable this(); 2428 @disable this(this); 2429 2430 /+ 2431 B0 40 7F # pedal on 2432 B0 40 00 # sustain pedal off 2433 +/ 2434 2435 /// Always pass card == 0. 2436 this(int card) { 2437 assert(card == 0); 2438 2439 this("default"); // "hw:4,0" 2440 } 2441 2442 /++ 2443 `device` is a device name. On Linux, it is the ALSA string. 2444 On Windows, it is currently ignored, so you should pass "default" 2445 or null so when it does get implemented your code won't break. 2446 2447 History: 2448 Added Nov 8, 2020. 2449 +/ 2450 this(string device) { 2451 version(ALSA) { 2452 if(auto err = snd_rawmidi_open(&handle, null, device.toStringz, 0)) 2453 throw new AlsaException("rawmidi open", err); 2454 } else version(WinMM) { 2455 if(auto err = midiInOpen(&handle, 0, cast(DWORD_PTR) &mmCallback, cast(DWORD_PTR) &this, CALLBACK_FUNCTION)) 2456 throw new WinMMException("midi in open", err); 2457 } else static assert(0); 2458 } 2459 2460 private bool recording = false; 2461 2462 /// 2463 void stop() { 2464 recording = false; 2465 } 2466 2467 /++ 2468 Records raw midi input data from the device. 2469 2470 The timestamp is given in milliseconds since recording 2471 began (if you keep this program running for 23ish days 2472 it might overflow! so... don't do that.). The other bytes 2473 are the midi messages. 2474 2475 $(PITFALL Do not call any other multimedia functions from the callback!) 2476 +/ 2477 void record(void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg) { 2478 version(ALSA) { 2479 recording = true; 2480 ubyte[1024] data; 2481 import core.time; 2482 auto start = MonoTime.currTime; 2483 while(recording) { 2484 auto read = snd_rawmidi_read(handle, data.ptr, data.length); 2485 if(read < 0) 2486 throw new AlsaException("midi read", cast(int) read); 2487 2488 auto got = data[0 .. read]; 2489 while(got.length) { 2490 // FIXME some messages are fewer bytes.... 2491 dg(cast(uint) (MonoTime.currTime - start).total!"msecs", got[0], got[1], got[2]); 2492 got = got[3 .. $]; 2493 } 2494 } 2495 } else version(WinMM) { 2496 recording = true; 2497 this.dg = dg; 2498 scope(exit) 2499 this.dg = null; 2500 midiInStart(handle); 2501 scope(exit) 2502 midiInReset(handle); 2503 2504 while(recording) { 2505 Sleep(1); 2506 } 2507 } else static assert(0); 2508 } 2509 2510 version(WinMM) 2511 private void delegate(uint timestamp, ubyte b1, ubyte b2, ubyte b3) dg; 2512 2513 2514 version(WinMM) 2515 extern(Windows) 2516 static 2517 void mmCallback(HMIDIIN handle, UINT msg, DWORD_PTR user, DWORD_PTR param1, DWORD_PTR param2) { 2518 MidiInput* mi = cast(MidiInput*) user; 2519 if(msg == MIM_DATA) { 2520 mi.dg( 2521 cast(uint) param2, 2522 param1 & 0xff, 2523 (param1 >> 8) & 0xff, 2524 (param1 >> 16) & 0xff 2525 ); 2526 } 2527 } 2528 2529 ~this() { 2530 version(ALSA) { 2531 snd_rawmidi_close(handle); 2532 } else version(WinMM) { 2533 midiInClose(handle); 2534 } else static assert(0); 2535 } 2536 } 2537 2538 /// Gives MIDI output access. 2539 struct MidiOutput { 2540 version(ALSA) { 2541 snd_rawmidi_t* handle; 2542 } else version(WinMM) { 2543 HMIDIOUT handle; 2544 } 2545 2546 @disable this(); 2547 @disable this(this); 2548 2549 /// Always pass card == 0. 2550 this(int card) { 2551 assert(card == 0); 2552 2553 this("default"); // "hw:3,0" 2554 } 2555 2556 /++ 2557 `device` is a device name. On Linux, it is the ALSA string. 2558 On Windows, it is currently ignored, so you should pass "default" 2559 or null so when it does get implemented your code won't break. 2560 2561 If you pass the string "DUMMY", it will not actually open a device 2562 and simply be a do-nothing mock object; 2563 2564 History: 2565 Added Nov 8, 2020. 2566 2567 Support for the "DUMMY" device was added on January 2, 2022. 2568 +/ 2569 this(string device) { 2570 if(device == "DUMMY") 2571 return; 2572 2573 version(ALSA) { 2574 if(auto err = snd_rawmidi_open(null, &handle, device.toStringz, 0)) 2575 throw new AlsaException("rawmidi open", err); 2576 } else version(WinMM) { 2577 if(auto err = midiOutOpen(&handle, 0, 0, 0, CALLBACK_NULL)) 2578 throw new WinMMException("midi out open", err); 2579 } else static assert(0); 2580 } 2581 2582 void silenceAllNotes() { 2583 foreach(a; 0 .. 16) 2584 writeMidiMessage((0x0b << 4)|a /*MIDI_EVENT_CONTROLLER*/, 123, 0); 2585 } 2586 2587 /// Send a reset message, silencing all notes 2588 void reset() { 2589 if(!handle) return; 2590 2591 version(ALSA) { 2592 silenceAllNotes(); 2593 static immutable ubyte[1] resetCmd = [0xff]; 2594 writeRawMessageData(resetCmd[]); 2595 // and flush it immediately 2596 snd_rawmidi_drain(handle); 2597 } else version(WinMM) { 2598 if(auto error = midiOutReset(handle)) 2599 throw new WinMMException("midi reset", error); 2600 } else static assert(0); 2601 } 2602 2603 /// Writes a single low-level midi message 2604 /// Timing and sending sane data is your responsibility! 2605 void writeMidiMessage(int status, int param1, int param2) { 2606 if(!handle) return; 2607 version(ALSA) { 2608 ubyte[3] dataBuffer; 2609 2610 dataBuffer[0] = cast(ubyte) status; 2611 dataBuffer[1] = cast(ubyte) param1; 2612 dataBuffer[2] = cast(ubyte) param2; 2613 2614 auto msg = status >> 4; 2615 ubyte[] data; 2616 if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) 2617 data = dataBuffer[0 .. 2]; 2618 else 2619 data = dataBuffer[]; 2620 2621 writeRawMessageData(data); 2622 } else version(WinMM) { 2623 DWORD word = (param2 << 16) | (param1 << 8) | status; 2624 if(auto error = midiOutShortMsg(handle, word)) 2625 throw new WinMMException("midi out", error); 2626 } else static assert(0); 2627 2628 } 2629 2630 /// Writes a series of individual raw messages. 2631 /// Timing and sending sane data is your responsibility! 2632 /// The data should NOT include any timestamp bytes - each midi message should be 2 or 3 bytes. 2633 void writeRawMessageData(scope const(ubyte)[] data) { 2634 if(!handle) return; 2635 if(data.length == 0) 2636 return; 2637 version(ALSA) { 2638 ssize_t written; 2639 2640 while(data.length) { 2641 written = snd_rawmidi_write(handle, data.ptr, data.length); 2642 if(written < 0) 2643 throw new AlsaException("midi write", cast(int) written); 2644 data = data[cast(int) written .. $]; 2645 } 2646 } else version(WinMM) { 2647 while(data.length) { 2648 auto msg = data[0] >> 4; 2649 if(msg == MidiEvent.ProgramChange || msg == MidiEvent.ChannelAftertouch) { 2650 writeMidiMessage(data[0], data[1], 0); 2651 data = data[2 .. $]; 2652 } else { 2653 writeMidiMessage(data[0], data[1], data[2]); 2654 data = data[3 .. $]; 2655 } 2656 } 2657 } else static assert(0); 2658 } 2659 2660 ~this() { 2661 if(!handle) return; 2662 version(ALSA) { 2663 snd_rawmidi_close(handle); 2664 } else version(WinMM) { 2665 midiOutClose(handle); 2666 } else static assert(0); 2667 } 2668 } 2669 2670 2671 // FIXME: maybe add a PC speaker beep function for completeness 2672 2673 /// Interfaces with the default sound card. You should only have a single instance of this and it should 2674 /// be stack allocated, so its destructor cleans up after it. 2675 /// 2676 /// A mixer gives access to things like volume controls and mute buttons. It should also give a 2677 /// callback feature to alert you of when the settings are changed by another program. 2678 version(ALSA) // FIXME 2679 struct AudioMixer { 2680 // To port to a new OS: put the data in the right version blocks 2681 // then implement each function. Leave else static assert(0) at the 2682 // end of each version group in a function so it is easier to implement elsewhere later. 2683 // 2684 // If a function is only relevant on your OS, put the whole function in a version block 2685 // and give it an OS specific name of some sort. 2686 // 2687 // Feel free to do that btw without worrying about lowest common denominator: we want low level access when we want it. 2688 // 2689 // Put necessary bindings at the end of the file, or use an import if you like, but I prefer these files to be standalone. 2690 version(ALSA) { 2691 snd_mixer_t* handle; 2692 snd_mixer_selem_id_t* sid; 2693 snd_mixer_elem_t* selem; 2694 2695 c_long maxVolume, minVolume; // these are ok to use if you are writing ALSA specific code i guess 2696 2697 enum selemName = "Master"; 2698 } 2699 2700 @disable this(); 2701 @disable this(this); 2702 2703 /// Only cardId == 0 is supported 2704 this(int cardId) { 2705 assert(cardId == 0, "Pass 0 to use default sound card."); 2706 2707 this("default"); 2708 } 2709 2710 /++ 2711 `device` is a device name. On Linux, it is the ALSA string. 2712 On Windows, it is currently ignored, so you should pass "default" 2713 or null so when it does get implemented your code won't break. 2714 2715 History: 2716 Added Nov 8, 2020. 2717 +/ 2718 this(string device) { 2719 version(ALSA) { 2720 if(auto err = snd_mixer_open(&handle, 0)) 2721 throw new AlsaException("open sound", err); 2722 scope(failure) 2723 snd_mixer_close(handle); 2724 if(auto err = snd_mixer_attach(handle, device.toStringz)) 2725 throw new AlsaException("attach to sound card", err); 2726 if(auto err = snd_mixer_selem_register(handle, null, null)) 2727 throw new AlsaException("register mixer", err); 2728 if(auto err = snd_mixer_load(handle)) 2729 throw new AlsaException("load mixer", err); 2730 2731 if(auto err = snd_mixer_selem_id_malloc(&sid)) 2732 throw new AlsaException("master channel open", err); 2733 scope(failure) 2734 snd_mixer_selem_id_free(sid); 2735 snd_mixer_selem_id_set_index(sid, 0); 2736 snd_mixer_selem_id_set_name(sid, selemName); 2737 selem = snd_mixer_find_selem(handle, sid); 2738 if(selem is null) 2739 throw new AlsaException("find master element", 0); 2740 2741 if(auto err = snd_mixer_selem_get_playback_volume_range(selem, &minVolume, &maxVolume)) 2742 throw new AlsaException("get volume range", err); 2743 2744 version(with_eventloop) { 2745 import arsd.eventloop; 2746 addFileEventListeners(getAlsaFileDescriptors()[0], &eventListener, null, null); 2747 setAlsaElemCallback(&alsaCallback); 2748 } 2749 } else static assert(0); 2750 } 2751 2752 ~this() { 2753 version(ALSA) { 2754 version(with_eventloop) { 2755 import arsd.eventloop; 2756 removeFileEventListeners(getAlsaFileDescriptors()[0]); 2757 } 2758 snd_mixer_selem_id_free(sid); 2759 snd_mixer_close(handle); 2760 } else static assert(0); 2761 } 2762 2763 version(ALSA) 2764 version(with_eventloop) { 2765 static struct MixerEvent {} 2766 nothrow @nogc 2767 extern(C) static int alsaCallback(snd_mixer_elem_t*, uint) { 2768 import arsd.eventloop; 2769 try 2770 send(MixerEvent()); 2771 catch(Exception) 2772 return 1; 2773 2774 return 0; 2775 } 2776 2777 void eventListener(int fd) { 2778 handleAlsaEvents(); 2779 } 2780 } 2781 2782 /// Gets the master channel's mute state 2783 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 2784 @property bool muteMaster() { 2785 version(ALSA) { 2786 int result; 2787 if(auto err = snd_mixer_selem_get_playback_switch(selem, 0, &result)) 2788 throw new AlsaException("get mute state", err); 2789 return result == 0; 2790 } else static assert(0); 2791 } 2792 2793 /// Mutes or unmutes the master channel 2794 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 2795 @property void muteMaster(bool mute) { 2796 version(ALSA) { 2797 if(auto err = snd_mixer_selem_set_playback_switch_all(selem, mute ? 0 : 1)) 2798 throw new AlsaException("set mute state", err); 2799 } else static assert(0); 2800 } 2801 2802 /// returns a percentage, between 0 and 100 (inclusive) 2803 int getMasterVolume() { 2804 version(ALSA) { 2805 auto volume = getMasterVolumeExact(); 2806 return cast(int)(volume * 100 / (maxVolume - minVolume)); 2807 } else static assert(0); 2808 } 2809 2810 /// Gets the exact value returned from the operating system. The range may vary. 2811 int getMasterVolumeExact() { 2812 version(ALSA) { 2813 c_long volume; 2814 snd_mixer_selem_get_playback_volume(selem, 0, &volume); 2815 return cast(int)volume; 2816 } else static assert(0); 2817 } 2818 2819 /// sets a percentage on the volume, so it must be 0 <= volume <= 100 2820 /// Note: this affects shared system state and you should not use it unless the end user wants you to. 2821 void setMasterVolume(int volume) { 2822 version(ALSA) { 2823 assert(volume >= 0 && volume <= 100); 2824 setMasterVolumeExact(cast(int)(volume * (maxVolume - minVolume) / 100)); 2825 } else static assert(0); 2826 } 2827 2828 /// Sets an exact volume. Must be in range of the OS provided min and max. 2829 void setMasterVolumeExact(int volume) { 2830 version(ALSA) { 2831 if(auto err = snd_mixer_selem_set_playback_volume_all(selem, volume)) 2832 throw new AlsaException("set volume", err); 2833 } else static assert(0); 2834 } 2835 2836 version(ALSA) { 2837 /// Gets the ALSA descriptors which you can watch for events 2838 /// on using regular select, poll, epoll, etc. 2839 int[] getAlsaFileDescriptors() { 2840 import core.sys.posix.poll; 2841 pollfd[32] descriptors = void; 2842 int got = snd_mixer_poll_descriptors(handle, descriptors.ptr, descriptors.length); 2843 int[] result; 2844 result.length = got; 2845 foreach(i, desc; descriptors[0 .. got]) 2846 result[i] = desc.fd; 2847 return result; 2848 } 2849 2850 /// When the FD is ready, call this to let ALSA do its thing. 2851 void handleAlsaEvents() { 2852 snd_mixer_handle_events(handle); 2853 } 2854 2855 /// Set a callback for the master volume change events. 2856 void setAlsaElemCallback(snd_mixer_elem_callback_t dg) { 2857 snd_mixer_elem_set_callback(selem, dg); 2858 } 2859 } 2860 } 2861 2862 // **************** 2863 // Midi helpers 2864 // **************** 2865 2866 // FIXME: code the .mid file format, read and write 2867 2868 enum MidiEvent { 2869 NoteOff = 0x08, 2870 NoteOn = 0x09, 2871 NoteAftertouch = 0x0a, 2872 Controller = 0x0b, 2873 ProgramChange = 0x0c, // one param 2874 ChannelAftertouch = 0x0d, // one param 2875 PitchBend = 0x0e, 2876 } 2877 2878 enum MidiNote : ubyte { 2879 middleC = 60, 2880 A = 69, // 440 Hz 2881 As = 70, 2882 B = 71, 2883 C = 72, 2884 Cs = 73, 2885 D = 74, 2886 Ds = 75, 2887 E = 76, 2888 F = 77, 2889 Fs = 78, 2890 G = 79, 2891 Gs = 80, 2892 } 2893 2894 /// Puts a note on at the beginning of the passed slice, advancing it by the amount of the message size. 2895 /// Returns the message slice. 2896 /// 2897 /// See: http://www.midi.org/techspecs/midimessages.php 2898 ubyte[] midiNoteOn(ref ubyte[] where, ubyte channel, byte note, byte velocity) { 2899 where[0] = (MidiEvent.NoteOn << 4) | (channel&0x0f); 2900 where[1] = note; 2901 where[2] = velocity; 2902 auto it = where[0 .. 3]; 2903 where = where[3 .. $]; 2904 return it; 2905 } 2906 2907 /// Note off. 2908 ubyte[] midiNoteOff(ref ubyte[] where, ubyte channel, byte note, byte velocity) { 2909 where[0] = (MidiEvent.NoteOff << 4) | (channel&0x0f); 2910 where[1] = note; 2911 where[2] = velocity; 2912 auto it = where[0 .. 3]; 2913 where = where[3 .. $]; 2914 return it; 2915 } 2916 2917 /// Aftertouch. 2918 ubyte[] midiNoteAftertouch(ref ubyte[] where, ubyte channel, byte note, byte pressure) { 2919 where[0] = (MidiEvent.NoteAftertouch << 4) | (channel&0x0f); 2920 where[1] = note; 2921 where[2] = pressure; 2922 auto it = where[0 .. 3]; 2923 where = where[3 .. $]; 2924 return it; 2925 } 2926 2927 /// Controller. 2928 ubyte[] midiNoteController(ref ubyte[] where, ubyte channel, byte controllerNumber, byte controllerValue) { 2929 where[0] = (MidiEvent.Controller << 4) | (channel&0x0f); 2930 where[1] = controllerNumber; 2931 where[2] = controllerValue; 2932 auto it = where[0 .. 3]; 2933 where = where[3 .. $]; 2934 return it; 2935 } 2936 2937 /// Program change. 2938 ubyte[] midiProgramChange(ref ubyte[] where, ubyte channel, byte program) { 2939 where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f); 2940 where[1] = program; 2941 auto it = where[0 .. 2]; 2942 where = where[2 .. $]; 2943 return it; 2944 } 2945 2946 /// Channel aftertouch. 2947 ubyte[] midiChannelAftertouch(ref ubyte[] where, ubyte channel, byte amount) { 2948 where[0] = (MidiEvent.ProgramChange << 4) | (channel&0x0f); 2949 where[1] = amount; 2950 auto it = where[0 .. 2]; 2951 where = where[2 .. $]; 2952 return it; 2953 } 2954 2955 /// Pitch bend. FIXME doesn't work right 2956 ubyte[] midiNotePitchBend(ref ubyte[] where, ubyte channel, short change) { 2957 /* 2958 first byte is llllll 2959 second byte is mmmmmm 2960 2961 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. 2962 */ 2963 where[0] = (MidiEvent.PitchBend << 4) | (channel&0x0f); 2964 // FIXME 2965 where[1] = 0; 2966 where[2] = 0; 2967 auto it = where[0 .. 3]; 2968 where = where[3 .. $]; 2969 return it; 2970 } 2971 2972 2973 // **************** 2974 // Wav helpers 2975 // **************** 2976 2977 // FIXME: the .wav file format should be here, read and write (at least basics) 2978 // as well as some kind helpers to generate some sounds. 2979 2980 // **************** 2981 // OS specific helper stuff follows 2982 // **************** 2983 2984 private const(char)* toStringz(string s) { 2985 return s.ptr; // FIXME jic 2986 } 2987 2988 version(ALSA) 2989 // Opens the PCM device with default settings: stereo, 16 bit, 44.1 kHz, interleaved R/W. 2990 snd_pcm_t* openAlsaPcm(snd_pcm_stream_t direction, int SampleRate, int channels, string cardName = "default") { 2991 snd_pcm_t* handle; 2992 snd_pcm_hw_params_t* hwParams; 2993 2994 /* Open PCM and initialize hardware */ 2995 2996 // import arsd.core; 2997 // writeln("before"); 2998 if (auto err = snd_pcm_open(&handle, cardName.toStringz, direction, 0)) 2999 throw new AlsaException("open device", err); 3000 // writeln("after"); 3001 scope(failure) 3002 snd_pcm_close(handle); 3003 3004 3005 if (auto err = snd_pcm_hw_params_malloc(&hwParams)) 3006 throw new AlsaException("params malloc", err); 3007 scope(exit) 3008 snd_pcm_hw_params_free(hwParams); 3009 3010 if (auto err = snd_pcm_hw_params_any(handle, hwParams)) 3011 // can actually survive a failure here, we will just move forward 3012 {} // throw new AlsaException("params init", err); 3013 3014 if (auto err = snd_pcm_hw_params_set_access(handle, hwParams, snd_pcm_access_t.SND_PCM_ACCESS_RW_INTERLEAVED)) 3015 throw new AlsaException("params access", err); 3016 3017 if (auto err = snd_pcm_hw_params_set_format(handle, hwParams, snd_pcm_format.SND_PCM_FORMAT_S16_LE)) 3018 throw new AlsaException("params format", err); 3019 3020 uint rate = SampleRate; 3021 int dir = 0; 3022 if (auto err = snd_pcm_hw_params_set_rate_near(handle, hwParams, &rate, &dir)) 3023 throw new AlsaException("params rate", err); 3024 3025 assert(rate == SampleRate); // cheap me 3026 3027 if (auto err = snd_pcm_hw_params_set_channels(handle, hwParams, channels)) 3028 throw new AlsaException("params channels", err); 3029 3030 uint periods = 4; 3031 { 3032 auto err = snd_pcm_hw_params_set_periods_near(handle, hwParams, &periods, 0); 3033 if(err < 0) 3034 throw new AlsaException("periods", err); 3035 3036 // import std.stdio; writeln(periods); 3037 snd_pcm_uframes_t sz = (BUFFER_SIZE_FRAMES * periods); 3038 err = snd_pcm_hw_params_set_buffer_size_near(handle, hwParams, &sz); 3039 if(err < 0) 3040 throw new AlsaException("buffer size", err); 3041 } 3042 3043 if (auto err = snd_pcm_hw_params(handle, hwParams)) 3044 throw new AlsaException("params install", err); 3045 3046 /* Setting up the callbacks */ 3047 3048 snd_pcm_sw_params_t* swparams; 3049 if(auto err = snd_pcm_sw_params_malloc(&swparams)) 3050 throw new AlsaException("sw malloc", err); 3051 scope(exit) 3052 snd_pcm_sw_params_free(swparams); 3053 if(auto err = snd_pcm_sw_params_current(handle, swparams)) 3054 throw new AlsaException("sw set", err); 3055 if(auto err = snd_pcm_sw_params_set_avail_min(handle, swparams, BUFFER_SIZE_FRAMES)) 3056 throw new AlsaException("sw min", err); 3057 if(auto err = snd_pcm_sw_params_set_start_threshold(handle, swparams, 0)) 3058 throw new AlsaException("sw threshold", err); 3059 if(auto err = snd_pcm_sw_params(handle, swparams)) 3060 throw new AlsaException("sw params", err); 3061 3062 /* finish setup */ 3063 3064 // writeln("prepare"); 3065 if (auto err = snd_pcm_prepare(handle)) 3066 throw new AlsaException("prepare", err); 3067 // writeln("done"); 3068 3069 assert(handle !is null); 3070 return handle; 3071 } 3072 3073 version(ALSA) 3074 class AlsaException : AudioException { 3075 this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3076 auto msg = snd_strerror(error); 3077 import core.stdc.string; 3078 super(cast(string) (message ~ ": " ~ msg[0 .. strlen(msg)]), file, line, next); 3079 } 3080 } 3081 3082 version(WinMM) 3083 class WinMMException : AudioException { 3084 this(string message, int error, string file = __FILE__, size_t line = __LINE__, Throwable next = null) { 3085 // FIXME: format the error 3086 // midiOutGetErrorText, etc. 3087 super(message, file, line, next); 3088 } 3089 } 3090 3091 // **************** 3092 // Bindings follow 3093 // **************** 3094 3095 version(ALSA) { 3096 extern(C): 3097 @nogc nothrow: 3098 pragma(lib, "asound"); 3099 private import core.sys.posix.poll; 3100 3101 const(char)* snd_strerror(int); 3102 3103 // pcm 3104 enum snd_pcm_stream_t { 3105 SND_PCM_STREAM_PLAYBACK, 3106 SND_PCM_STREAM_CAPTURE 3107 } 3108 3109 enum snd_pcm_access_t { 3110 /** mmap access with simple interleaved channels */ 3111 SND_PCM_ACCESS_MMAP_INTERLEAVED = 0, 3112 /** mmap access with simple non interleaved channels */ 3113 SND_PCM_ACCESS_MMAP_NONINTERLEAVED, 3114 /** mmap access with complex placement */ 3115 SND_PCM_ACCESS_MMAP_COMPLEX, 3116 /** snd_pcm_readi/snd_pcm_writei access */ 3117 SND_PCM_ACCESS_RW_INTERLEAVED, 3118 /** snd_pcm_readn/snd_pcm_writen access */ 3119 SND_PCM_ACCESS_RW_NONINTERLEAVED, 3120 SND_PCM_ACCESS_LAST = SND_PCM_ACCESS_RW_NONINTERLEAVED 3121 } 3122 3123 enum snd_pcm_format { 3124 /** Unknown */ 3125 SND_PCM_FORMAT_UNKNOWN = -1, 3126 /** Signed 8 bit */ 3127 SND_PCM_FORMAT_S8 = 0, 3128 /** Unsigned 8 bit */ 3129 SND_PCM_FORMAT_U8, 3130 /** Signed 16 bit Little Endian */ 3131 SND_PCM_FORMAT_S16_LE, 3132 /** Signed 16 bit Big Endian */ 3133 SND_PCM_FORMAT_S16_BE, 3134 /** Unsigned 16 bit Little Endian */ 3135 SND_PCM_FORMAT_U16_LE, 3136 /** Unsigned 16 bit Big Endian */ 3137 SND_PCM_FORMAT_U16_BE, 3138 /** Signed 24 bit Little Endian using low three bytes in 32-bit word */ 3139 SND_PCM_FORMAT_S24_LE, 3140 /** Signed 24 bit Big Endian using low three bytes in 32-bit word */ 3141 SND_PCM_FORMAT_S24_BE, 3142 /** Unsigned 24 bit Little Endian using low three bytes in 32-bit word */ 3143 SND_PCM_FORMAT_U24_LE, 3144 /** Unsigned 24 bit Big Endian using low three bytes in 32-bit word */ 3145 SND_PCM_FORMAT_U24_BE, 3146 /** Signed 32 bit Little Endian */ 3147 SND_PCM_FORMAT_S32_LE, 3148 /** Signed 32 bit Big Endian */ 3149 SND_PCM_FORMAT_S32_BE, 3150 /** Unsigned 32 bit Little Endian */ 3151 SND_PCM_FORMAT_U32_LE, 3152 /** Unsigned 32 bit Big Endian */ 3153 SND_PCM_FORMAT_U32_BE, 3154 /** Float 32 bit Little Endian, Range -1.0 to 1.0 */ 3155 SND_PCM_FORMAT_FLOAT_LE, 3156 /** Float 32 bit Big Endian, Range -1.0 to 1.0 */ 3157 SND_PCM_FORMAT_FLOAT_BE, 3158 /** Float 64 bit Little Endian, Range -1.0 to 1.0 */ 3159 SND_PCM_FORMAT_FLOAT64_LE, 3160 /** Float 64 bit Big Endian, Range -1.0 to 1.0 */ 3161 SND_PCM_FORMAT_FLOAT64_BE, 3162 /** IEC-958 Little Endian */ 3163 SND_PCM_FORMAT_IEC958_SUBFRAME_LE, 3164 /** IEC-958 Big Endian */ 3165 SND_PCM_FORMAT_IEC958_SUBFRAME_BE, 3166 /** Mu-Law */ 3167 SND_PCM_FORMAT_MU_LAW, 3168 /** A-Law */ 3169 SND_PCM_FORMAT_A_LAW, 3170 /** Ima-ADPCM */ 3171 SND_PCM_FORMAT_IMA_ADPCM, 3172 /** MPEG */ 3173 SND_PCM_FORMAT_MPEG, 3174 /** GSM */ 3175 SND_PCM_FORMAT_GSM, 3176 /** Special */ 3177 SND_PCM_FORMAT_SPECIAL = 31, 3178 /** Signed 24bit Little Endian in 3bytes format */ 3179 SND_PCM_FORMAT_S24_3LE = 32, 3180 /** Signed 24bit Big Endian in 3bytes format */ 3181 SND_PCM_FORMAT_S24_3BE, 3182 /** Unsigned 24bit Little Endian in 3bytes format */ 3183 SND_PCM_FORMAT_U24_3LE, 3184 /** Unsigned 24bit Big Endian in 3bytes format */ 3185 SND_PCM_FORMAT_U24_3BE, 3186 /** Signed 20bit Little Endian in 3bytes format */ 3187 SND_PCM_FORMAT_S20_3LE, 3188 /** Signed 20bit Big Endian in 3bytes format */ 3189 SND_PCM_FORMAT_S20_3BE, 3190 /** Unsigned 20bit Little Endian in 3bytes format */ 3191 SND_PCM_FORMAT_U20_3LE, 3192 /** Unsigned 20bit Big Endian in 3bytes format */ 3193 SND_PCM_FORMAT_U20_3BE, 3194 /** Signed 18bit Little Endian in 3bytes format */ 3195 SND_PCM_FORMAT_S18_3LE, 3196 /** Signed 18bit Big Endian in 3bytes format */ 3197 SND_PCM_FORMAT_S18_3BE, 3198 /** Unsigned 18bit Little Endian in 3bytes format */ 3199 SND_PCM_FORMAT_U18_3LE, 3200 /** Unsigned 18bit Big Endian in 3bytes format */ 3201 SND_PCM_FORMAT_U18_3BE, 3202 /* G.723 (ADPCM) 24 kbit/s, 8 samples in 3 bytes */ 3203 SND_PCM_FORMAT_G723_24, 3204 /* G.723 (ADPCM) 24 kbit/s, 1 sample in 1 byte */ 3205 SND_PCM_FORMAT_G723_24_1B, 3206 /* G.723 (ADPCM) 40 kbit/s, 8 samples in 3 bytes */ 3207 SND_PCM_FORMAT_G723_40, 3208 /* G.723 (ADPCM) 40 kbit/s, 1 sample in 1 byte */ 3209 SND_PCM_FORMAT_G723_40_1B, 3210 /* Direct Stream Digital (DSD) in 1-byte samples (x8) */ 3211 SND_PCM_FORMAT_DSD_U8, 3212 /* Direct Stream Digital (DSD) in 2-byte samples (x16) */ 3213 SND_PCM_FORMAT_DSD_U16_LE, 3214 SND_PCM_FORMAT_LAST = SND_PCM_FORMAT_DSD_U16_LE, 3215 3216 // I snipped a bunch of endian-specific ones! 3217 } 3218 3219 struct snd_pcm_t {} 3220 struct snd_pcm_hw_params_t {} 3221 struct snd_pcm_sw_params_t {} 3222 3223 int snd_pcm_open(snd_pcm_t**, const char*, snd_pcm_stream_t, int); 3224 int snd_pcm_close(snd_pcm_t*); 3225 int snd_pcm_pause(snd_pcm_t*, int); 3226 int snd_pcm_prepare(snd_pcm_t*); 3227 int snd_pcm_hw_params(snd_pcm_t*, snd_pcm_hw_params_t*); 3228 int snd_pcm_hw_params_set_periods(snd_pcm_t*, snd_pcm_hw_params_t*, uint, int); 3229 int snd_pcm_hw_params_set_periods_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int); 3230 int snd_pcm_hw_params_set_buffer_size(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t); 3231 int snd_pcm_hw_params_set_buffer_size_near(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_uframes_t*); 3232 int snd_pcm_hw_params_set_channels(snd_pcm_t*, snd_pcm_hw_params_t*, uint); 3233 int snd_pcm_hw_params_malloc(snd_pcm_hw_params_t**); 3234 void snd_pcm_hw_params_free(snd_pcm_hw_params_t*); 3235 int snd_pcm_hw_params_any(snd_pcm_t*, snd_pcm_hw_params_t*); 3236 int snd_pcm_hw_params_set_access(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_access_t); 3237 int snd_pcm_hw_params_set_format(snd_pcm_t*, snd_pcm_hw_params_t*, snd_pcm_format); 3238 int snd_pcm_hw_params_set_rate_near(snd_pcm_t*, snd_pcm_hw_params_t*, uint*, int*); 3239 3240 int snd_pcm_sw_params_malloc(snd_pcm_sw_params_t**); 3241 void snd_pcm_sw_params_free(snd_pcm_sw_params_t*); 3242 3243 int snd_pcm_sw_params_current(snd_pcm_t *pcm, snd_pcm_sw_params_t *params); 3244 int snd_pcm_sw_params(snd_pcm_t *pcm, snd_pcm_sw_params_t *params); 3245 int snd_pcm_sw_params_set_avail_min(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 3246 int snd_pcm_sw_params_set_start_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 3247 int snd_pcm_sw_params_set_stop_threshold(snd_pcm_t*, snd_pcm_sw_params_t*, snd_pcm_uframes_t); 3248 3249 alias snd_pcm_sframes_t = c_long; 3250 alias snd_pcm_uframes_t = c_ulong; 3251 snd_pcm_sframes_t snd_pcm_writei(snd_pcm_t*, const void*, snd_pcm_uframes_t size); 3252 snd_pcm_sframes_t snd_pcm_readi(snd_pcm_t*, void*, snd_pcm_uframes_t size); 3253 3254 int snd_pcm_wait(snd_pcm_t *pcm, int timeout); 3255 snd_pcm_sframes_t snd_pcm_avail(snd_pcm_t *pcm); 3256 snd_pcm_sframes_t snd_pcm_avail_update(snd_pcm_t *pcm); 3257 3258 int snd_pcm_recover (snd_pcm_t* pcm, int err, int silent); 3259 3260 alias snd_lib_error_handler_t = void function (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...); 3261 int snd_lib_error_set_handler (snd_lib_error_handler_t handler); 3262 3263 import core.stdc.stdarg; 3264 private void alsa_message_silencer (const(char)* file, int line, const(char)* function_, int err, const(char)* fmt, ...) @system {} 3265 //k8: ALSAlib loves to trash stderr; shut it up 3266 void silence_alsa_messages () { snd_lib_error_set_handler(&alsa_message_silencer); } 3267 extern(D) shared static this () { silence_alsa_messages(); } 3268 3269 // raw midi 3270 3271 static if(is(size_t == uint)) 3272 alias ssize_t = int; 3273 else 3274 alias ssize_t = long; 3275 3276 3277 struct snd_rawmidi_t {} 3278 int snd_rawmidi_open(snd_rawmidi_t**, snd_rawmidi_t**, const char*, int); 3279 int snd_rawmidi_close(snd_rawmidi_t*); 3280 int snd_rawmidi_drain(snd_rawmidi_t*); 3281 ssize_t snd_rawmidi_write(snd_rawmidi_t*, const void*, size_t); 3282 ssize_t snd_rawmidi_read(snd_rawmidi_t*, void*, size_t); 3283 3284 // mixer 3285 3286 struct snd_mixer_t {} 3287 struct snd_mixer_elem_t {} 3288 struct snd_mixer_selem_id_t {} 3289 3290 alias snd_mixer_elem_callback_t = int function(snd_mixer_elem_t*, uint); 3291 3292 int snd_mixer_open(snd_mixer_t**, int mode); 3293 int snd_mixer_close(snd_mixer_t*); 3294 int snd_mixer_attach(snd_mixer_t*, const char*); 3295 int snd_mixer_load(snd_mixer_t*); 3296 3297 // FIXME: those aren't actually void* 3298 int snd_mixer_selem_register(snd_mixer_t*, void*, void*); 3299 int snd_mixer_selem_id_malloc(snd_mixer_selem_id_t**); 3300 void snd_mixer_selem_id_free(snd_mixer_selem_id_t*); 3301 void snd_mixer_selem_id_set_index(snd_mixer_selem_id_t*, uint); 3302 void snd_mixer_selem_id_set_name(snd_mixer_selem_id_t*, const char*); 3303 snd_mixer_elem_t* snd_mixer_find_selem(snd_mixer_t*, const scope snd_mixer_selem_id_t*); 3304 3305 // FIXME: the int should be an enum for channel identifier 3306 int snd_mixer_selem_get_playback_volume(snd_mixer_elem_t*, int, c_long*); 3307 3308 int snd_mixer_selem_get_playback_volume_range(snd_mixer_elem_t*, c_long*, c_long*); 3309 3310 int snd_mixer_selem_set_playback_volume_all(snd_mixer_elem_t*, c_long); 3311 3312 void snd_mixer_elem_set_callback(snd_mixer_elem_t*, snd_mixer_elem_callback_t); 3313 int snd_mixer_poll_descriptors(snd_mixer_t*, pollfd*, uint space); 3314 3315 int snd_mixer_handle_events(snd_mixer_t*); 3316 3317 // FIXME: the first int should be an enum for channel identifier 3318 int snd_mixer_selem_get_playback_switch(snd_mixer_elem_t*, int, int* value); 3319 int snd_mixer_selem_set_playback_switch_all(snd_mixer_elem_t*, int); 3320 } 3321 3322 version(WinMM) { 3323 extern(Windows): 3324 @nogc nothrow: 3325 pragma(lib, "winmm"); 3326 import core.sys.windows.windows; 3327 3328 /* 3329 Windows functions include: 3330 http://msdn.microsoft.com/en-us/library/ms713762%28VS.85%29.aspx 3331 http://msdn.microsoft.com/en-us/library/ms713504%28v=vs.85%29.aspx 3332 http://msdn.microsoft.com/en-us/library/windows/desktop/dd798480%28v=vs.85%29.aspx# 3333 http://msdn.microsoft.com/en-US/subscriptions/ms712109.aspx 3334 */ 3335 3336 // pcm 3337 3338 // midi 3339 /+ 3340 alias HMIDIOUT = HANDLE; 3341 alias MMRESULT = UINT; 3342 3343 MMRESULT midiOutOpen(HMIDIOUT*, UINT, DWORD, DWORD, DWORD); 3344 MMRESULT midiOutClose(HMIDIOUT); 3345 MMRESULT midiOutReset(HMIDIOUT); 3346 MMRESULT midiOutShortMsg(HMIDIOUT, DWORD); 3347 3348 alias HWAVEOUT = HANDLE; 3349 3350 struct WAVEFORMATEX { 3351 WORD wFormatTag; 3352 WORD nChannels; 3353 DWORD nSamplesPerSec; 3354 DWORD nAvgBytesPerSec; 3355 WORD nBlockAlign; 3356 WORD wBitsPerSample; 3357 WORD cbSize; 3358 } 3359 3360 struct WAVEHDR { 3361 void* lpData; 3362 DWORD dwBufferLength; 3363 DWORD dwBytesRecorded; 3364 DWORD dwUser; 3365 DWORD dwFlags; 3366 DWORD dwLoops; 3367 WAVEHDR *lpNext; 3368 DWORD reserved; 3369 } 3370 3371 enum UINT WAVE_MAPPER= -1; 3372 3373 MMRESULT waveOutOpen(HWAVEOUT*, UINT_PTR, WAVEFORMATEX*, void* callback, void*, DWORD); 3374 MMRESULT waveOutClose(HWAVEOUT); 3375 MMRESULT waveOutPrepareHeader(HWAVEOUT, WAVEHDR*, UINT); 3376 MMRESULT waveOutUnprepareHeader(HWAVEOUT, WAVEHDR*, UINT); 3377 MMRESULT waveOutWrite(HWAVEOUT, WAVEHDR*, UINT); 3378 3379 MMRESULT waveOutGetVolume(HWAVEOUT, PDWORD); 3380 MMRESULT waveOutSetVolume(HWAVEOUT, DWORD); 3381 3382 enum CALLBACK_TYPEMASK = 0x70000; 3383 enum CALLBACK_NULL = 0; 3384 enum CALLBACK_WINDOW = 0x10000; 3385 enum CALLBACK_TASK = 0x20000; 3386 enum CALLBACK_FUNCTION = 0x30000; 3387 enum CALLBACK_THREAD = CALLBACK_TASK; 3388 enum CALLBACK_EVENT = 0x50000; 3389 3390 enum WAVE_FORMAT_PCM = 1; 3391 3392 enum WHDR_PREPARED = 2; 3393 enum WHDR_BEGINLOOP = 4; 3394 enum WHDR_ENDLOOP = 8; 3395 enum WHDR_INQUEUE = 16; 3396 3397 enum WinMMMessage : UINT { 3398 MM_JOY1MOVE = 0x3A0, 3399 MM_JOY2MOVE, 3400 MM_JOY1ZMOVE, 3401 MM_JOY2ZMOVE, // = 0x3A3 3402 MM_JOY1BUTTONDOWN = 0x3B5, 3403 MM_JOY2BUTTONDOWN, 3404 MM_JOY1BUTTONUP, 3405 MM_JOY2BUTTONUP, 3406 MM_MCINOTIFY, // = 0x3B9 3407 MM_WOM_OPEN = 0x3BB, 3408 MM_WOM_CLOSE, 3409 MM_WOM_DONE, 3410 MM_WIM_OPEN, 3411 MM_WIM_CLOSE, 3412 MM_WIM_DATA, 3413 MM_MIM_OPEN, 3414 MM_MIM_CLOSE, 3415 MM_MIM_DATA, 3416 MM_MIM_LONGDATA, 3417 MM_MIM_ERROR, 3418 MM_MIM_LONGERROR, 3419 MM_MOM_OPEN, 3420 MM_MOM_CLOSE, 3421 MM_MOM_DONE, // = 0x3C9 3422 MM_DRVM_OPEN = 0x3D0, 3423 MM_DRVM_CLOSE, 3424 MM_DRVM_DATA, 3425 MM_DRVM_ERROR, 3426 MM_STREAM_OPEN, 3427 MM_STREAM_CLOSE, 3428 MM_STREAM_DONE, 3429 MM_STREAM_ERROR, // = 0x3D7 3430 MM_MOM_POSITIONCB = 0x3CA, 3431 MM_MCISIGNAL, 3432 MM_MIM_MOREDATA, // = 0x3CC 3433 MM_MIXM_LINE_CHANGE = 0x3D0, 3434 MM_MIXM_CONTROL_CHANGE = 0x3D1 3435 } 3436 3437 3438 enum WOM_OPEN = WinMMMessage.MM_WOM_OPEN; 3439 enum WOM_CLOSE = WinMMMessage.MM_WOM_CLOSE; 3440 enum WOM_DONE = WinMMMessage.MM_WOM_DONE; 3441 enum WIM_OPEN = WinMMMessage.MM_WIM_OPEN; 3442 enum WIM_CLOSE = WinMMMessage.MM_WIM_CLOSE; 3443 enum WIM_DATA = WinMMMessage.MM_WIM_DATA; 3444 3445 3446 uint mciSendStringA(const scope char*,char*,uint,void*); 3447 3448 +/ 3449 } 3450 3451 version(with_resampler) { 3452 /* Copyright (C) 2007-2008 Jean-Marc Valin 3453 * Copyright (C) 2008 Thorvald Natvig 3454 * D port by Ketmar // Invisible Vector 3455 * 3456 * Arbitrary resampling code 3457 * 3458 * Redistribution and use in source and binary forms, with or without 3459 * modification, are permitted provided that the following conditions are 3460 * met: 3461 * 3462 * 1. Redistributions of source code must retain the above copyright notice, 3463 * this list of conditions and the following disclaimer. 3464 * 3465 * 2. Redistributions in binary form must reproduce the above copyright 3466 * notice, this list of conditions and the following disclaimer in the 3467 * documentation and/or other materials provided with the distribution. 3468 * 3469 * 3. The name of the author may not be used to endorse or promote products 3470 * derived from this software without specific prior written permission. 3471 * 3472 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 3473 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 3474 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 3475 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 3476 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 3477 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 3478 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 3479 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 3480 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN 3481 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 3482 * POSSIBILITY OF SUCH DAMAGE. 3483 */ 3484 3485 /* A-a-a-and now... D port is covered by the following license! 3486 * 3487 * This program is free software: you can redistribute it and/or modify 3488 * it under the terms of the GNU General Public License as published by 3489 * the Free Software Foundation, either version 3 of the License, or 3490 * (at your option) any later version. 3491 * 3492 * This program is distributed in the hope that it will be useful, 3493 * but WITHOUT ANY WARRANTY; without even the implied warranty of 3494 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 3495 * GNU General Public License for more details. 3496 * 3497 * You should have received a copy of the GNU General Public License 3498 * along with this program. If not, see <http://www.gnu.org/licenses/>. 3499 */ 3500 //module iv.follin.resampler /*is aliced*/; 3501 //import iv.alice; 3502 3503 /* 3504 The design goals of this code are: 3505 - Very fast algorithm 3506 - SIMD-friendly algorithm 3507 - Low memory requirement 3508 - Good *perceptual* quality (and not best SNR) 3509 3510 Warning: This resampler is relatively new. Although I think I got rid of 3511 all the major bugs and I don't expect the API to change anymore, there 3512 may be something I've missed. So use with caution. 3513 3514 This algorithm is based on this original resampling algorithm: 3515 Smith, Julius O. Digital Audio Resampling Home Page 3516 Center for Computer Research in Music and Acoustics (CCRMA), 3517 Stanford University, 2007. 3518 Web published at http://www-ccrma.stanford.edu/~jos/resample/. 3519 3520 There is one main difference, though. This resampler uses cubic 3521 interpolation instead of linear interpolation in the above paper. This 3522 makes the table much smaller and makes it possible to compute that table 3523 on a per-stream basis. In turn, being able to tweak the table for each 3524 stream makes it possible to both reduce complexity on simple ratios 3525 (e.g. 2/3), and get rid of the rounding operations in the inner loop. 3526 The latter both reduces CPU time and makes the algorithm more SIMD-friendly. 3527 */ 3528 version = sincresample_use_full_table; 3529 version(X86) { 3530 version(sincresample_disable_sse) { 3531 } else { 3532 version(D_PIC) {} else version = sincresample_use_sse; 3533 } 3534 } 3535 3536 3537 // ////////////////////////////////////////////////////////////////////////// // 3538 public struct SpeexResampler { 3539 public: 3540 alias Quality = int; 3541 enum : uint { 3542 Fastest = 0, 3543 Voip = 3, 3544 Default = 4, 3545 Desktop = 5, 3546 Music = 8, 3547 Best = 10, 3548 } 3549 3550 enum Error { 3551 OK = 0, 3552 NoMemory, 3553 BadState, 3554 BadArgument, 3555 BadData, 3556 } 3557 3558 private: 3559 nothrow @trusted @nogc: 3560 alias ResamplerFn = int function (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen); 3561 3562 private: 3563 uint inRate; 3564 uint outRate; 3565 uint numRate; // from 3566 uint denRate; // to 3567 3568 Quality srQuality; 3569 uint chanCount; 3570 uint filterLen; 3571 uint memAllocSize; 3572 uint bufferSize; 3573 int intAdvance; 3574 int fracAdvance; 3575 float cutoff; 3576 uint oversample; 3577 bool started; 3578 3579 // these are per-channel 3580 int[64] lastSample; 3581 uint[64] sampFracNum; 3582 uint[64] magicSamples; 3583 3584 float* mem; 3585 uint realMemLen; // how much memory really allocated 3586 float* sincTable; 3587 uint sincTableLen; 3588 uint realSincTableLen; // how much memory really allocated 3589 ResamplerFn resampler; 3590 3591 int inStride; 3592 int outStride; 3593 3594 public: 3595 static string errorStr (int err) { 3596 switch (err) with (Error) { 3597 case OK: return "success"; 3598 case NoMemory: return "memory allocation failed"; 3599 case BadState: return "bad resampler state"; 3600 case BadArgument: return "invalid argument"; 3601 case BadData: return "bad data passed"; 3602 default: 3603 } 3604 return "unknown error"; 3605 } 3606 3607 public: 3608 @disable this (this); 3609 ~this () { deinit(); } 3610 3611 @property bool inited () const pure { return (resampler !is null); } 3612 3613 void deinit () { 3614 import core.stdc.stdlib : free; 3615 if (mem !is null) { free(mem); mem = null; } 3616 if (sincTable !is null) { free(sincTable); sincTable = null; } 3617 /* 3618 memAllocSize = realMemLen = 0; 3619 sincTableLen = realSincTableLen = 0; 3620 resampler = null; 3621 started = false; 3622 */ 3623 inRate = outRate = numRate = denRate = 0; 3624 srQuality = cast(Quality)666; 3625 chanCount = 0; 3626 filterLen = 0; 3627 memAllocSize = 0; 3628 bufferSize = 0; 3629 intAdvance = 0; 3630 fracAdvance = 0; 3631 cutoff = 0; 3632 oversample = 0; 3633 started = 0; 3634 3635 mem = null; 3636 realMemLen = 0; // how much memory really allocated 3637 sincTable = null; 3638 sincTableLen = 0; 3639 realSincTableLen = 0; // how much memory really allocated 3640 resampler = null; 3641 3642 inStride = outStride = 0; 3643 } 3644 3645 /** Create a new resampler with integer input and output rates. 3646 * 3647 * Params: 3648 * chans = Number of channels to be processed 3649 * inRate = Input sampling rate (integer number of Hz). 3650 * outRate = Output sampling rate (integer number of Hz). 3651 * aquality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality. 3652 * 3653 * Returns: 3654 * 0 or error code 3655 */ 3656 Error setup (uint chans, uint ainRate, uint aoutRate, Quality aquality/*, usize line=__LINE__*/) { 3657 //{ import core.stdc.stdio; printf("init: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); } 3658 import core.stdc.stdlib : malloc, free; 3659 3660 deinit(); 3661 if (aquality < 0) aquality = 0; 3662 if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best; 3663 if (chans < 1 || chans > 16) return Error.BadArgument; 3664 3665 started = false; 3666 inRate = 0; 3667 outRate = 0; 3668 numRate = 0; 3669 denRate = 0; 3670 srQuality = cast(Quality)666; // it's ok 3671 sincTableLen = 0; 3672 memAllocSize = 0; 3673 filterLen = 0; 3674 mem = null; 3675 resampler = null; 3676 3677 cutoff = 1.0f; 3678 chanCount = chans; 3679 inStride = 1; 3680 outStride = 1; 3681 3682 bufferSize = 160; 3683 3684 // per channel data 3685 lastSample[] = 0; 3686 magicSamples[] = 0; 3687 sampFracNum[] = 0; 3688 3689 setQuality(aquality); 3690 setRate(ainRate, aoutRate); 3691 3692 if (auto filterErr = updateFilter()) { deinit(); return filterErr; } 3693 skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros 3694 3695 return Error.OK; 3696 } 3697 3698 /** Set (change) the input/output sampling rates (integer value). 3699 * 3700 * Params: 3701 * ainRate = Input sampling rate (integer number of Hz). 3702 * aoutRate = Output sampling rate (integer number of Hz). 3703 * 3704 * Returns: 3705 * 0 or error code 3706 */ 3707 Error setRate (uint ainRate, uint aoutRate/*, usize line=__LINE__*/) { 3708 //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ainRate, aoutRate, cast(uint)line); } 3709 if (inRate == ainRate && outRate == aoutRate) return Error.OK; 3710 //{ import core.stdc.stdio; printf("changing rate: %u -> %u at %u\n", ratioNum, ratioDen, cast(uint)line); } 3711 3712 uint oldDen = denRate; 3713 inRate = ainRate; 3714 outRate = aoutRate; 3715 auto div = gcd(ainRate, aoutRate); 3716 numRate = ainRate/div; 3717 denRate = aoutRate/div; 3718 3719 if (oldDen > 0) { 3720 foreach (ref v; sampFracNum.ptr[0..chanCount]) { 3721 v = v*denRate/oldDen; 3722 // safety net 3723 if (v >= denRate) v = denRate-1; 3724 } 3725 } 3726 3727 return (inited ? updateFilter() : Error.OK); 3728 } 3729 3730 /** Get the current input/output sampling rates (integer value). 3731 * 3732 * Params: 3733 * ainRate = Input sampling rate (integer number of Hz) copied. 3734 * aoutRate = Output sampling rate (integer number of Hz) copied. 3735 */ 3736 void getRate (out uint ainRate, out uint aoutRate) { 3737 ainRate = inRate; 3738 aoutRate = outRate; 3739 } 3740 3741 @property uint getInRate () { return inRate; } 3742 @property uint getOutRate () { return outRate; } 3743 3744 @property uint getChans () { return chanCount; } 3745 3746 /** Get the current resampling ratio. This will be reduced to the least common denominator. 3747 * 3748 * Params: 3749 * ratioNum = Numerator of the sampling rate ratio copied 3750 * ratioDen = Denominator of the sampling rate ratio copied 3751 */ 3752 void getRatio (out uint ratioNum, out uint ratioDen) { 3753 ratioNum = numRate; 3754 ratioDen = denRate; 3755 } 3756 3757 /** Set (change) the conversion quality. 3758 * 3759 * Params: 3760 * quality = Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality. 3761 * 3762 * Returns: 3763 * 0 or error code 3764 */ 3765 Error setQuality (Quality aquality) { 3766 if (aquality < 0) aquality = 0; 3767 if (aquality > SpeexResampler.Best) aquality = SpeexResampler.Best; 3768 if (srQuality == aquality) return Error.OK; 3769 srQuality = aquality; 3770 return (inited ? updateFilter() : Error.OK); 3771 } 3772 3773 /** Get the conversion quality. 3774 * 3775 * Returns: 3776 * Resampling quality between 0 and 10, where 0 has poor quality and 10 has very high quality. 3777 */ 3778 int getQuality () { return srQuality; } 3779 3780 /** Get the latency introduced by the resampler measured in input samples. 3781 * 3782 * Returns: 3783 * Input latency; 3784 */ 3785 int inputLatency () { return filterLen/2; } 3786 3787 /** Get the latency introduced by the resampler measured in output samples. 3788 * 3789 * Returns: 3790 * Output latency. 3791 */ 3792 int outputLatency () { return ((filterLen/2)*denRate+(numRate>>1))/numRate; } 3793 3794 /* Make sure that the first samples to go out of the resamplers don't have 3795 * leading zeros. This is only useful before starting to use a newly created 3796 * resampler. It is recommended to use that when resampling an audio file, as 3797 * it will generate a file with the same length. For real-time processing, 3798 * it is probably easier not to use this call (so that the output duration 3799 * is the same for the first frame). 3800 * 3801 * Setup/reset sequence will automatically call this, so it is private. 3802 */ 3803 private void skipZeros () { foreach (immutable i; 0..chanCount) lastSample.ptr[i] = filterLen/2; } 3804 3805 static struct Data { 3806 const(float)[] dataIn; 3807 float[] dataOut; 3808 uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count) 3809 uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count) 3810 } 3811 3812 /** Resample (an interleaved) float array. The input and output buffers must *not* overlap. 3813 * `data.dataIn` can be empty, but `data.dataOut` can't. 3814 * Function will return number of consumed samples (*not* *frames*!) in `data.inputSamplesUsed`, 3815 * and number of produced samples in `data.outputSamplesUsed`. 3816 * You should provide enough samples for all channels, and all channels will be processed. 3817 * 3818 * Params: 3819 * data = input and output buffers, number of frames consumed and produced 3820 * 3821 * Returns: 3822 * 0 or error code 3823 */ 3824 Error process(string mode="interleaved") (ref Data data) { 3825 static assert(mode == "interleaved" || mode == "sequential"); 3826 3827 data.inputSamplesUsed = data.outputSamplesUsed = 0; 3828 if (!inited) return Error.BadState; 3829 3830 if (data.dataIn.length%chanCount || data.dataOut.length < 1 || data.dataOut.length%chanCount) return Error.BadData; 3831 if (data.dataIn.length > uint.max/4 || data.dataOut.length > uint.max/4) return Error.BadData; 3832 3833 static if (mode == "interleaved") { 3834 inStride = outStride = chanCount; 3835 } else { 3836 inStride = outStride = 1; 3837 } 3838 uint iofs = 0, oofs = 0; 3839 immutable uint idclen = cast(uint)(data.dataIn.length/chanCount); 3840 immutable uint odclen = cast(uint)(data.dataOut.length/chanCount); 3841 foreach (immutable i; 0..chanCount) { 3842 data.inputSamplesUsed = idclen; 3843 data.outputSamplesUsed = odclen; 3844 if (data.dataIn.length) { 3845 processOneChannel(i, data.dataIn.ptr+iofs, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed); 3846 } else { 3847 processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr+oofs, &data.outputSamplesUsed); 3848 } 3849 static if (mode == "interleaved") { 3850 ++iofs; 3851 ++oofs; 3852 } else { 3853 iofs += idclen; 3854 oofs += odclen; 3855 } 3856 } 3857 data.inputSamplesUsed *= chanCount; 3858 data.outputSamplesUsed *= chanCount; 3859 return Error.OK; 3860 } 3861 3862 3863 //HACK for libswresample 3864 // return -1 or number of outframes 3865 int swrconvert (float** outbuf, int outframes, const(float)**inbuf, int inframes) { 3866 if (!inited || outframes < 1 || inframes < 0) return -1; 3867 inStride = outStride = 1; 3868 Data data; 3869 foreach (immutable i; 0..chanCount) { 3870 data.dataIn = (inframes ? inbuf[i][0..inframes] : null); 3871 data.dataOut = (outframes ? outbuf[i][0..outframes] : null); 3872 data.inputSamplesUsed = inframes; 3873 data.outputSamplesUsed = outframes; 3874 if (inframes > 0) { 3875 processOneChannel(i, data.dataIn.ptr, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed); 3876 } else { 3877 processOneChannel(i, null, &data.inputSamplesUsed, data.dataOut.ptr, &data.outputSamplesUsed); 3878 } 3879 } 3880 return data.outputSamplesUsed; 3881 } 3882 3883 /// Reset a resampler so a new (unrelated) stream can be processed. 3884 void reset () { 3885 lastSample[] = 0; 3886 magicSamples[] = 0; 3887 sampFracNum[] = 0; 3888 //foreach (immutable i; 0..chanCount*(filterLen-1)) mem[i] = 0; 3889 if (mem !is null) mem[0..chanCount*(filterLen-1)] = 0; 3890 skipZeros(); // make sure that the first samples to go out of the resamplers don't have leading zeros 3891 } 3892 3893 private: 3894 Error processOneChannel (uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen) { 3895 uint ilen = *indataLen; 3896 uint olen = *outdataLen; 3897 float* x = mem+chanIdx*memAllocSize; 3898 immutable int filterOfs = filterLen-1; 3899 immutable uint xlen = memAllocSize-filterOfs; 3900 immutable int istride = inStride; 3901 if (magicSamples.ptr[chanIdx]) olen -= magic(chanIdx, &outdata, olen); 3902 if (!magicSamples.ptr[chanIdx]) { 3903 while (ilen && olen) { 3904 uint ichunk = (ilen > xlen ? xlen : ilen); 3905 uint ochunk = olen; 3906 if (indata !is null) { 3907 //foreach (immutable j; 0..ichunk) x[j+filterOfs] = indata[j*istride]; 3908 if (istride == 1) { 3909 x[filterOfs..filterOfs+ichunk] = indata[0..ichunk]; 3910 } else { 3911 auto sp = indata; 3912 auto dp = x+filterOfs; 3913 foreach (immutable j; 0..ichunk) { *dp++ = *sp; sp += istride; } 3914 } 3915 } else { 3916 //foreach (immutable j; 0..ichunk) x[j+filterOfs] = 0; 3917 x[filterOfs..filterOfs+ichunk] = 0; 3918 } 3919 processNative(chanIdx, &ichunk, outdata, &ochunk); 3920 ilen -= ichunk; 3921 olen -= ochunk; 3922 outdata += ochunk*outStride; 3923 if (indata !is null) indata += ichunk*istride; 3924 } 3925 } 3926 *indataLen -= ilen; 3927 *outdataLen -= olen; 3928 return Error.OK; 3929 } 3930 3931 Error processNative (uint chanIdx, uint* indataLen, float* outdata, uint* outdataLen) { 3932 immutable N = filterLen; 3933 int outSample = 0; 3934 float* x = mem+chanIdx*memAllocSize; 3935 uint ilen; 3936 started = true; 3937 // call the right resampler through the function ptr 3938 outSample = resampler(this, chanIdx, x, indataLen, outdata, outdataLen); 3939 if (lastSample.ptr[chanIdx] < cast(int)*indataLen) *indataLen = lastSample.ptr[chanIdx]; 3940 *outdataLen = outSample; 3941 lastSample.ptr[chanIdx] -= *indataLen; 3942 ilen = *indataLen; 3943 foreach (immutable j; 0..N-1) x[j] = x[j+ilen]; 3944 return Error.OK; 3945 } 3946 3947 int magic (uint chanIdx, float **outdata, uint outdataLen) { 3948 uint tempInLen = magicSamples.ptr[chanIdx]; 3949 float* x = mem+chanIdx*memAllocSize; 3950 processNative(chanIdx, &tempInLen, *outdata, &outdataLen); 3951 magicSamples.ptr[chanIdx] -= tempInLen; 3952 // if we couldn't process all "magic" input samples, save the rest for next time 3953 if (magicSamples.ptr[chanIdx]) { 3954 immutable N = filterLen; 3955 foreach (immutable i; 0..magicSamples.ptr[chanIdx]) x[N-1+i] = x[N-1+i+tempInLen]; 3956 } 3957 *outdata += outdataLen*outStride; 3958 return outdataLen; 3959 } 3960 3961 Error updateFilter () { 3962 uint oldFilterLen = filterLen; 3963 uint oldAllocSize = memAllocSize; 3964 bool useDirect; 3965 uint minSincTableLen; 3966 uint minAllocSize; 3967 3968 intAdvance = numRate/denRate; 3969 fracAdvance = numRate%denRate; 3970 oversample = qualityMap.ptr[srQuality].oversample; 3971 filterLen = qualityMap.ptr[srQuality].baseLength; 3972 3973 if (numRate > denRate) { 3974 // down-sampling 3975 cutoff = qualityMap.ptr[srQuality].downsampleBandwidth*denRate/numRate; 3976 // FIXME: divide the numerator and denominator by a certain amount if they're too large 3977 filterLen = filterLen*numRate/denRate; 3978 // round up to make sure we have a multiple of 8 for SSE 3979 filterLen = ((filterLen-1)&(~0x7))+8; 3980 if (2*denRate < numRate) oversample >>= 1; 3981 if (4*denRate < numRate) oversample >>= 1; 3982 if (8*denRate < numRate) oversample >>= 1; 3983 if (16*denRate < numRate) oversample >>= 1; 3984 if (oversample < 1) oversample = 1; 3985 } else { 3986 // up-sampling 3987 cutoff = qualityMap.ptr[srQuality].upsampleBandwidth; 3988 } 3989 3990 // choose the resampling type that requires the least amount of memory 3991 version(sincresample_use_full_table) { 3992 useDirect = true; 3993 if (int.max/float.sizeof/denRate < filterLen) goto fail; 3994 } else { 3995 useDirect = (filterLen*denRate <= filterLen*oversample+8 && int.max/float.sizeof/denRate >= filterLen); 3996 } 3997 3998 if (useDirect) { 3999 minSincTableLen = filterLen*denRate; 4000 } else { 4001 if ((int.max/float.sizeof-8)/oversample < filterLen) goto fail; 4002 minSincTableLen = filterLen*oversample+8; 4003 } 4004 4005 if (sincTableLen < minSincTableLen) { 4006 import core.stdc.stdlib : realloc; 4007 auto nslen = cast(uint)(minSincTableLen*float.sizeof); 4008 if (nslen > realSincTableLen) { 4009 if (nslen < 512*1024) nslen = 512*1024; // inc to 3 mb? 4010 auto x = cast(float*)realloc(sincTable, nslen); 4011 if (!x) goto fail; 4012 sincTable = x; 4013 realSincTableLen = nslen; 4014 } 4015 sincTableLen = minSincTableLen; 4016 } 4017 4018 if (useDirect) { 4019 foreach (int i; 0..denRate) { 4020 foreach (int j; 0..filterLen) { 4021 sincTable[i*filterLen+j] = sinc(cutoff, ((j-cast(int)filterLen/2+1)-(cast(float)i)/denRate), filterLen, qualityMap.ptr[srQuality].windowFunc); 4022 } 4023 } 4024 if (srQuality > 8) { 4025 resampler = &resamplerBasicDirect!double; 4026 } else { 4027 resampler = &resamplerBasicDirect!float; 4028 } 4029 } else { 4030 foreach (immutable int i; -4..cast(int)(oversample*filterLen+4)) { 4031 sincTable[i+4] = sinc(cutoff, (i/cast(float)oversample-filterLen/2), filterLen, qualityMap.ptr[srQuality].windowFunc); 4032 } 4033 if (srQuality > 8) { 4034 resampler = &resamplerBasicInterpolate!double; 4035 } else { 4036 resampler = &resamplerBasicInterpolate!float; 4037 } 4038 } 4039 4040 /* Here's the place where we update the filter memory to take into account 4041 the change in filter length. It's probably the messiest part of the code 4042 due to handling of lots of corner cases. */ 4043 4044 // adding bufferSize to filterLen won't overflow here because filterLen could be multiplied by float.sizeof above 4045 minAllocSize = filterLen-1+bufferSize; 4046 if (minAllocSize > memAllocSize) { 4047 import core.stdc.stdlib : realloc; 4048 if (int.max/float.sizeof/chanCount < minAllocSize) goto fail; 4049 auto nslen = cast(uint)(chanCount*minAllocSize*mem[0].sizeof); 4050 if (nslen > realMemLen) { 4051 if (nslen < 16384) nslen = 16384; 4052 auto x = cast(float*)realloc(mem, nslen); 4053 if (x is null) goto fail; 4054 mem = x; 4055 realMemLen = nslen; 4056 } 4057 memAllocSize = minAllocSize; 4058 } 4059 if (!started) { 4060 //foreach (i=0;i<chanCount*memAllocSize;i++) mem[i] = 0; 4061 mem[0..chanCount*memAllocSize] = 0; 4062 } else if (filterLen > oldFilterLen) { 4063 // increase the filter length 4064 foreach_reverse (uint i; 0..chanCount) { 4065 uint j; 4066 uint olen = oldFilterLen; 4067 { 4068 // try and remove the magic samples as if nothing had happened 4069 //FIXME: this is wrong but for now we need it to avoid going over the array bounds 4070 olen = oldFilterLen+2*magicSamples.ptr[i]; 4071 for (j = oldFilterLen-1+magicSamples.ptr[i]; j--; ) mem[i*memAllocSize+j+magicSamples.ptr[i]] = mem[i*oldAllocSize+j]; 4072 //for (j = 0; j < magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = 0; 4073 mem[i*memAllocSize..i*memAllocSize+magicSamples.ptr[i]] = 0; 4074 magicSamples.ptr[i] = 0; 4075 } 4076 if (filterLen > olen) { 4077 // if the new filter length is still bigger than the "augmented" length 4078 // copy data going backward 4079 for (j = 0; j < olen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = mem[i*memAllocSize+(olen-2-j)]; 4080 // then put zeros for lack of anything better 4081 for (; j < filterLen-1; ++j) mem[i*memAllocSize+(filterLen-2-j)] = 0; 4082 // adjust lastSample 4083 lastSample.ptr[i] += (filterLen-olen)/2; 4084 } else { 4085 // put back some of the magic! 4086 magicSamples.ptr[i] = (olen-filterLen)/2; 4087 for (j = 0; j < filterLen-1+magicSamples.ptr[i]; ++j) mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]]; 4088 } 4089 } 4090 } else if (filterLen < oldFilterLen) { 4091 // reduce filter length, this a bit tricky 4092 // we need to store some of the memory as "magic" samples so they can be used directly as input the next time(s) 4093 foreach (immutable i; 0..chanCount) { 4094 uint j; 4095 uint oldMagic = magicSamples.ptr[i]; 4096 magicSamples.ptr[i] = (oldFilterLen-filterLen)/2; 4097 // we must copy some of the memory that's no longer used 4098 // copy data going backward 4099 for (j = 0; j < filterLen-1+magicSamples.ptr[i]+oldMagic; ++j) { 4100 mem[i*memAllocSize+j] = mem[i*memAllocSize+j+magicSamples.ptr[i]]; 4101 } 4102 magicSamples.ptr[i] += oldMagic; 4103 } 4104 } 4105 return Error.OK; 4106 4107 fail: 4108 resampler = null; 4109 /* mem may still contain consumed input samples for the filter. 4110 Restore filterLen so that filterLen-1 still points to the position after 4111 the last of these samples. */ 4112 filterLen = oldFilterLen; 4113 return Error.NoMemory; 4114 } 4115 } 4116 4117 4118 // ////////////////////////////////////////////////////////////////////////// // 4119 static immutable double[68] kaiser12Table = [ 4120 0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076, 4121 0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014, 4122 0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601, 4123 0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014, 4124 0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490, 4125 0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546, 4126 0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178, 4127 0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947, 4128 0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058, 4129 0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438, 4130 0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734, 4131 0.00001000, 0.00000000]; 4132 4133 static immutable double[36] kaiser10Table = [ 4134 0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446, 4135 0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347, 4136 0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962, 4137 0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451, 4138 0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739, 4139 0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000]; 4140 4141 static immutable double[36] kaiser8Table = [ 4142 0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200, 4143 0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126, 4144 0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272, 4145 0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758, 4146 0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490, 4147 0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000]; 4148 4149 static immutable double[36] kaiser6Table = [ 4150 0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003, 4151 0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565, 4152 0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561, 4153 0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058, 4154 0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600, 4155 0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000]; 4156 4157 struct FuncDef { 4158 immutable(double)* table; 4159 int oversample; 4160 } 4161 4162 static immutable FuncDef Kaiser12 = FuncDef(kaiser12Table.ptr, 64); 4163 static immutable FuncDef Kaiser10 = FuncDef(kaiser10Table.ptr, 32); 4164 static immutable FuncDef Kaiser8 = FuncDef(kaiser8Table.ptr, 32); 4165 static immutable FuncDef Kaiser6 = FuncDef(kaiser6Table.ptr, 32); 4166 4167 4168 struct QualityMapping { 4169 int baseLength; 4170 int oversample; 4171 float downsampleBandwidth; 4172 float upsampleBandwidth; 4173 immutable FuncDef* windowFunc; 4174 } 4175 4176 4177 /* This table maps conversion quality to internal parameters. There are two 4178 reasons that explain why the up-sampling bandwidth is larger than the 4179 down-sampling bandwidth: 4180 1) When up-sampling, we can assume that the spectrum is already attenuated 4181 close to the Nyquist rate (from an A/D or a previous resampling filter) 4182 2) Any aliasing that occurs very close to the Nyquist rate will be masked 4183 by the sinusoids/noise just below the Nyquist rate (guaranteed only for 4184 up-sampling). 4185 */ 4186 static immutable QualityMapping[11] qualityMap = [ 4187 QualityMapping( 8, 4, 0.830f, 0.860f, &Kaiser6 ), /* Q0 */ 4188 QualityMapping( 16, 4, 0.850f, 0.880f, &Kaiser6 ), /* Q1 */ 4189 QualityMapping( 32, 4, 0.882f, 0.910f, &Kaiser6 ), /* Q2 */ /* 82.3% cutoff ( ~60 dB stop) 6 */ 4190 QualityMapping( 48, 8, 0.895f, 0.917f, &Kaiser8 ), /* Q3 */ /* 84.9% cutoff ( ~80 dB stop) 8 */ 4191 QualityMapping( 64, 8, 0.921f, 0.940f, &Kaiser8 ), /* Q4 */ /* 88.7% cutoff ( ~80 dB stop) 8 */ 4192 QualityMapping( 80, 16, 0.922f, 0.940f, &Kaiser10), /* Q5 */ /* 89.1% cutoff (~100 dB stop) 10 */ 4193 QualityMapping( 96, 16, 0.940f, 0.945f, &Kaiser10), /* Q6 */ /* 91.5% cutoff (~100 dB stop) 10 */ 4194 QualityMapping(128, 16, 0.950f, 0.950f, &Kaiser10), /* Q7 */ /* 93.1% cutoff (~100 dB stop) 10 */ 4195 QualityMapping(160, 16, 0.960f, 0.960f, &Kaiser10), /* Q8 */ /* 94.5% cutoff (~100 dB stop) 10 */ 4196 QualityMapping(192, 32, 0.968f, 0.968f, &Kaiser12), /* Q9 */ /* 95.5% cutoff (~100 dB stop) 10 */ 4197 QualityMapping(256, 32, 0.975f, 0.975f, &Kaiser12), /* Q10 */ /* 96.6% cutoff (~100 dB stop) 10 */ 4198 ]; 4199 4200 4201 nothrow @trusted @nogc: 4202 /*8, 24, 40, 56, 80, 104, 128, 160, 200, 256, 320*/ 4203 double computeFunc (float x, immutable FuncDef* func) { 4204 version(Posix) import core.stdc.math : lrintf; 4205 import std.math : floor; 4206 //double[4] interp; 4207 float y = x*func.oversample; 4208 version(Posix) { 4209 int ind = cast(int)lrintf(floor(y)); 4210 } else { 4211 int ind = cast(int)(floor(y)); 4212 } 4213 float frac = (y-ind); 4214 immutable f2 = frac*frac; 4215 immutable f3 = f2*frac; 4216 double interp3 = -0.1666666667*frac+0.1666666667*(f3); 4217 double interp2 = frac+0.5*(f2)-0.5*(f3); 4218 //double interp2 = 1.0f-0.5f*frac-f2+0.5f*f3; 4219 double interp0 = -0.3333333333*frac+0.5*(f2)-0.1666666667*(f3); 4220 // just to make sure we don't have rounding problems 4221 double interp1 = 1.0f-interp3-interp2-interp0; 4222 //sum = frac*accum[1]+(1-frac)*accum[2]; 4223 return interp0*func.table[ind]+interp1*func.table[ind+1]+interp2*func.table[ind+2]+interp3*func.table[ind+3]; 4224 } 4225 4226 4227 // the slow way of computing a sinc for the table; should improve that some day 4228 float sinc (float cutoff, float x, int N, immutable FuncDef *windowFunc) { 4229 version(LittleEndian) { 4230 align(1) union temp_float { align(1): float f; uint n; } 4231 } else { 4232 static T fabs(T) (T n) pure { static if (__VERSION__ > 2067) pragma(inline, true); return (n < 0 ? -n : n); } 4233 } 4234 import std.math : sin, PI; 4235 version(LittleEndian) { 4236 temp_float txx = void; 4237 txx.f = x; 4238 txx.n &= 0x7fff_ffff; // abs 4239 if (txx.f < 1.0e-6f) return cutoff; 4240 if (txx.f > 0.5f*N) return 0; 4241 } else { 4242 if (fabs(x) < 1.0e-6f) return cutoff; 4243 if (fabs(x) > 0.5f*N) return 0; 4244 } 4245 //FIXME: can it really be any slower than this? 4246 immutable float xx = x*cutoff; 4247 immutable pixx = PI*xx; 4248 version(LittleEndian) { 4249 return cutoff*sin(pixx)/pixx*computeFunc(2.0*txx.f/N, windowFunc); 4250 } else { 4251 return cutoff*sin(pixx)/pixx*computeFunc(fabs(2.0*x/N), windowFunc); 4252 } 4253 } 4254 4255 4256 void cubicCoef (in float frac, float* interp) { 4257 immutable f2 = frac*frac; 4258 immutable f3 = f2*frac; 4259 // compute interpolation coefficients; i'm not sure whether this corresponds to cubic interpolation but I know it's MMSE-optimal on a sinc 4260 interp[0] = -0.16667f*frac+0.16667f*f3; 4261 interp[1] = frac+0.5f*f2-0.5f*f3; 4262 //interp[2] = 1.0f-0.5f*frac-f2+0.5f*f3; 4263 interp[3] = -0.33333f*frac+0.5f*f2-0.16667f*f3; 4264 // just to make sure we don't have rounding problems 4265 interp[2] = 1.0-interp[0]-interp[1]-interp[3]; 4266 } 4267 4268 4269 // ////////////////////////////////////////////////////////////////////////// // 4270 int resamplerBasicDirect(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint* indataLen, float* outdata, uint* outdataLen) 4271 if (is(T == float) || is(T == double)) 4272 { 4273 auto N = st.filterLen; 4274 static if (is(T == double)) assert(N%4 == 0); 4275 int outSample = 0; 4276 int lastSample = st.lastSample.ptr[chanIdx]; 4277 uint sampFracNum = st.sampFracNum.ptr[chanIdx]; 4278 const(float)* sincTable = st.sincTable; 4279 immutable outStride = st.outStride; 4280 immutable intAdvance = st.intAdvance; 4281 immutable fracAdvance = st.fracAdvance; 4282 immutable denRate = st.denRate; 4283 T sum = void; 4284 while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) { 4285 const(float)* sinct = &sincTable[sampFracNum*N]; 4286 const(float)* iptr = &indata[lastSample]; 4287 static if (is(T == float)) { 4288 // at least 2x speedup with SSE here (but for unrolled loop) 4289 if (N%4 == 0) { 4290 version(sincresample_use_sse) { 4291 //align(64) __gshared float[4] zero = 0; 4292 align(64) __gshared float[4+128] zeroesBuf = 0; // dmd cannot into such aligns, alas 4293 __gshared uint zeroesptr = 0; 4294 if (zeroesptr == 0) { 4295 zeroesptr = cast(uint)zeroesBuf.ptr; 4296 if (zeroesptr&0x3f) zeroesptr = (zeroesptr|0x3f)+1; 4297 } 4298 //assert((zeroesptr&0x3f) == 0, "wtf?!"); 4299 asm nothrow @safe @nogc { 4300 mov ECX,[N]; 4301 shr ECX,2; 4302 mov EAX,[zeroesptr]; 4303 movaps XMM0,[EAX]; 4304 mov EAX,[sinct]; 4305 mov EBX,[iptr]; 4306 mov EDX,16; 4307 align 8; 4308 rbdseeloop: 4309 movups XMM1,[EAX]; 4310 movups XMM2,[EBX]; 4311 mulps XMM1,XMM2; 4312 addps XMM0,XMM1; 4313 add EAX,EDX; 4314 add EBX,EDX; 4315 dec ECX; 4316 jnz rbdseeloop; 4317 // store result in sum 4318 movhlps XMM1,XMM0; // now low part of XMM1 contains high part of XMM0 4319 addps XMM0,XMM1; // low part of XMM0 is ok 4320 movaps XMM1,XMM0; 4321 shufps XMM1,XMM0,0b_01_01_01_01; // 2nd float of XMM0 goes to the 1st float of XMM1 4322 addss XMM0,XMM1; 4323 movss [sum],XMM0; 4324 } 4325 /* 4326 float sum1 = 0; 4327 foreach (immutable j; 0..N) sum1 += sinct[j]*iptr[j]; 4328 import std.math; 4329 if (fabs(sum-sum1) > 0.000001f) { 4330 import core.stdc.stdio; 4331 printf("sum=%f; sum1=%f\n", sum, sum1); 4332 assert(0); 4333 } 4334 */ 4335 } else { 4336 // no SSE; for my i3 unrolled loop is almost of the speed of SSE code 4337 T[4] accum = 0; 4338 foreach (immutable j; 0..N/4) { 4339 accum.ptr[0] += *sinct++ * *iptr++; 4340 accum.ptr[1] += *sinct++ * *iptr++; 4341 accum.ptr[2] += *sinct++ * *iptr++; 4342 accum.ptr[3] += *sinct++ * *iptr++; 4343 } 4344 sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3]; 4345 } 4346 } else { 4347 sum = 0; 4348 foreach (immutable j; 0..N) sum += *sinct++ * *iptr++; 4349 } 4350 outdata[outStride*outSample++] = sum; 4351 } else { 4352 if (N%4 == 0) { 4353 //TODO: write SSE code here! 4354 // for my i3 unrolled loop is ~2 times faster 4355 T[4] accum = 0; 4356 foreach (immutable j; 0..N/4) { 4357 accum.ptr[0] += cast(double)*sinct++ * cast(double)*iptr++; 4358 accum.ptr[1] += cast(double)*sinct++ * cast(double)*iptr++; 4359 accum.ptr[2] += cast(double)*sinct++ * cast(double)*iptr++; 4360 accum.ptr[3] += cast(double)*sinct++ * cast(double)*iptr++; 4361 } 4362 sum = accum.ptr[0]+accum.ptr[1]+accum.ptr[2]+accum.ptr[3]; 4363 } else { 4364 sum = 0; 4365 foreach (immutable j; 0..N) sum += cast(double)*sinct++ * cast(double)*iptr++; 4366 } 4367 outdata[outStride*outSample++] = cast(float)sum; 4368 } 4369 lastSample += intAdvance; 4370 sampFracNum += fracAdvance; 4371 if (sampFracNum >= denRate) { 4372 sampFracNum -= denRate; 4373 ++lastSample; 4374 } 4375 } 4376 st.lastSample.ptr[chanIdx] = lastSample; 4377 st.sampFracNum.ptr[chanIdx] = sampFracNum; 4378 return outSample; 4379 } 4380 4381 4382 int resamplerBasicInterpolate(T) (ref SpeexResampler st, uint chanIdx, const(float)* indata, uint *indataLen, float *outdata, uint *outdataLen) 4383 if (is(T == float) || is(T == double)) 4384 { 4385 immutable N = st.filterLen; 4386 assert(N%4 == 0); 4387 int outSample = 0; 4388 int lastSample = st.lastSample.ptr[chanIdx]; 4389 uint sampFracNum = st.sampFracNum.ptr[chanIdx]; 4390 immutable outStride = st.outStride; 4391 immutable intAdvance = st.intAdvance; 4392 immutable fracAdvance = st.fracAdvance; 4393 immutable denRate = st.denRate; 4394 float sum; 4395 4396 float[4] interp = void; 4397 T[4] accum = void; 4398 while (!(lastSample >= cast(int)(*indataLen) || outSample >= cast(int)(*outdataLen))) { 4399 const(float)* iptr = &indata[lastSample]; 4400 const int offset = sampFracNum*st.oversample/st.denRate; 4401 const float frac = (cast(float)((sampFracNum*st.oversample)%st.denRate))/st.denRate; 4402 accum[] = 0; 4403 //TODO: optimize! 4404 foreach (immutable j; 0..N) { 4405 immutable T currIn = iptr[j]; 4406 accum.ptr[0] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-2]); 4407 accum.ptr[1] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset-1]); 4408 accum.ptr[2] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset]); 4409 accum.ptr[3] += currIn*(st.sincTable[4+(j+1)*st.oversample-offset+1]); 4410 } 4411 4412 cubicCoef(frac, interp.ptr); 4413 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]); 4414 4415 outdata[outStride*outSample++] = sum; 4416 lastSample += intAdvance; 4417 sampFracNum += fracAdvance; 4418 if (sampFracNum >= denRate) { 4419 sampFracNum -= denRate; 4420 ++lastSample; 4421 } 4422 } 4423 4424 st.lastSample.ptr[chanIdx] = lastSample; 4425 st.sampFracNum.ptr[chanIdx] = sampFracNum; 4426 return outSample; 4427 } 4428 4429 4430 // ////////////////////////////////////////////////////////////////////////// // 4431 uint gcd (uint a, uint b) pure { 4432 if (a == 0) return b; 4433 if (b == 0) return a; 4434 for (;;) { 4435 if (a > b) { 4436 a %= b; 4437 if (a == 0) return b; 4438 if (a == 1) return 1; 4439 } else { 4440 b %= a; 4441 if (b == 0) return a; 4442 if (b == 1) return 1; 4443 } 4444 } 4445 } 4446 4447 4448 // ////////////////////////////////////////////////////////////////////////// // 4449 // very simple and cheap cubic upsampler 4450 struct CubicUpsampler { 4451 public: 4452 nothrow @trusted @nogc: 4453 float[2] curposfrac; // current position offset [0..1) 4454 float step; // how long we should move on one step? 4455 float[4][2] data; // -1..3 4456 uint[2] drain; 4457 4458 void reset () { 4459 curposfrac[] = 0.0f; 4460 foreach (ref d; data) d[] = 0.0f; 4461 drain[] = 0; 4462 } 4463 4464 bool setup (float astep) { 4465 if (astep >= 1.0f) return false; 4466 step = astep; 4467 return true; 4468 } 4469 4470 /* 4471 static struct Data { 4472 const(float)[] dataIn; 4473 float[] dataOut; 4474 uint inputSamplesUsed; // out value, in samples (i.e. multiplied by channel count) 4475 uint outputSamplesUsed; // out value, in samples (i.e. multiplied by channel count) 4476 } 4477 */ 4478 4479 SpeexResampler.Error process (ref SpeexResampler.Data d) { 4480 d.inputSamplesUsed = d.outputSamplesUsed = 0; 4481 if (d.dataOut.length < 2) return SpeexResampler.Error.OK; 4482 foreach (uint cidx; 0..2) { 4483 uint inleft = cast(uint)d.dataIn.length/2; 4484 uint outleft = cast(uint)d.dataOut.length/2; 4485 processChannel(inleft, outleft, (d.dataIn.length ? d.dataIn.ptr+cidx : null), (d.dataOut.length ? d.dataOut.ptr+cidx : null), cidx); 4486 d.outputSamplesUsed += cast(uint)(d.dataOut.length/2)-outleft; 4487 d.inputSamplesUsed += cast(uint)(d.dataIn.length/2)-inleft; 4488 } 4489 return SpeexResampler.Error.OK; 4490 } 4491 4492 private void processChannel (ref uint inleft, ref uint outleft, const(float)* dataIn, float* dataOut, uint cidx) { 4493 if (outleft == 0) return; 4494 if (inleft == 0 && drain.ptr[cidx] <= 1) return; 4495 auto dt = data.ptr[cidx].ptr; 4496 auto drn = drain.ptr+cidx; 4497 auto cpf = curposfrac.ptr+cidx; 4498 immutable float st = step; 4499 for (;;) { 4500 // fill buffer 4501 while ((*drn) < 4) { 4502 if (inleft == 0) return; 4503 dt[(*drn)++] = *dataIn; 4504 dataIn += 2; 4505 --inleft; 4506 } 4507 if (outleft == 0) return; 4508 --outleft; 4509 // cubic interpolation 4510 /*version(none)*/ { 4511 // interpolate between y1 and y2 4512 immutable float mu = (*cpf); // how far we are moved from y1 to y2 4513 immutable float mu2 = mu*mu; // wow 4514 immutable float y0 = dt[0], y1 = dt[1], y2 = dt[2], y3 = dt[3]; 4515 version(complex_cubic) { 4516 immutable float z0 = 0.5*y3; 4517 immutable float z1 = 0.5*y0; 4518 immutable float a0 = 1.5*y1-z1-1.5*y2+z0; 4519 immutable float a1 = y0-2.5*y1+2*y2-z0; 4520 immutable float a2 = 0.5*y2-z1; 4521 } else { 4522 immutable float a0 = y3-y2-y0+y1; 4523 immutable float a1 = y0-y1-a0; 4524 immutable float a2 = y2-y0; 4525 } 4526 *dataOut = a0*mu*mu2+a1*mu2+a2*mu+y1; 4527 }// else *dataOut = dt[1]; 4528 dataOut += 2; 4529 if (((*cpf) += st) >= 1.0f) { 4530 (*cpf) -= 1.0f; 4531 dt[0] = dt[1]; 4532 dt[1] = dt[2]; 4533 dt[2] = dt[3]; 4534 dt[3] = 0.0f; 4535 --(*drn); // will request more input bytes 4536 } 4537 } 4538 } 4539 } 4540 } 4541 4542 version(with_resampler) 4543 abstract class ResamplingContext { 4544 int inputSampleRate; 4545 int outputSampleRate; 4546 4547 int inputChannels; 4548 int outputChannels; 4549 4550 SpeexResampler resamplerLeft; 4551 SpeexResampler resamplerRight; 4552 4553 SpeexResampler.Data resamplerDataLeft; 4554 SpeexResampler.Data resamplerDataRight; 4555 4556 float[][2] buffersIn; 4557 float[][2] buffersOut; 4558 4559 uint rateNum; 4560 uint rateDem; 4561 4562 float[][2] dataReady; 4563 4564 SampleControlFlags scflags; 4565 4566 this(SampleControlFlags scflags, int inputSampleRate, int outputSampleRate, int inputChannels, int outputChannels) { 4567 this.scflags = scflags; 4568 this.inputSampleRate = inputSampleRate; 4569 this.outputSampleRate = outputSampleRate; 4570 this.inputChannels = inputChannels; 4571 this.outputChannels = outputChannels; 4572 4573 4574 if(auto err = resamplerLeft.setup(1, inputSampleRate, outputSampleRate, 5)) 4575 throw new Exception("ugh"); 4576 resamplerRight.setup(1, inputSampleRate, outputSampleRate, 5); 4577 4578 processNewRate(); 4579 } 4580 4581 void changePlaybackSpeed(float newMultiplier) { 4582 resamplerLeft.setRate(cast(uint) (inputSampleRate * newMultiplier), outputSampleRate); 4583 resamplerRight.setRate(cast(uint) (inputSampleRate * newMultiplier), outputSampleRate); 4584 4585 processNewRate(); 4586 } 4587 4588 void processNewRate() { 4589 resamplerLeft.getRatio(rateNum, rateDem); 4590 4591 int add = (rateNum % rateDem) ? 1 : 0; 4592 4593 buffersIn[0] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add); 4594 buffersOut[0] = new float[](BUFFER_SIZE_FRAMES); 4595 if(inputChannels > 1) { 4596 buffersIn[1] = new float[](BUFFER_SIZE_FRAMES * rateNum / rateDem + add); 4597 buffersOut[1] = new float[](BUFFER_SIZE_FRAMES); 4598 } 4599 4600 } 4601 4602 /+ 4603 float*[2] tmp; 4604 tmp[0] = buffersIn[0].ptr; 4605 tmp[1] = buffersIn[1].ptr; 4606 4607 auto actuallyGot = v.getSamplesFloat(v.chans, tmp.ptr, cast(int) buffersIn[0].length); 4608 4609 resamplerDataLeft.dataIn should be a slice of buffersIn[0] that is filled up 4610 ditto for resamplerDataRight if the source has two channels 4611 +/ 4612 abstract void loadMoreSamples(); 4613 4614 bool loadMore() { 4615 resamplerDataLeft.dataIn = buffersIn[0]; 4616 resamplerDataLeft.dataOut = buffersOut[0]; 4617 4618 resamplerDataRight.dataIn = buffersIn[1]; 4619 resamplerDataRight.dataOut = buffersOut[1]; 4620 4621 loadMoreSamples(); 4622 4623 //resamplerLeft.reset(); 4624 4625 if(auto err = resamplerLeft.process(resamplerDataLeft)) 4626 throw new Exception("ugh"); 4627 if(inputChannels > 1) 4628 //resamplerRight.reset(); 4629 resamplerRight.process(resamplerDataRight); 4630 4631 resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[0 .. resamplerDataLeft.outputSamplesUsed]; 4632 resamplerDataRight.dataOut = resamplerDataRight.dataOut[0 .. resamplerDataRight.outputSamplesUsed]; 4633 4634 if(resamplerDataLeft.dataOut.length == 0) { 4635 return true; 4636 } 4637 return false; 4638 } 4639 4640 4641 bool fillBuffer(short[] buffer) { 4642 if(cast(int) buffer.length != buffer.length) 4643 throw new Exception("eeeek"); 4644 4645 if(scflags.paused) { 4646 buffer[] = 0; 4647 return true; 4648 } 4649 4650 if(outputChannels == 1) { 4651 foreach(ref s; buffer) { 4652 if(resamplerDataLeft.dataOut.length == 0) { 4653 if(loadMore()) { 4654 scflags.finished_ = true; 4655 return false; 4656 } 4657 } 4658 4659 if(inputChannels == 1) { 4660 s = cast(short) (resamplerDataLeft.dataOut[0] * short.max); 4661 resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $]; 4662 } else { 4663 s = cast(short) ((resamplerDataLeft.dataOut[0] + resamplerDataRight.dataOut[0]) * short.max / 2); 4664 4665 resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $]; 4666 resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $]; 4667 } 4668 } 4669 4670 scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels * scflags.playbackSpeed; 4671 } else if(outputChannels == 2) { 4672 foreach(idx, ref s; buffer) { 4673 if(resamplerDataLeft.dataOut.length == 0) { 4674 if(loadMore()) { 4675 scflags.finished_ = true; 4676 return false; 4677 } 4678 } 4679 4680 if(inputChannels == 1) { 4681 s = cast(short) (resamplerDataLeft.dataOut[0] * short.max); 4682 if(idx & 1) 4683 resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $]; 4684 } else { 4685 if(idx & 1) { 4686 s = cast(short) (resamplerDataRight.dataOut[0] * short.max); 4687 resamplerDataRight.dataOut = resamplerDataRight.dataOut[1 .. $]; 4688 } else { 4689 s = cast(short) (resamplerDataLeft.dataOut[0] * short.max); 4690 resamplerDataLeft.dataOut = resamplerDataLeft.dataOut[1 .. $]; 4691 } 4692 } 4693 } 4694 4695 scflags.currentPosition += cast(float) buffer.length / outputSampleRate / outputChannels * scflags.playbackSpeed; 4696 } else assert(0); 4697 4698 if(scflags.stopped) 4699 scflags.finished_ = true; 4700 return !scflags.stopped; 4701 } 4702 } 4703 4704 private enum scriptable = "arsd_jsvar_compatible";