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