1 /** 2 Audio decoder and encoder abstraction. This delegates to format-specific encoders/decoders. 3 4 Copyright: Guillaume Piolats 2020. 5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 module audioformats.stream; 8 9 import core.stdc.stdio; 10 import core.stdc..string; 11 import core.stdc.stdlib: malloc, realloc, free; 12 13 import dplug.core.nogc; 14 import dplug.core.vec; 15 16 import audioformats.io; 17 18 version(decodeMP3) import audioformats.minimp3_ex; 19 version(decodeFLAC) import audioformats.drflac; 20 version(decodeOGG) import audioformats.stb_vorbis2; 21 version(decodeOPUS) import audioformats.dopus; 22 version(decodeMOD) import audioformats.pocketmod; 23 24 version(decodeWAV) import audioformats.wav; 25 else version(encodeWAV) import audioformats.wav; 26 27 version(decodeXM) import audioformats.libxm; 28 29 /// Library for sound file decoding and encoding. 30 /// All operations are blocking, and should not be done in a real-time audio thread. 31 /// (besides, you would also need resampling for playback). 32 /// Also not thread-safe, synchronization in on yours. 33 34 /// Format of audio files. 35 enum AudioFileFormat 36 { 37 wav, /// WAVE format 38 mp3, /// MP3 format 39 flac, /// FLAC format 40 ogg, /// OGG format 41 opus, /// Opus format 42 mod, /// ProTracker MOD format 43 xm, /// FastTracker II Extended Module format 44 unknown 45 } 46 47 /// Returns: String representation of an `AudioFileFormat`. 48 string convertAudioFileFormatToString(AudioFileFormat fmt) 49 { 50 final switch(fmt) with (AudioFileFormat) 51 { 52 case wav: return "wav"; 53 case mp3: return "mp3"; 54 case flac: return "flac"; 55 case ogg: return "ogg"; 56 case opus: return "opus"; 57 case mod: return "mod"; 58 case xm: return "xm"; 59 case unknown: return "unknown"; 60 } 61 } 62 63 64 /// The length of things you shouldn't query a length about: 65 /// - files that are being written 66 /// - audio files you don't know the extent 67 enum audiostreamUnknownLength = -1; 68 69 /// An AudioStream is a pointer to a dynamically allocated `Stream`. 70 public struct AudioStream 71 { 72 public: // This is also part of the public API 73 74 75 /// Opens an audio stream that decodes from a file. 76 /// This stream will be opened for reading only. 77 /// 78 /// Params: 79 /// path An UTF-8 path to the sound file. 80 /// 81 /// Note: throws a manually allocated exception in case of error. Free it with `dplug.core.destroyFree`. 82 void openFromFile(const(char)[] path) @nogc 83 { 84 cleanUp(); 85 86 fileContext = mallocNew!FileContext(); 87 fileContext.initialize(path, false); 88 userData = fileContext; 89 90 _io = mallocNew!IOCallbacks(); 91 _io.seek = &file_seek; 92 _io.tell = &file_tell; 93 _io.getFileLength = &file_getFileLength; 94 _io.read = &file_read; 95 _io.write = null; 96 _io.skip = &file_skip; 97 _io.flush = null; 98 99 startDecoding(); 100 } 101 102 /// Opens an audio stream that decodes from memory. 103 /// This stream will be opened for reading only. 104 /// Note: throws a manually allocated exception in case of error. Free it with `dplug.core.destroyFree`. 105 /// 106 /// Params: inputData The whole file to decode. 107 void openFromMemory(const(ubyte)[] inputData) @nogc 108 { 109 cleanUp(); 110 111 memoryContext = mallocNew!MemoryContext(); 112 memoryContext.initializeWithConstantInput(inputData.ptr, inputData.length); 113 114 userData = memoryContext; 115 116 _io = mallocNew!IOCallbacks(); 117 _io.seek = &memory_seek; 118 _io.tell = &memory_tell; 119 _io.getFileLength = &memory_getFileLength; 120 _io.read = &memory_read; 121 _io.write = null; 122 _io.skip = &memory_skip; 123 _io.flush = null; 124 125 startDecoding(); 126 } 127 128 /// Opens an audio stream that writes to file. 129 /// This stream will be open for writing only. 130 /// Note: throws a manually allocated exception in case of error. Free it with `dplug.core.destroyFree`. 131 /// 132 /// Params: 133 /// path An UTF-8 path to the sound file. 134 /// format Audio file format to generate. 135 /// sampleRate Sample rate of this audio stream. This samplerate might be rounded up to the nearest integer number. 136 /// numChannels Number of channels of this audio stream. 137 void openToFile(const(char)[] path, AudioFileFormat format, float sampleRate, int numChannels) @nogc 138 { 139 cleanUp(); 140 141 fileContext = mallocNew!FileContext(); 142 fileContext.initialize(path, true); 143 userData = fileContext; 144 145 _io = mallocNew!IOCallbacks(); 146 _io.seek = &file_seek; 147 _io.tell = &file_tell; 148 _io.getFileLength = null; 149 _io.read = null; 150 _io.write = &file_write; 151 _io.skip = null; 152 _io.flush = &file_flush; 153 154 startEncoding(format, sampleRate, numChannels); 155 } 156 157 /// Opens an audio stream that writes to a dynamically growable output buffer. 158 /// This stream will be open for writing only. 159 /// Access to the internal buffer after encoding with `finalizeAndGetEncodedResult`. 160 /// Note: throws a manually allocated exception in case of error. Free it with `dplug.core.destroyFree`. 161 /// 162 /// Params: 163 /// format Audio file format to generate. 164 /// sampleRate Sample rate of this audio stream. This samplerate might be rounded up to the nearest integer number. 165 /// numChannels Number of channels of this audio stream. 166 void openToBuffer(AudioFileFormat format, float sampleRate, int numChannels) @nogc 167 { 168 cleanUp(); 169 170 memoryContext = mallocNew!MemoryContext(); 171 memoryContext.initializeWithInternalGrowableBuffer(); 172 userData = memoryContext; 173 174 _io = mallocNew!IOCallbacks(); 175 _io.seek = &memory_seek; 176 _io.tell = &memory_tell; 177 _io.getFileLength = null; 178 _io.read = null; 179 _io.write = &memory_write_append; 180 _io.skip = null; 181 _io.flush = &memory_flush; 182 183 startEncoding(format, sampleRate, numChannels); 184 } 185 186 /// Opens an audio stream that writes to a pre-defined area in memory of `maxLength` bytes. 187 /// This stream will be open for writing only. 188 /// Destroy this stream with `closeAudioStream`. 189 /// Note: throws a manually allocated exception in case of error. Free it with `dplug.core.destroyFree`. 190 /// 191 /// Params: 192 /// data Pointer to output memory. 193 /// size_t maxLength. 194 /// format Audio file format to generate. 195 /// sampleRate Sample rate of this audio stream. This samplerate might be rounded up to the nearest integer number. 196 /// numChannels Number of channels of this audio stream. 197 void openToMemory(ubyte* data, 198 size_t maxLength, 199 AudioFileFormat format, 200 float sampleRate, 201 int numChannels) @nogc 202 { 203 cleanUp(); 204 205 memoryContext = mallocNew!MemoryContext(); 206 memoryContext.initializeWithExternalOutputBuffer(data, maxLength); 207 userData = memoryContext; 208 209 _io = mallocNew!IOCallbacks(); 210 _io.seek = &memory_seek; 211 _io.tell = &memory_tell; 212 _io.getFileLength = null; 213 _io.read = null; 214 _io.write = &memory_write_limited; 215 _io.skip = null; 216 _io.flush = &memory_flush; 217 218 startEncoding(format, sampleRate, numChannels); 219 } 220 221 ~this() @nogc 222 { 223 cleanUp(); 224 } 225 226 void cleanUp() @nogc 227 { 228 // Write the last needed bytes if needed 229 finalizeEncodingIfNeeded(); 230 231 version(decodeMP3) 232 { 233 if (_mp3DecoderNew !is null) 234 { 235 mp3dec_ex_close(_mp3DecoderNew); 236 free(_mp3DecoderNew); 237 _mp3DecoderNew = null; 238 } 239 if (_mp3io !is null) 240 { 241 free(_mp3io); 242 _mp3io = null; 243 } 244 } 245 246 version(decodeFLAC) 247 { 248 if (_flacDecoder !is null) 249 { 250 drflac_close(_flacDecoder); 251 _flacDecoder = null; 252 _flacPositionFrame = 0; 253 } 254 } 255 256 version(decodeOGG) 257 { 258 if (_oggHandle !is null) 259 { 260 stb_vorbis_close(_oggHandle); 261 _oggHandle = null; 262 } 263 _oggBuffer.reallocBuffer(0); 264 } 265 266 version(decodeOPUS) 267 { 268 if (_opusDecoder !is null) 269 { 270 opusClose(_opusDecoder); 271 _opusDecoder = null; 272 } 273 _opusBuffer = null; 274 } 275 276 version(decodeWAV) 277 { 278 if (_wavDecoder !is null) 279 { 280 destroyFree(_wavDecoder); 281 _wavDecoder = null; 282 } 283 } 284 285 version(decodeXM) 286 { 287 if (_xmDecoder !is null) 288 { 289 xm_free_context(_xmDecoder); 290 _xmDecoder = null; 291 } 292 if (_xmContent != null) 293 { 294 free(_xmContent); 295 _xmContent = null; 296 } 297 } 298 299 version(decodeMOD) 300 { 301 if (_modDecoder !is null) 302 { 303 free(_modDecoder); 304 _modDecoder = null; 305 _modContent.reallocBuffer(0); 306 } 307 } 308 309 version(encodeWAV) 310 { 311 if (_wavEncoder !is null) 312 { 313 destroyFree(_wavEncoder); 314 _wavEncoder = null; 315 } 316 } 317 318 if (_decoderContext) 319 { 320 destroyFree(_decoderContext); 321 _decoderContext = null; 322 } 323 324 if (fileContext !is null) 325 { 326 if (fileContext.file !is null) 327 { 328 int result = fclose(fileContext.file); 329 if (result) 330 throw mallocNew!Exception("Closing of audio file errored"); 331 } 332 destroyFree(fileContext); 333 fileContext = null; 334 } 335 336 if (memoryContext !is null) 337 { 338 // TODO destroy buffer if any is owned 339 destroyFree(memoryContext); 340 memoryContext = null; 341 } 342 343 if (_io !is null) 344 { 345 destroyFree(_io); 346 _io = null; 347 } 348 } 349 350 /// Returns: File format of this stream. 351 AudioFileFormat getFormat() nothrow @nogc 352 { 353 return _format; 354 } 355 356 /// Returns: `true` if using this stream's operations is acceptable in an audio thread (eg: no file I/O). 357 bool realtimeSafe() @nogc 358 { 359 return fileContext is null; 360 } 361 362 /// Returns: `true` if this stream is concerning a tracker module format. 363 /// This is useful because the seek/tell functions are different. 364 bool isModule() @nogc 365 { 366 final switch(_format) with (AudioFileFormat) 367 { 368 case wav: 369 case mp3: 370 case flac: 371 case ogg: 372 case opus: 373 return false; 374 case mod: 375 case xm: 376 return true; 377 case unknown: 378 assert(false); 379 380 } 381 } 382 383 /// Returns: `true` if this stream allows seeking. 384 /// Note: the particular function to call for seeking depends on whether the stream is a tracker module. 385 /// See_also: `seekPosition`. 386 bool canSeek() @nogc 387 { 388 final switch(_format) with (AudioFileFormat) 389 { 390 case wav: 391 case mp3: 392 case flac: 393 case ogg: 394 case opus: 395 return true; 396 case mod: 397 case xm: 398 return true; 399 case unknown: 400 assert(false); 401 402 } 403 } 404 405 /// Returns: `true` if this stream is currently open for reading (decoding). 406 /// `false` if the stream has been destroyed, or if it was created for encoding instead. 407 bool isOpenForReading() nothrow @nogc 408 { 409 return (_io !is null) && (_io.read !is null); 410 } 411 412 deprecated("Use isOpenForWriting instead") alias isOpenedForWriting = isOpenForWriting; 413 414 /// Returns: `true` if this stream is currently open for writing (encoding). 415 /// `false` if the stream has been destroyed, finalized with `finalizeEncoding()`, 416 /// or if it was created for decoding instead. 417 bool isOpenForWriting() nothrow @nogc 418 { 419 // Note: 420 // * when opened for reading, I/O operations given are: seek/tell/getFileLength/read. 421 // * when opened for writing, I/O operations given are: seek/tell/write/flush. 422 return (_io !is null) && (_io.read is null); 423 } 424 425 /// Returns: Number of channels in this stream. 1 means mono, 2 means stereo... 426 int getNumChannels() nothrow @nogc 427 { 428 return _numChannels; 429 } 430 431 /// Returns: Length of this stream in frames. 432 /// Note: may return the special value `audiostreamUnknownLength` if the length is unknown. 433 long getLengthInFrames() nothrow @nogc 434 { 435 return _lengthInFrames; 436 } 437 438 /// Returns: Sample-rate of this stream in Hz. 439 float getSamplerate() nothrow @nogc 440 { 441 return _sampleRate; 442 } 443 444 /// Read interleaved float samples in the given buffer `outData`. 445 /// 446 /// Params: 447 /// outData Buffer where to put decoded samples. Samples are arranged in an interleaved fashion. 448 /// Must have room for `frames` x `getNumChannels()` samples. 449 /// For a stereo file, the output data will contain LRLRLR... repeated `result` times. 450 /// 451 /// frames The number of multichannel frames to be read. 452 /// A frame is `getNumChannels()` samples. 453 /// 454 /// Returns: Number of actually read frames. Multiply by `getNumChannels()` to get the number of read samples. 455 /// When that number is less than `frames`, it means the stream is done decoding, or that there was a decoding error. 456 /// 457 /// TODO: once this returned less than `frames`, are we guaranteed we can keep calling that and it returns 0? 458 int readSamplesFloat(float* outData, int frames) @nogc 459 { 460 assert(isOpenForReading()); 461 462 final switch(_format) 463 { 464 case AudioFileFormat.opus: 465 { 466 version(decodeOPUS) 467 { 468 try 469 { 470 // Can't decoder further than end of the stream. 471 if (_opusPositionFrame + frames > _lengthInFrames) 472 { 473 frames = cast(int)(_lengthInFrames - _opusPositionFrame); 474 } 475 476 int decoded = 0; 477 while (decoded < frames) 478 { 479 // Is there any sample left in _opusBuffer? 480 // If not decode some frames. 481 if (_opusBuffer is null || _opusBuffer.length == 0) 482 { 483 _opusBuffer = _opusDecoder.readFrame(); 484 if (_opusBuffer is null) 485 break; 486 } 487 488 int samplesInBuffer = cast(int) _opusBuffer.length; 489 int framesInBuffer = samplesInBuffer / _numChannels; 490 if (framesInBuffer == 0) 491 break; 492 493 // Frames to pull are min( frames left to decode, frames available) 494 int framesToDecode = frames - decoded; 495 int framesToUse = framesToDecode < framesInBuffer ? framesToDecode : framesInBuffer; 496 assert(framesToUse != 0); 497 498 int samplesToUse = framesToUse * _numChannels; 499 int outOffset = decoded*_numChannels; 500 501 if (outData !is null) // for seeking, we have the ability in OPUS to call readSamplesFloat with no outData 502 { 503 for (int n = 0; n < samplesToUse; ++n) 504 { 505 outData[outOffset + n] = _opusBuffer[n] / 32767.0f; 506 } 507 } 508 _opusBuffer = _opusBuffer[samplesToUse..$]; // reduce size of intermediate buffer 509 decoded += framesToUse; 510 } 511 _opusPositionFrame += decoded; 512 assert(_opusPositionFrame <= _lengthInFrames); 513 return decoded; 514 } 515 catch(Exception e) 516 { 517 destroyFree(e); 518 return 0; // decoding might fail, in which case return zero samples 519 } 520 } 521 } 522 523 case AudioFileFormat.flac: 524 { 525 version(decodeFLAC) 526 { 527 assert(_flacDecoder !is null); 528 529 int* integerData = cast(int*)outData; 530 int samples = cast(int) drflac_read_s32(_flacDecoder, frames, integerData); 531 532 // "Samples are always output as interleaved signed 32-bit PCM." 533 // Convert to float with type-punning. Note that this looses some precision. 534 double factor = 1.0 / int.max; 535 foreach(n; 0..samples) 536 { 537 outData[n] = integerData[n] * factor; 538 } 539 int framesDecoded = samples / _numChannels; 540 _flacPositionFrame += framesDecoded; 541 return framesDecoded; 542 } 543 else 544 { 545 assert(false); // Impossible 546 } 547 } 548 549 case AudioFileFormat.ogg: 550 { 551 version(decodeOGG) 552 { 553 assert(_oggHandle !is null); 554 return stb_vorbis_get_samples_float_interleaved(_oggHandle, _numChannels, outData, frames * _numChannels); 555 } 556 else 557 { 558 assert(false); // Impossible 559 } 560 } 561 562 case AudioFileFormat.mp3: 563 { 564 version(decodeMP3) 565 { 566 assert(_mp3DecoderNew !is null); 567 568 int samplesNeeded = frames * _numChannels; 569 int result = cast(int) mp3dec_ex_read(_mp3DecoderNew, outData, samplesNeeded); 570 if (result < 0) // error 571 return 0; 572 return result / _numChannels; 573 } 574 else 575 { 576 assert(false); // Impossible 577 } 578 } 579 case AudioFileFormat.wav: 580 version(decodeWAV) 581 { 582 assert(_wavDecoder !is null); 583 int readFrames = _wavDecoder.readSamples(outData, frames); 584 return readFrames; 585 } 586 else 587 { 588 assert(false); // Impossible 589 } 590 591 case AudioFileFormat.xm: 592 version(decodeXM) 593 { 594 assert(_xmDecoder !is null); 595 596 if (xm_get_loop_count(_xmDecoder) >= 1) 597 return 0; // song is finished 598 599 xm_generate_samples(_xmDecoder, outData, frames); 600 return frames; // Note: XM decoder pads end with zeroes. 601 } 602 else 603 { 604 assert(false); // Impossible 605 } 606 607 case AudioFileFormat.mod: 608 version(decodeMOD) 609 { 610 if (pocketmod_loop_count(_modDecoder) >= 1) 611 return 0; // end stream after MOD finishes, looping not supported 612 assert(_modDecoder !is null); 613 int bytesReturned = pocketmod_render(_modDecoder, outData, frames * 2 * 4); 614 assert((bytesReturned % 8) == 0); 615 return bytesReturned / 8; 616 } 617 else 618 { 619 assert(false); // Impossible 620 } 621 622 case AudioFileFormat.unknown: 623 // One shouldn't ever get there, since in this case 624 // opening has failed. 625 assert(false); 626 } 627 } 628 ///ditto 629 int readSamplesFloat(float[] outData) @nogc 630 { 631 assert( (outData.length % _numChannels) == 0); 632 return readSamplesFloat(outData.ptr, cast(int)(outData.length / _numChannels) ); 633 } 634 635 /// Write interleaved float samples to the stream, from the given buffer `inData[0..frames]`. 636 /// 637 /// Params: 638 /// inData Buffer of interleaved samples to append to the stream. 639 /// Must contain `frames` x `getNumChannels()` samples. 640 /// For a stereo file, `inData` contains LRLRLR... repeated `frames` times. 641 /// 642 /// frames The number of frames to append to the stream. 643 /// A frame is `getNumChannels()` samples. 644 /// 645 /// Returns: Number of actually written frames. Multiply by `getNumChannels()` to get the number of written samples. 646 /// When that number is less than `frames`, it means the stream had a write error. 647 int writeSamplesFloat(float* inData, int frames) nothrow @nogc 648 { 649 assert(_io && _io.write !is null); 650 651 final switch(_format) 652 { 653 case AudioFileFormat.mp3: 654 case AudioFileFormat.flac: 655 case AudioFileFormat.ogg: 656 case AudioFileFormat.opus: 657 case AudioFileFormat.mod: 658 case AudioFileFormat.xm: 659 case AudioFileFormat.unknown: 660 { 661 assert(false); // Shouldn't have arrived here, such encoding aren't supported. 662 } 663 case AudioFileFormat.wav: 664 { 665 version(encodeWAV) 666 { 667 return _wavEncoder.writeSamples(inData, frames); 668 } 669 else 670 { 671 assert(false, "no support for WAV encoding"); 672 } 673 } 674 } 675 } 676 ///ditto 677 int writeSamplesFloat(float[] inData) nothrow @nogc 678 { 679 assert( (inData.length % _numChannels) == 0); 680 return writeSamplesFloat(inData.ptr, cast(int)(inData.length / _numChannels)); 681 } 682 683 // ----------------------------------------------------------------------------------------------------- 684 // <module functions> 685 // Those tracker module-specific functions below can only be called when `isModule()` returns `true`. 686 // Additionally, seeking function can only be called if `canSeek()` also returns `true`. 687 // ----------------------------------------------------------------------------------------------------- 688 689 /// Length. Returns the amount of patterns in the module 690 /// Formats that support this: MOD, XM. 691 int countModulePatterns() 692 { 693 assert(isOpenForReading() && isModule()); 694 final switch(_format) with (AudioFileFormat) 695 { 696 case mp3: 697 case flac: 698 case ogg: 699 case opus: 700 case wav: 701 case unknown: 702 assert(false); 703 case mod: 704 return _modDecoder.num_patterns; 705 case xm: 706 return xm_get_number_of_patterns(_xmDecoder); 707 } 708 } 709 710 /// Length. Returns the amount of PLAYED patterns in the module 711 /// Formats that support this: MOD, XM. 712 int getModuleLength() 713 { 714 assert(isOpenForReading() && isModule()); 715 final switch(_format) with (AudioFileFormat) 716 { 717 case mp3: 718 case flac: 719 case ogg: 720 case opus: 721 case wav: 722 case unknown: 723 assert(false); 724 case mod: 725 return _modDecoder.length; 726 case xm: 727 return xm_get_module_length(_xmDecoder); 728 } 729 } 730 731 /// Tell. Returns amount of rows in a pattern. 732 /// Formats that support this: MOD, XM. 733 /// Returns: -1 on error. Else, number of patterns. 734 int rowsInPattern(int pattern) 735 { 736 assert(isOpenForReading() && isModule()); 737 final switch(_format) with (AudioFileFormat) 738 { 739 case mp3: 740 case flac: 741 case ogg: 742 case opus: 743 case wav: 744 case unknown: 745 assert(false); 746 747 case mod: 748 // According to http://lclevy.free.fr/mo3/mod.txt 749 // there's 64 lines (aka rows) per pattern. 750 // TODO: error checking, make sure no out of bounds happens. 751 return 64; 752 753 case xm: 754 { 755 int numPatterns = xm_get_number_of_patterns(_xmDecoder); 756 if (pattern < 0 || pattern >= numPatterns) 757 return -1; 758 759 return xm_get_number_of_rows(_xmDecoder, cast(ushort) pattern); 760 } 761 } 762 } 763 764 /// Tell. Returns the current playing pattern id 765 /// Formats that support this: MOD, XM 766 int tellModulePattern() 767 { 768 assert(isOpenForReading() && isModule()); 769 final switch(_format) with (AudioFileFormat) 770 { 771 case mp3: 772 case flac: 773 case ogg: 774 case opus: 775 case wav: 776 case unknown: 777 assert(false); 778 case mod: 779 return _modDecoder.pattern; 780 case xm: 781 return _xmDecoder.current_table_index; 782 } 783 } 784 785 /// Tell. Returns the current playing row id 786 /// Formats that support this: MOD, XM 787 int tellModuleRow() 788 { 789 assert(isOpenForReading() && isModule()); 790 final switch(_format) with (AudioFileFormat) 791 { 792 case mp3: 793 case flac: 794 case ogg: 795 case opus: 796 case wav: 797 case unknown: 798 assert(false); 799 case mod: 800 return _modDecoder.line; 801 case xm: 802 return _xmDecoder.current_row; 803 } 804 } 805 806 /// Playback info. Returns the amount of multi-channel frames remaining in the current playing pattern. 807 /// Formats that support this: MOD 808 int framesRemainingInPattern() 809 { 810 assert(isOpenForReading() && isModule()); 811 final switch(_format) with (AudioFileFormat) 812 { 813 case mp3: 814 case flac: 815 case ogg: 816 case opus: 817 case wav: 818 case unknown: 819 assert(false); 820 821 case mod: 822 return pocketmod_count_remaining_samples(_modDecoder); 823 case xm: 824 return xm_count_remaining_samples(_xmDecoder); 825 } 826 } 827 828 /// Seeking. Subsequent reads start from pattern + row, 0 index 829 /// Only available for input streams. 830 /// Formats that support seeking per pattern/row: MOD, XM 831 /// Returns: `true` in case of success. 832 bool seekPosition(int pattern, int row) 833 { 834 assert(isOpenForReading() && isModule() && canSeek()); 835 final switch(_format) with (AudioFileFormat) 836 { 837 case mp3: 838 case flac: 839 case ogg: 840 case opus: 841 case wav: 842 case unknown: 843 assert(false); 844 845 case mod: 846 // NOTE: This is untested. 847 return pocketmod_seek(_modDecoder, pattern, row, 0); 848 849 case xm: 850 return xm_seek(_xmDecoder, pattern, row, 0); 851 } 852 } 853 854 // ----------------------------------------------------------------------------------------------------- 855 // </module functions> 856 // ----------------------------------------------------------------------------------------------------- 857 858 // ----------------------------------------------------------------------------------------------------- 859 // <non-module functions> 860 // Those functions below can't be used for tracker module formats, because there is no real concept of 861 // absolute position in these formats. 862 // ----------------------------------------------------------------------------------------------------- 863 864 /// Seeking. Subsequent reads start from multi-channel frame index `frames`. 865 /// Only available for input streams, for streams whose `canSeek()` returns `true`. 866 /// Warning: `seekPosition(lengthInFrames)` is Undefined Behaviour for now. (it works in MP3 867 bool seekPosition(int frame) 868 { 869 assert(isOpenForReading() && !isModule() && canSeek()); // seeking doesn't have the same sense with modules. 870 final switch(_format) with (AudioFileFormat) 871 { 872 case mp3: 873 version(decodeMP3) 874 { 875 assert(_lengthInFrames != audiostreamUnknownLength); 876 if (frame < 0 || frame > _lengthInFrames) 877 return false; 878 return (mp3dec_ex_seek(_mp3DecoderNew, frame * _numChannels) == 0); 879 } 880 else 881 assert(false); 882 case flac: 883 version(decodeFLAC) 884 { 885 if (frame < 0 || frame > _lengthInFrames) 886 return false; 887 bool success = drflac__seek_to_sample__brute_force (_flacDecoder, frame * _numChannels); 888 if (success) 889 _flacPositionFrame = frame; 890 return success; 891 } 892 else 893 assert(false); 894 case ogg: 895 version(decodeOGG) 896 { 897 return stb_vorbis_seek(_oggHandle, frame) == 1; 898 } 899 else 900 assert(false); 901 case opus: 902 version(decodeOPUS) 903 { 904 if (frame < 0 || frame > _lengthInFrames) 905 return false; 906 long where = _opusDecoder.ogg.seekPCM(frame); 907 _opusPositionFrame = where; 908 int toSkip = cast(int)(frame - where); 909 910 // skip remaining samples for sample-accurate seeking 911 // Note: this also updates _opusPositionFrame 912 int skipped = readSamplesFloat(null, cast(int) toSkip); 913 // TODO: if decoding `toSkip` samples failed, restore previous state? 914 return skipped == toSkip; 915 } 916 else 917 assert(false); 918 919 case mod: 920 case xm: 921 assert(false); 922 923 case wav: 924 version(decodeWAV) 925 return _wavDecoder.seekPosition(frame); 926 else 927 assert(false); 928 case unknown: 929 assert(false); 930 931 } 932 } 933 934 /// Tell. Returns the current position in multichannel frames. -1 on error. 935 int tellPosition() 936 { 937 assert(isOpenForReading() && !isModule() && canSeek()); // seeking doesn't have the same sense with modules. 938 final switch(_format) with (AudioFileFormat) 939 { 940 case mp3: 941 version(decodeMP3) 942 { 943 return cast(int) _mp3DecoderNew.cur_sample / _numChannels; 944 } 945 else 946 assert(false); 947 case flac: 948 version(decodeFLAC) 949 { 950 // Implemented externally since drflac is impenetrable. 951 //return cast(int) _flacPositionFrame; 952 return -1; // doesn't work in last frame though... seekPosition buggy in FLAC? 953 } 954 else 955 assert(false); 956 case ogg: 957 version(decodeOGG) 958 return cast(int) stb_vorbis_get_sample_offset(_oggHandle); 959 else 960 assert(false); 961 962 case opus: 963 version(decodeOPUS) 964 { 965 return cast(int) _opusPositionFrame; // implemented externally 966 } 967 else 968 assert(false); 969 970 case wav: 971 version(decodeWAV) 972 return _wavDecoder.tellPosition(); 973 else 974 assert(false); 975 976 case mod: 977 case xm: 978 case unknown: 979 assert(false); 980 981 } 982 } 983 984 985 // ----------------------------------------------------------------------------------------------------- 986 // </non-module functions> 987 // ----------------------------------------------------------------------------------------------------- 988 989 /// Call `fflush()` on written samples, if any. 990 /// It is only useful for streamable output formats, that may want to flush things to disk. 991 void flush() nothrow @nogc 992 { 993 assert( _io && (_io.write !is null) ); 994 _io.flush(userData); 995 } 996 997 /// Finalize encoding. After finalization, further writes are not possible anymore 998 /// however the stream is considered complete and valid for storage. 999 void finalizeEncoding() @nogc 1000 { 1001 // If you crash here, it's because `finalizeEncoding` has been called twice. 1002 assert(isOpenForWriting()); 1003 1004 final switch(_format) with (AudioFileFormat) 1005 { 1006 case mp3: 1007 case flac: 1008 case ogg: 1009 case opus: 1010 case mod: 1011 case xm: 1012 assert(false); // unsupported output encoding 1013 case wav: 1014 { 1015 _wavEncoder.finalizeEncoding(); 1016 break; 1017 } 1018 case unknown: 1019 assert(false); 1020 } 1021 _io.write = null; // prevents further encodings 1022 } 1023 1024 // Finalize encoding and get internal buffer. 1025 // This can be called multiple times, in which cases the stream is finalized only the first time. 1026 const(ubyte)[] finalizeAndGetEncodedResult() @nogc 1027 { 1028 // only callable while appending, else it's a programming error 1029 assert( (memoryContext !is null) && ( memoryContext.bufferCanGrow ) ); 1030 1031 finalizeEncodingIfNeeded(); 1032 return memoryContext.buffer[0..memoryContext.size]; 1033 } 1034 1035 private: 1036 IOCallbacks* _io; 1037 1038 // This type of context is a closure to remember where the data is. 1039 void* userData; // is equal to either fileContext or memoryContext 1040 FileContext* fileContext; 1041 MemoryContext* memoryContext; 1042 1043 // This type of context is a closure to remember where _io and user Data is. 1044 DecoderContext* _decoderContext; 1045 1046 AudioFileFormat _format; 1047 float _sampleRate; 1048 int _numChannels; 1049 long _lengthInFrames; 1050 1051 // Decoders 1052 version(decodeMP3) 1053 { 1054 mp3dec_ex_t* _mp3DecoderNew; // allocated on heap since it's a 16kb object 1055 mp3dec_io_t* _mp3io; 1056 } 1057 version(decodeFLAC) 1058 { 1059 drflac* _flacDecoder; 1060 long _flacPositionFrame; 1061 } 1062 version(decodeOGG) 1063 { 1064 ubyte[] _oggBuffer; // all allocations from the ogg decoder 1065 stb_vorbis* _oggHandle; 1066 } 1067 version(decodeWAV) 1068 { 1069 WAVDecoder _wavDecoder; 1070 } 1071 version(decodeMOD) 1072 { 1073 pocketmod_context* _modDecoder = null; 1074 ubyte[] _modContent = null; // whole buffer, copied 1075 } 1076 version(decodeXM) 1077 { 1078 xm_context_t* _xmDecoder = null; 1079 ubyte* _xmContent = null; 1080 } 1081 1082 version(decodeOPUS) 1083 { 1084 OpusFile _opusDecoder; 1085 short[] _opusBuffer; 1086 long _opusPositionFrame; 1087 } 1088 1089 // Encoder 1090 version(encodeWAV) 1091 { 1092 WAVEncoder _wavEncoder; 1093 } 1094 1095 void startDecoding() @nogc 1096 { 1097 // Create a decoder context 1098 _decoderContext = mallocNew!DecoderContext; 1099 _decoderContext.userDataIO = userData; 1100 _decoderContext.callbacks = _io; 1101 1102 version(decodeOPUS) 1103 { 1104 try 1105 { 1106 _opusDecoder = opusOpen(_io, userData); 1107 assert(_opusDecoder !is null); 1108 _format = AudioFileFormat.opus; 1109 _sampleRate = _opusDecoder.rate; // Note: Opus file are always 48Khz 1110 _numChannels = _opusDecoder.channels(); 1111 _lengthInFrames = _opusDecoder.smpduration(); 1112 _opusPositionFrame = 0; 1113 return; 1114 } 1115 catch(Exception e) 1116 { 1117 destroyFree(e); 1118 } 1119 _opusDecoder = null; 1120 } 1121 1122 version(decodeFLAC) 1123 { 1124 _io.seek(0, false, userData); 1125 1126 // Is it a FLAC? 1127 { 1128 drflac_read_proc onRead = &flac_read; 1129 drflac_seek_proc onSeek = &flac_seek; 1130 void* pUserData = _decoderContext; 1131 _flacDecoder = drflac_open (onRead, onSeek, _decoderContext); 1132 if (_flacDecoder !is null) 1133 { 1134 _format = AudioFileFormat.flac; 1135 _sampleRate = _flacDecoder.sampleRate; 1136 _numChannels = _flacDecoder.channels; 1137 _lengthInFrames = _flacDecoder.totalSampleCount / _numChannels; 1138 _flacPositionFrame = 0; 1139 return; 1140 } 1141 } 1142 } 1143 1144 version(decodeWAV) 1145 { 1146 // Check if it's a WAV. 1147 1148 _io.seek(0, false, userData); 1149 1150 try 1151 { 1152 _wavDecoder = mallocNew!WAVDecoder(_io, userData); 1153 _wavDecoder.scan(); 1154 1155 // WAV detected 1156 _format = AudioFileFormat.wav; 1157 _sampleRate = _wavDecoder._sampleRate; 1158 _numChannels = _wavDecoder._channels; 1159 _lengthInFrames = _wavDecoder._lengthInFrames; 1160 return; 1161 } 1162 catch(Exception e) 1163 { 1164 // not a WAV 1165 destroyFree(e); 1166 } 1167 destroyFree(_wavDecoder); 1168 _wavDecoder = null; 1169 } 1170 1171 version(decodeOGG) 1172 { 1173 _io.seek(0, false, userData); 1174 1175 // Is it an OGG? 1176 { 1177 //"In my test files the maximal-size usage is ~150KB", so let's take a bit more 1178 _oggBuffer.reallocBuffer(200 * 1024); 1179 1180 stb_vorbis_alloc alloc; 1181 alloc.alloc_buffer = cast(ubyte*)(_oggBuffer.ptr); 1182 alloc.alloc_buffer_length_in_bytes = cast(int)(_oggBuffer.length); 1183 1184 int error; 1185 1186 _oggHandle = stb_vorbis_open_file(_io, userData, &error, &alloc); 1187 if (error == VORBIS__no_error) 1188 { 1189 _format = AudioFileFormat.ogg; 1190 _sampleRate = _oggHandle.sample_rate; 1191 _numChannels = _oggHandle.channels; 1192 _lengthInFrames = stb_vorbis_stream_length_in_samples(_oggHandle); 1193 return; 1194 } 1195 else 1196 { 1197 _oggHandle = null; 1198 } 1199 } 1200 } 1201 1202 version(decodeMP3) 1203 { 1204 // Check if it's a MP3. 1205 { 1206 _io.seek(0, false, userData); 1207 1208 ubyte* scratchBuffer = cast(ubyte*) malloc(MINIMP3_BUF_SIZE*2); 1209 scope(exit) free(scratchBuffer); 1210 1211 _mp3io = cast(mp3dec_io_t*) malloc(mp3dec_io_t.sizeof); 1212 _mp3io.read = &mp3_io_read; 1213 _mp3io.read_data = _decoderContext; 1214 _mp3io.seek = &mp3_io_seek; 1215 _mp3io.seek_data = _decoderContext; 1216 1217 if ( mp3dec_detect_cb(_mp3io, scratchBuffer, MINIMP3_BUF_SIZE*2) == 0 ) 1218 { 1219 // This is a MP3. Try to open a stream. 1220 1221 // Allocate a mp3dec_ex_t object 1222 _mp3DecoderNew = cast(mp3dec_ex_t*) malloc(mp3dec_ex_t.sizeof); 1223 1224 int result = mp3dec_ex_open_cb(_mp3DecoderNew, _mp3io, MP3D_SEEK_TO_SAMPLE); 1225 1226 if (0 == result) 1227 { 1228 // MP3 detected 1229 // but it seems we need to iterate all frames to know the length... 1230 _format = AudioFileFormat.mp3; 1231 _sampleRate = _mp3DecoderNew.info.hz; 1232 _numChannels = _mp3DecoderNew.info.channels; 1233 _lengthInFrames = _mp3DecoderNew.samples / _numChannels; 1234 return; 1235 } 1236 else 1237 { 1238 free(_mp3DecoderNew); 1239 _mp3DecoderNew = null; 1240 free(_mp3io); 1241 _mp3io = null; 1242 } 1243 } 1244 } 1245 } 1246 1247 version(decodeXM) 1248 { 1249 { 1250 // we need the first 60 bytes to check if XM 1251 char[60] xmHeader; 1252 int bytes; 1253 1254 _io.seek(0, false, userData); 1255 long lenBytes = _io.getFileLength(userData); 1256 if (lenBytes < 60) 1257 goto not_a_xm; 1258 1259 bytes = _io.read(xmHeader.ptr, 60, userData); 1260 if (bytes != 60) 1261 goto not_a_xm; 1262 1263 if (0 != xm_check_sanity_preload(xmHeader.ptr, 60)) 1264 goto not_a_xm; 1265 1266 _xmContent = cast(ubyte*) malloc(cast(int)lenBytes); 1267 _io.seek(0, false, userData); 1268 bytes = _io.read(_xmContent, cast(int)lenBytes, userData); 1269 if (bytes != cast(int)lenBytes) 1270 goto not_a_xm; 1271 1272 if (0 == xm_create_context_safe(&_xmDecoder, cast(const(char)*)_xmContent, cast(size_t)lenBytes, 44100)) 1273 { 1274 assert(_xmDecoder !is null); 1275 1276 xm_set_max_loop_count(_xmDecoder, 1); 1277 1278 _format = AudioFileFormat.xm; 1279 _sampleRate = 44100.0f; 1280 _numChannels = 2; 1281 _lengthInFrames = audiostreamUnknownLength; 1282 return; 1283 } 1284 1285 not_a_xm: 1286 assert(_xmDecoder == null); 1287 free(_xmContent); 1288 _xmContent = null; 1289 } 1290 } 1291 1292 version(decodeMOD) 1293 { 1294 { 1295 // we need either the first 1084 or 600 bytes if available 1296 _io.seek(0, false, userData); 1297 long lenBytes = _io.getFileLength(userData); 1298 if (lenBytes >= 600) 1299 { 1300 int headerBytes = lenBytes > 1084 ? 1084 : cast(int)lenBytes; 1301 1302 ubyte[1084] header; 1303 int bytes = _io.read(header.ptr, headerBytes, userData); 1304 1305 if (_pocketmod_ident(null, header.ptr, bytes)) 1306 { 1307 // This is a MOD, allocate a proper context, and read the whole file. 1308 _modDecoder = cast(pocketmod_context*) malloc(pocketmod_context.sizeof); 1309 1310 // Read whole .mod in a buffer, since the decoder work all from memory 1311 _io.seek(0, false, userData); 1312 _modContent.reallocBuffer(cast(size_t)lenBytes); 1313 bytes = _io.read(_modContent.ptr, cast(int)lenBytes, userData); 1314 1315 if (pocketmod_init(_modDecoder, _modContent.ptr, bytes, 44100)) 1316 { 1317 _format = AudioFileFormat.mod; 1318 _sampleRate = 44100.0f; 1319 _numChannels = 2; 1320 _lengthInFrames = audiostreamUnknownLength; 1321 return; 1322 } 1323 } 1324 } 1325 } 1326 } 1327 1328 _format = AudioFileFormat.unknown; 1329 _sampleRate = float.nan; 1330 _numChannels = 0; 1331 _lengthInFrames = -1; 1332 1333 throw mallocNew!Exception("Cannot decode stream: unrecognized encoding."); 1334 } 1335 1336 void startEncoding(AudioFileFormat format, float sampleRate, int numChannels) @nogc 1337 { 1338 _format = format; 1339 _sampleRate = sampleRate; 1340 _numChannels = numChannels; 1341 1342 final switch(format) with (AudioFileFormat) 1343 { 1344 case mp3: 1345 throw mallocNew!Exception("Unsupported encoding format: MP3"); 1346 case flac: 1347 throw mallocNew!Exception("Unsupported encoding format: FLAC"); 1348 case ogg: 1349 throw mallocNew!Exception("Unsupported encoding format: OGG"); 1350 case opus: 1351 throw mallocNew!Exception("Unsupported encoding format: Opus"); 1352 case mod: 1353 throw mallocNew!Exception("Unsupported encoding format: MOD"); 1354 case xm: 1355 throw mallocNew!Exception("Unsupported encoding format: XM"); 1356 case wav: 1357 { 1358 // Note: fractional sample rates not supported by WAV, signal an integer one 1359 int isampleRate = cast(int)(sampleRate + 0.5f); 1360 _wavEncoder = mallocNew!WAVEncoder(_io, userData, isampleRate, numChannels ); 1361 break; 1362 } 1363 case unknown: 1364 throw mallocNew!Exception("Can't encode using 'unknown' coding"); 1365 } 1366 } 1367 1368 void finalizeEncodingIfNeeded() @nogc 1369 { 1370 if (_io && (_io.write !is null)) // if we have been encoding something 1371 { 1372 finalizeEncoding(); 1373 } 1374 } 1375 } 1376 1377 // AudioStream should be able to go on a smallish 32-bit stack, 1378 // and malloc the rest on the heap when needed. 1379 static assert(AudioStream.sizeof <= 256); 1380 1381 private: // not meant to be imported at all 1382 1383 1384 1385 // Internal object for audio-formats 1386 1387 1388 // File callbacks 1389 // The file callbacks are using the C stdlib. 1390 1391 struct FileContext // this is what is passed to I/O when used in file mode 1392 { 1393 // Used when streaming of writing a file 1394 FILE* file = null; 1395 1396 // Size of the file in bytes, only used when reading/writing a file. 1397 long fileSize; 1398 1399 // Initialize this context 1400 void initialize(const(char)[] path, bool forWrite) @nogc 1401 { 1402 CString strZ = CString(path); 1403 file = fopen(strZ.storage, forWrite ? "wb".ptr : "rb".ptr); 1404 if (file is null) 1405 throw mallocNew!Exception("File not found"); 1406 // finds the size of the file 1407 fseek(file, 0, SEEK_END); 1408 fileSize = ftell(file); 1409 fseek(file, 0, SEEK_SET); 1410 } 1411 } 1412 1413 long file_tell(void* userData) nothrow @nogc 1414 { 1415 FileContext* context = cast(FileContext*)userData; 1416 return ftell(context.file); 1417 } 1418 1419 bool file_seek(long offset, bool relative, void* userData) nothrow @nogc 1420 { 1421 FileContext* context = cast(FileContext*)userData; 1422 assert(offset <= int.max); 1423 int r = fseek(context.file, cast(int)offset, relative ? SEEK_CUR : SEEK_SET); // Limitations: file larger than 2gb not supported 1424 return r == 0; 1425 } 1426 1427 long file_getFileLength(void* userData) nothrow @nogc 1428 { 1429 FileContext* context = cast(FileContext*)userData; 1430 return context.fileSize; 1431 } 1432 1433 int file_read(void* outData, int bytes, void* userData) nothrow @nogc 1434 { 1435 FileContext* context = cast(FileContext*)userData; 1436 size_t bytesRead = fread(outData, 1, bytes, context.file); 1437 return cast(int)bytesRead; 1438 } 1439 1440 int file_write(void* inData, int bytes, void* userData) nothrow @nogc 1441 { 1442 FileContext* context = cast(FileContext*)userData; 1443 size_t bytesWritten = fwrite(inData, 1, bytes, context.file); 1444 return cast(int)bytesWritten; 1445 } 1446 1447 bool file_skip(int bytes, void* userData) nothrow @nogc 1448 { 1449 FileContext* context = cast(FileContext*)userData; 1450 return (0 == fseek(context.file, bytes, SEEK_CUR)); 1451 } 1452 1453 bool file_flush(void* userData) nothrow @nogc 1454 { 1455 FileContext* context = cast(FileContext*)userData; 1456 return ( fflush(context.file) == 0 ); 1457 } 1458 1459 // Memory read callback 1460 // Using the read buffer instead 1461 1462 struct MemoryContext 1463 { 1464 bool bufferIsOwned; 1465 bool bufferCanGrow; 1466 1467 // Buffer 1468 ubyte* buffer = null; 1469 1470 size_t size; // current buffer size 1471 size_t cursor; // where we are in the buffer 1472 size_t capacity; // max buffer size before realloc 1473 1474 void initializeWithConstantInput(const(ubyte)* data, size_t length) nothrow @nogc 1475 { 1476 // Make a copy of the input buffer, since it could be temporary. 1477 bufferIsOwned = true; 1478 bufferCanGrow = false; 1479 1480 buffer = mallocDup(data[0..length]).ptr; // Note: the copied slice is made mutable. 1481 size = length; 1482 cursor = 0; 1483 capacity = length; 1484 } 1485 1486 void initializeWithExternalOutputBuffer(ubyte* data, size_t length) nothrow @nogc 1487 { 1488 bufferIsOwned = false; 1489 bufferCanGrow = false; 1490 buffer = data; 1491 size = 0; 1492 cursor = 0; 1493 capacity = length; 1494 } 1495 1496 void initializeWithInternalGrowableBuffer() nothrow @nogc 1497 { 1498 bufferIsOwned = true; 1499 bufferCanGrow = true; 1500 buffer = null; 1501 size = 0; 1502 cursor = 0; 1503 capacity = 0; 1504 } 1505 1506 ~this() 1507 { 1508 if (bufferIsOwned) 1509 { 1510 if (buffer !is null) 1511 { 1512 free(buffer); 1513 buffer = null; 1514 } 1515 } 1516 } 1517 } 1518 1519 long memory_tell(void* userData) nothrow @nogc 1520 { 1521 MemoryContext* context = cast(MemoryContext*)userData; 1522 return cast(long)(context.cursor); 1523 } 1524 1525 bool memory_seek(long offset, bool relative, void* userData) nothrow @nogc 1526 { 1527 MemoryContext* context = cast(MemoryContext*)userData; 1528 if (relative) offset += context.cursor; 1529 if (offset < 0) 1530 return false; 1531 1532 bool r = true; 1533 if (offset >= context.size) // can't seek past end of buffer, stick to the end so that read return 0 byte 1534 { 1535 offset = context.size; 1536 r = false; 1537 } 1538 context.cursor = cast(size_t)offset; // Note: memory streams larger than 2gb not supported 1539 return r; 1540 } 1541 1542 long memory_getFileLength(void* userData) nothrow @nogc 1543 { 1544 MemoryContext* context = cast(MemoryContext*)userData; 1545 return cast(long)(context.size); 1546 } 1547 1548 int memory_read(void* outData, int bytes, void* userData) nothrow @nogc 1549 { 1550 MemoryContext* context = cast(MemoryContext*)userData; 1551 size_t cursor = context.cursor; 1552 size_t size = context.size; 1553 size_t available = size - cursor; 1554 if (bytes < available) 1555 { 1556 outData[0..bytes] = context.buffer[cursor..cursor + bytes]; 1557 context.cursor += bytes; 1558 return bytes; 1559 } 1560 else 1561 { 1562 outData[0..available] = context.buffer[cursor..cursor + available]; 1563 context.cursor = context.size; 1564 return cast(int)available; 1565 } 1566 } 1567 1568 int memory_write_limited(void* inData, int bytes, void* userData) nothrow @nogc 1569 { 1570 MemoryContext* context = cast(MemoryContext*)userData; 1571 size_t cursor = context.cursor; 1572 size_t size = context.size; 1573 size_t available = size - cursor; 1574 ubyte* buffer = context.buffer; 1575 ubyte* source = cast(ubyte*) inData; 1576 1577 if (cursor + bytes > available) 1578 { 1579 bytes = cast(int)(available - cursor); 1580 } 1581 1582 buffer[cursor..(cursor + bytes)] = source[0..bytes]; 1583 context.size += bytes; 1584 context.cursor += bytes; 1585 return bytes; 1586 } 1587 1588 int memory_write_append(void* inData, int bytes, void* userData) nothrow @nogc 1589 { 1590 MemoryContext* context = cast(MemoryContext*)userData; 1591 size_t cursor = context.cursor; 1592 size_t size = context.size; 1593 size_t available = size - cursor; 1594 ubyte* buffer = context.buffer; 1595 ubyte* source = cast(ubyte*) inData; 1596 1597 if (cursor + bytes > available) 1598 { 1599 size_t oldSize = context.capacity; 1600 size_t newSize = cursor + bytes; 1601 if (newSize < oldSize * 2 + 1) 1602 newSize = oldSize * 2 + 1; 1603 buffer = cast(ubyte*) realloc(buffer, newSize); 1604 context.capacity = newSize; 1605 1606 assert( cursor + bytes <= available ); 1607 } 1608 1609 buffer[cursor..(cursor + bytes)] = source[0..bytes]; 1610 context.size += bytes; 1611 context.cursor += bytes; 1612 return bytes; 1613 } 1614 1615 bool memory_skip(int bytes, void* userData) nothrow @nogc 1616 { 1617 MemoryContext* context = cast(MemoryContext*)userData; 1618 context.cursor += bytes; 1619 return context.cursor <= context.size; 1620 } 1621 1622 bool memory_flush(void* userData) nothrow @nogc 1623 { 1624 // do nothing, no flushign to do for memory 1625 return true; 1626 } 1627 1628 1629 // Decoder context 1630 struct DecoderContext 1631 { 1632 void* userDataIO; 1633 IOCallbacks* callbacks; 1634 } 1635 1636 // MP3 decoder read callback 1637 static int mp3ReadDelegate(void[] buf, void* userDataDecoder) @nogc nothrow 1638 { 1639 DecoderContext* context = cast(DecoderContext*) userDataDecoder; 1640 1641 // read bytes into the buffer, return number of bytes read or 0 for EOF, -1 on error 1642 // will never be called with empty buffer, or buffer more than 128KB 1643 1644 int bytes = context.callbacks.read(buf.ptr, cast(int)(buf.length), context.userDataIO); 1645 return bytes; 1646 } 1647 1648 1649 // FLAC decoder read callbacks 1650 1651 size_t flac_read(void* pUserData, void* pBufferOut, size_t bytesToRead) @nogc nothrow 1652 { 1653 DecoderContext* context = cast(DecoderContext*) pUserData; 1654 return context.callbacks.read(pBufferOut, cast(int)(bytesToRead), context.userDataIO); 1655 } 1656 1657 bool flac_seek(void* pUserData, int offset, drflac_seek_origin origin) @nogc nothrow 1658 { 1659 DecoderContext* context = cast(DecoderContext*) pUserData; 1660 if (origin == drflac_seek_origin_start) 1661 { 1662 context.callbacks.seek(offset, false, context.userDataIO); 1663 } 1664 else if (origin == drflac_seek_origin_current) 1665 { 1666 context.callbacks.seek(offset, true, context.userDataIO); 1667 } 1668 return true; 1669 } 1670 1671 // MP3 decoder read callbacks 1672 1673 size_t mp3_io_read(void *buf, size_t size, void *user_data) @nogc nothrow 1674 { 1675 DecoderContext* context = cast(DecoderContext*) user_data; 1676 return context.callbacks.read(buf, cast(int)(size), context.userDataIO); 1677 } 1678 1679 int mp3_io_seek(ulong position, void *user_data) @nogc nothrow 1680 { 1681 DecoderContext* context = cast(DecoderContext*) user_data; 1682 context.callbacks.seek(position, false, context.userDataIO); 1683 return 0; // doesn't detect seeking errors 1684 }