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