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