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