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