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