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: 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; 19 version(decodeFLAC) import audioformats.drflac; 20 version(decodeWAV) import audioformats.wav; 21 22 /// Libray for sound file decoding and encoding. 23 /// All operations are blocking, and should not be done in a real-time audio thread. 24 /// (besides, you would also need resampling for playback). 25 /// Also not thread-safe, synchronization in on yours. 26 27 /// Format of audio files. 28 enum AudioFileFormat 29 { 30 wav, /// WAVE format 31 mp3, /// MP3 format 32 flac, /// FLAC foramt 33 unknown 34 } 35 36 /// Returns: String representation of an `AudioFileFormat`. 37 string convertAudioFileFormatToString(AudioFileFormat fmt) 38 { 39 final switch(fmt) with (AudioFileFormat) 40 { 41 case wav: return "wav"; 42 case mp3: return "mp3"; 43 case flac: return "flac"; 44 case unknown: return "unknown"; 45 } 46 } 47 48 49 /// The length of things you shouldn't query a length about: 50 /// - files that are being written 51 /// - audio files you don't know the extent 52 enum audiostreamUnknownLength = -1; 53 54 /// An AudioStream is a pointer to a dynamically allocated `Stream`. 55 public struct AudioStream 56 { 57 public: // This is also part of the public API 58 59 60 /// Opens an audio stream that decodes from a file. 61 /// This stream will be opened for reading only. 62 /// 63 /// Params: 64 /// path An UTF-8 path to the sound file. 65 /// 66 /// Note: throws a manually allocated exception in case of error. Free it with `dplug.core.destroyFree`. 67 void openFromFile(const(char)[] path) @nogc 68 { 69 cleanUp(); 70 71 fileContext = mallocNew!FileContext(); 72 fileContext.initialize(path, false); 73 userData = fileContext; 74 75 _io = mallocNew!IOCallbacks(); 76 _io.seek = &file_seek; 77 _io.tell = &file_tell; 78 _io.getFileLength = &file_getFileLength; 79 _io.read = &file_read; 80 _io.write = null; 81 _io.skip = &file_skip; 82 _io.flush = null; 83 84 startDecoding(); 85 } 86 87 /// Opens an audio stream that decodes from memory. 88 /// This stream will be opened for reading only. 89 /// Note: throws a manually allocated exception in case of error. Free it with `dplug.core.destroyFree`. 90 /// 91 /// Params: inputData The whole file to decode. 92 void openFromMemory(const(ubyte)[] inputData) @nogc 93 { 94 cleanUp(); 95 96 memoryContext = mallocNew!MemoryContext(); 97 memoryContext.initializeWithConstantInput(inputData.ptr, inputData.length); 98 99 userData = memoryContext; 100 101 _io = mallocNew!IOCallbacks(); 102 _io.seek = &memory_seek; 103 _io.tell = &memory_tell; 104 _io.getFileLength = &memory_getFileLength; 105 _io.read = &memory_read; 106 _io.write = null; 107 _io.skip = &memory_skip; 108 _io.flush = null; 109 110 startDecoding(); 111 } 112 113 /// Opens an audio stream that writes to file. 114 /// This stream will be opened for writing only. 115 /// Note: throws a manually allocated exception in case of error. Free it with `dplug.core.destroyFree`. 116 /// 117 /// Params: 118 /// path An UTF-8 path to the sound file. 119 /// format Audio file format to generate. 120 /// sampleRate Sample rate of this audio stream. This samplerate might be rounded up to the nearest integer number. 121 /// numChannels Number of channels of this audio stream. 122 void openToFile(const(char)[] path, AudioFileFormat format, float sampleRate, int numChannels) @nogc 123 { 124 cleanUp(); 125 126 fileContext = mallocNew!FileContext(); 127 fileContext.initialize(path, true); 128 userData = fileContext; 129 130 _io = mallocNew!IOCallbacks(); 131 _io.seek = &file_seek; 132 _io.tell = &file_tell; 133 _io.getFileLength = null; 134 _io.read = null; 135 _io.write = &file_write; 136 _io.skip = null; 137 _io.flush = &file_flush; 138 139 startEncoding(format, sampleRate, numChannels); 140 } 141 142 /// Opens an audio stream that writes to a dynamically growable output buffer. 143 /// This stream will be opened for writing only. 144 /// Access to the internal buffer after encoding with `finalizeAndGetEncodedResult`. 145 /// Note: throws a manually allocated exception in case of error. Free it with `dplug.core.destroyFree`. 146 /// 147 /// Params: 148 /// format Audio file format to generate. 149 /// sampleRate Sample rate of this audio stream. This samplerate might be rounded up to the nearest integer number. 150 /// numChannels Number of channels of this audio stream. 151 void openToBuffer(AudioFileFormat format, float sampleRate, int numChannels) @nogc 152 { 153 cleanUp(); 154 155 memoryContext = mallocNew!MemoryContext(); 156 memoryContext.initializeWithInternalGrowableBuffer(); 157 userData = memoryContext; 158 159 _io = mallocNew!IOCallbacks(); 160 _io.seek = &memory_seek; 161 _io.tell = &memory_tell; 162 _io.getFileLength = null; 163 _io.read = null; 164 _io.write = &memory_write_append; 165 _io.skip = null; 166 _io.flush = &memory_flush; 167 168 startEncoding(format, sampleRate, numChannels); 169 } 170 171 /// Opens an audio stream that writes to a pre-defined area in memory of `maxLength` bytes. 172 /// This stream will be opened for writing only. 173 /// Destroy this stream with `closeAudioStream`. 174 /// Note: throws a manually allocated exception in case of error. Free it with `dplug.core.destroyFree`. 175 /// 176 /// Params: 177 /// data Pointer to output memory. 178 /// size_t maxLength. 179 /// format Audio file format to generate. 180 /// sampleRate Sample rate of this audio stream. This samplerate might be rounded up to the nearest integer number. 181 /// numChannels Number of channels of this audio stream. 182 void openToMemory(ubyte* data, 183 size_t maxLength, 184 AudioFileFormat format, 185 float sampleRate, 186 int numChannels) @nogc 187 { 188 cleanUp(); 189 190 memoryContext = mallocNew!MemoryContext(); 191 memoryContext.initializeWithExternalOutputBuffer(data, maxLength); 192 userData = memoryContext; 193 194 _io = mallocNew!IOCallbacks(); 195 _io.seek = &memory_seek; 196 _io.tell = &memory_tell; 197 _io.getFileLength = null; 198 _io.read = null; 199 _io.write = &memory_write_limited; 200 _io.skip = null; 201 _io.flush = &memory_flush; 202 203 startEncoding(format, sampleRate, numChannels); 204 } 205 206 ~this() @nogc 207 { 208 cleanUp(); 209 } 210 211 void cleanUp() @nogc 212 { 213 // Write the last needed bytes if needed 214 finalizeEncodingIfNeeded(); 215 216 version(decodeMP3) 217 { 218 if (_mp3Decoder !is null) 219 { 220 destroyFree(_mp3Decoder); 221 _mp3Decoder = null; 222 } 223 } 224 225 version(decodeFLAC) 226 { 227 if (_flacDecoder !is null) 228 { 229 drflac_close(_flacDecoder); 230 _flacDecoder = null; 231 } 232 } 233 234 version(decodeWAV) 235 { 236 if (_wavDecoder !is null) 237 { 238 destroyFree(_wavDecoder); 239 _wavDecoder = null; 240 } 241 } 242 243 version(encodeWAV) 244 { 245 if (_wavEncoder !is null) 246 { 247 destroyFree(_wavEncoder); 248 _wavEncoder = null; 249 } 250 } 251 252 if (_decoderContext) 253 { 254 destroyFree(_decoderContext); 255 _decoderContext = null; 256 } 257 258 if (fileContext !is null) 259 { 260 if (fileContext.file !is null) 261 { 262 int result = fclose(fileContext.file); 263 if (result) 264 throw mallocNew!Exception("Closing of audio file errored"); 265 } 266 destroyFree(fileContext); 267 fileContext = null; 268 } 269 270 if (memoryContext !is null) 271 { 272 // TODO destroy buffer if any and owned 273 destroyFree(memoryContext); 274 memoryContext = null; 275 } 276 277 if (_io !is null) 278 { 279 // TODO destroy buffer if any and owned 280 destroyFree(_io); 281 _io = null; 282 } 283 } 284 285 /// Returns: File format of this stream. 286 AudioFileFormat getFormat() nothrow @nogc 287 { 288 return _format; 289 } 290 291 /// Returns: File format of this stream. 292 int getNumChannels() nothrow @nogc 293 { 294 return _numChannels; 295 } 296 297 /// Returns: Length of this stream in frames. 298 /// Note: may return `audiostreamUnknownLength` if the length is unknown. 299 long getLengthInFrames() nothrow @nogc 300 { 301 return _lengthInFrames; 302 } 303 304 /// Returns: Sample-rate of this stream in Hz. 305 float getSamplerate() nothrow @nogc 306 { 307 return _sampleRate; 308 } 309 310 /// Read interleaved float samples. 311 /// `outData` must have enough room for `frames` * `channels` decoded samples. 312 int readSamplesFloat(float* outData, int frames) @nogc 313 { 314 // If you fail here, you are using this `AudioStream` for decoding: 315 // - after it has been destroyed, 316 // - or it was created for encoding instead 317 assert(_io && _io.read !is null); 318 319 final switch(_format) 320 { 321 case AudioFileFormat.flac: 322 { 323 version(decodeFLAC) 324 { 325 assert(_flacDecoder !is null); 326 327 int* integerData = cast(int*)outData; 328 int samples = cast(int) drflac_read_s32(_flacDecoder, frames, integerData); 329 330 // "Samples are always output as interleaved signed 32-bit PCM." 331 // Convert to float with type-punning. Note that this looses some precision. 332 double factor = 1.0 / int.max; 333 foreach(n; 0..samples) 334 { 335 outData[n] = integerData[n] * factor; 336 } 337 return samples / _numChannels; 338 } 339 else 340 { 341 assert(false); // Impossible 342 } 343 } 344 case AudioFileFormat.mp3: 345 { 346 version(decodeMP3) 347 { 348 assert(_mp3Decoder !is null); 349 350 if (!_mp3Decoder.valid) 351 return 0; 352 353 // Ensure the read buffer is filled with at least `frames` interleaved frames. 354 355 int samplesNeeded = frames * _numChannels; 356 357 // Decode MP3 frame until we have `frames` samples or the file is terminated. 358 int read = 0; 359 while ( _mp3Decoder.valid && ( read < samplesNeeded ) ) 360 { 361 int numDecoded = cast(int)(_mp3Decoder.frameSamples.length); 362 size_t initialLength = _readBuffer.length(); 363 _readBuffer.resize(initialLength + numDecoded); 364 float invShortMax = 1.0 / cast(float)(short.max); 365 // Convert to float 366 // TODO is this correct? 367 foreach(n; 0..numDecoded) 368 { 369 import core.stdc.stdio; 370 _readBuffer[initialLength + n] = _mp3Decoder.frameSamples[n] * invShortMax; 371 } 372 read += numDecoded; 373 _mp3Decoder.decodeNextFrame(&mp3ReadDelegate); 374 } 375 376 if (read >= samplesNeeded) 377 { 378 outData[0..samplesNeeded] = _readBuffer[0..samplesNeeded]; 379 int remaining = read - samplesNeeded; 380 if (remaining > 0) 381 memmove(_readBuffer.ptr, &_readBuffer[samplesNeeded], float.sizeof * remaining); 382 _readBuffer.resize(remaining); // Note: Vec should keep that capacity and not free the memory. 383 return frames; 384 } 385 else 386 { 387 // How many sample can we produce? 388 int completeSamples = read / _numChannels; 389 outData[0..completeSamples] = _readBuffer[0..completeSamples]; 390 _readBuffer.resize(0); 391 return completeSamples; 392 } 393 } 394 else 395 { 396 assert(false); // Impossible 397 } 398 } 399 case AudioFileFormat.wav: 400 version(decodeWAV) 401 { 402 assert(_wavDecoder !is null); 403 int readFrames = _wavDecoder.readSamples(outData, frames); 404 return readFrames; 405 } 406 else 407 { 408 assert(false); // Impossible 409 } 410 411 case AudioFileFormat.unknown: 412 // One shouldn't ever get there, since in this case 413 // opening has failed. 414 assert(false); 415 } 416 } 417 ///ditto 418 int readSamplesFloat(float[] outData) @nogc 419 { 420 assert( (outData.length % _numChannels) == 0); 421 return readSamplesFloat(outData.ptr, cast(int)(outData.length / _numChannels) ); 422 } 423 424 /// Write interleaved float samples. 425 /// `inData` must have enough data for `frames` * `channels` samples. 426 int writeSamplesFloat(float* inData, int frames) nothrow @nogc 427 { 428 // If you fail here, you are using this `AudioStream` for encoding: 429 // - after it has been destroyed, 430 // - or after encoding has been finalized with 431 // - or it was created for encoding instead 432 assert(_io && _io.write !is null); 433 434 final switch(_format) 435 { 436 case AudioFileFormat.mp3: 437 case AudioFileFormat.flac: 438 case AudioFileFormat.unknown: 439 { 440 assert(false); // Shouldn't have arrived here, such encoding aren't supported. 441 } 442 case AudioFileFormat.wav: 443 { 444 version(encodeWAV) 445 { 446 return _wavEncoder.writeSamples(inData, frames); 447 } 448 else 449 { 450 assert(false, "no support for WAV encoding"); 451 } 452 } 453 } 454 } 455 ///ditto 456 int writeSamplesFloat(float[] inData) nothrow @nogc 457 { 458 assert( (inData.length % _numChannels) == 0); 459 return writeSamplesFloat(inData.ptr, cast(int)(inData.length / _numChannels)); 460 } 461 462 /// Call `fflush()` on written samples, if any. 463 /// Automatically done by `audiostreamClose`. 464 /// It is only useful for streamable output formats, that want to flush things to disk. 465 void flush() nothrow @nogc 466 { 467 assert( _io && (_io.write !is null) ); 468 _io.flush(userData); 469 } 470 471 /// Finalize encoding. After finalization, further writes are not possible anymore 472 /// however the stream is considered complete and valid for storage. 473 void finalizeEncoding() @nogc 474 { 475 // If you crash here, it's because `finalizeEncoding` has been called twice. 476 assert( _io && (_io.write !is null) ); 477 478 final switch(_format) with (AudioFileFormat) 479 { 480 case mp3: 481 case flac: 482 assert(false); // unsupported output encoding 483 case wav: 484 { 485 _wavEncoder.finalizeEncoding(); 486 break; 487 } 488 case unknown: 489 assert(false); 490 } 491 _io.write = null; // prevents further encodings 492 } 493 494 // Finalize encoding and get internal buffer. 495 // This can be called multiple times, in which cases the stream is finalized only the first time. 496 const(ubyte)[] finalizeAndGetEncodedResult() @nogc 497 { 498 // only callable while appending, else it's a programming error 499 assert( (memoryContext !is null) && ( memoryContext.bufferCanGrow ) ); 500 501 finalizeEncodingIfNeeded(); 502 return memoryContext.buffer[0..memoryContext.size]; 503 } 504 505 private: 506 IOCallbacks* _io; 507 508 // This type of context is a closure to remember where the data is. 509 void* userData; // is equal to either fileContext or memoryContext 510 FileContext* fileContext; 511 MemoryContext* memoryContext; 512 513 // This type of context is a closure to remember where _io and user Data is. 514 DecoderContext* _decoderContext; 515 516 AudioFileFormat _format; 517 float _sampleRate; 518 int _numChannels; 519 long _lengthInFrames; 520 521 // Decoders 522 version(decodeMP3) 523 { 524 MP3Decoder _mp3Decoder; 525 Vec!float _readBuffer; 526 } 527 version(decodeFLAC) 528 { 529 drflac* _flacDecoder; 530 } 531 version(decodeWAV) 532 { 533 WAVDecoder _wavDecoder; 534 } 535 536 // Encoder 537 version(encodeWAV) 538 { 539 WAVEncoder _wavEncoder; 540 } 541 542 bool isOpenedForWriting() nothrow @nogc 543 { 544 // Note: 545 // * when opened for reading, I/O operations given are: seek/tell/getFileLength/read. 546 // * when opened for writing, I/O operations given are: seek/tell/write. 547 return (_io !is null) && (_io.read is null); 548 } 549 550 void startDecoding() @nogc 551 { 552 // Create a decoder context 553 _decoderContext = mallocNew!DecoderContext; 554 _decoderContext.userDataIO = userData; 555 _decoderContext.callbacks = _io; 556 557 version(decodeFLAC) 558 { 559 _io.seek(0, false, userData); 560 561 // Is it a FLAC? 562 { 563 drflac_read_proc onRead = &flac_read; 564 drflac_seek_proc onSeek = &flac_seek; 565 void* pUserData = _decoderContext; 566 _flacDecoder = drflac_open (onRead, onSeek, _decoderContext); 567 if (_flacDecoder !is null) 568 { 569 _format = AudioFileFormat.flac; 570 _sampleRate = _flacDecoder.sampleRate; 571 _numChannels = _flacDecoder.channels; 572 _lengthInFrames = _flacDecoder.totalSampleCount / _numChannels; 573 return; 574 } 575 } 576 } 577 578 version(decodeWAV) 579 { 580 // Check if it's a WAV. 581 582 _io.seek(0, false, userData); 583 584 try 585 { 586 _wavDecoder = mallocNew!WAVDecoder(_io, userData); 587 _wavDecoder.scan(); 588 589 // WAV detected 590 _format = AudioFileFormat.wav; 591 _sampleRate = _wavDecoder._sampleRate; 592 _numChannels = _wavDecoder._channels; 593 _lengthInFrames = _wavDecoder._lengthInFrames; 594 return; 595 } 596 catch(Exception e) 597 { 598 // not a WAV 599 destroyFree(e); 600 } 601 destroyFree(_wavDecoder); 602 } 603 604 version(decodeMP3) 605 { 606 // Check if it's a MP3. 607 // minimp3 need a delegate 608 609 _io.seek(0, false, userData); 610 611 MP3Info info = mp3Scan(&mp3ReadDelegate, _decoderContext); 612 613 if (info.valid) 614 { 615 // MP3 detected 616 _format = AudioFileFormat.mp3; 617 _sampleRate = info.sampleRate; 618 _numChannels = info.channels; 619 _lengthInFrames = info.samples; 620 621 _io.seek(0, false, userData); 622 _mp3Decoder = mallocNew!MP3Decoder(&mp3ReadDelegate, _decoderContext); 623 624 _readBuffer = makeVec!float(); 625 626 if (!_mp3Decoder.valid) 627 throw mallocNew!Exception("invalid MP3 file"); 628 629 return; 630 } 631 } 632 } 633 634 void startEncoding(AudioFileFormat format, float sampleRate, int numChannels) @nogc 635 { 636 _format = format; 637 _sampleRate = sampleRate; 638 _numChannels = numChannels; 639 640 final switch(format) with (AudioFileFormat) 641 { 642 case mp3: 643 throw mallocNew!Exception("Unsupported encoding format: MP3"); 644 case flac: 645 throw mallocNew!Exception("Unsupported encoding format: FLAC"); 646 case wav: 647 { 648 // Note: fractional sample rates not supported by WAV, signal an integer one 649 int isampleRate = cast(int)(sampleRate + 0.5f); 650 _wavEncoder = mallocNew!WAVEncoder(_io, userData, isampleRate, numChannels ); 651 break; 652 } 653 case unknown: 654 throw mallocNew!Exception("Can't encode using 'unknown' coding"); 655 } 656 } 657 658 void finalizeEncodingIfNeeded() @nogc 659 { 660 if (_io && (_io.write !is null)) // if we have been encoding something 661 { 662 finalizeEncoding(); 663 } 664 } 665 } 666 667 private: // not meant to be imported at all 668 669 670 671 // Internal object for audio-formats 672 673 674 675 676 // File callbacks 677 // The file callbacks are using the C stdlib. 678 679 struct FileContext // this is what is passed to I/O when used in file mode 680 { 681 // Used when streaming of writing a file 682 FILE* file = null; 683 684 // Size of the file in bytes, only used when reading/writing a file. 685 long fileSize; 686 687 // Initialize this context 688 void initialize(const(char)[] path, bool forWrite) @nogc 689 { 690 CString strZ = CString(path); 691 file = fopen(strZ.storage, forWrite ? "wb".ptr : "rb".ptr); 692 693 // finds the size of the file 694 fseek(file, 0, SEEK_END); 695 fileSize = ftell(file); 696 fseek(file, 0, SEEK_SET); 697 } 698 } 699 700 long file_tell(void* userData) nothrow @nogc 701 { 702 FileContext* context = cast(FileContext*)userData; 703 return ftell(context.file); 704 } 705 706 void file_seek(long offset, bool relative, void* userData) nothrow @nogc 707 { 708 FileContext* context = cast(FileContext*)userData; 709 assert(offset <= int.max); 710 fseek(context.file, cast(int)offset, relative ? SEEK_CUR : SEEK_SET); // Limitations: file larger than 2gb not supported 711 } 712 713 long file_getFileLength(void* userData) nothrow @nogc 714 { 715 FileContext* context = cast(FileContext*)userData; 716 return context.fileSize; 717 } 718 719 int file_read(void* outData, int bytes, void* userData) nothrow @nogc 720 { 721 FileContext* context = cast(FileContext*)userData; 722 size_t bytesRead = fread(outData, 1, bytes, context.file); 723 return cast(int)bytesRead; 724 } 725 726 int file_write(void* inData, int bytes, void* userData) nothrow @nogc 727 { 728 FileContext* context = cast(FileContext*)userData; 729 size_t bytesWritten = fwrite(inData, 1, bytes, context.file); 730 return cast(int)bytesWritten; 731 } 732 733 bool file_skip(int bytes, void* userData) nothrow @nogc 734 { 735 FileContext* context = cast(FileContext*)userData; 736 return (0 == fseek(context.file, bytes, SEEK_CUR)); 737 } 738 739 bool file_flush(void* userData) nothrow @nogc 740 { 741 FileContext* context = cast(FileContext*)userData; 742 return ( fflush(context.file) == 0 ); 743 } 744 745 // Memory read callback 746 // Using the read buffer instead 747 748 struct MemoryContext 749 { 750 bool bufferIsOwned; 751 bool bufferCanGrow; 752 753 // Buffer 754 ubyte* buffer = null; 755 756 size_t size; // current buffer size 757 size_t cursor; // where we are in the buffer 758 size_t capacity; // max buffer size before realloc 759 760 void initializeWithConstantInput(const(ubyte)* data, size_t length) nothrow @nogc 761 { 762 // Make a copy of the input buffer, since it could be temporary. 763 bufferIsOwned = true; 764 bufferCanGrow = false; 765 766 buffer = mallocDup(data[0..length]).ptr; // Note: the copied slice is made mutable. 767 size = length; 768 cursor = 0; 769 capacity = length; 770 } 771 772 void initializeWithExternalOutputBuffer(ubyte* data, size_t length) nothrow @nogc 773 { 774 bufferIsOwned = false; 775 bufferCanGrow = false; 776 buffer = data; 777 size = 0; 778 cursor = 0; 779 capacity = length; 780 } 781 782 void initializeWithInternalGrowableBuffer() nothrow @nogc 783 { 784 bufferIsOwned = true; 785 bufferCanGrow = true; 786 buffer = null; 787 size = 0; 788 cursor = 0; 789 capacity = 0; 790 } 791 792 ~this() 793 { 794 if (bufferIsOwned) 795 { 796 if (buffer !is null) 797 { 798 free(buffer); 799 buffer = null; 800 } 801 } 802 } 803 } 804 805 long memory_tell(void* userData) nothrow @nogc 806 { 807 MemoryContext* context = cast(MemoryContext*)userData; 808 return cast(long)(context.cursor); 809 } 810 811 void memory_seek(long offset, bool relative, void* userData) nothrow @nogc 812 { 813 MemoryContext* context = cast(MemoryContext*)userData; 814 if (relative) offset += context.cursor; 815 if (offset >= context.size) // can't seek past end of buffer, stick to the end so that read return 0 byte 816 offset = context.size; 817 context.cursor = cast(size_t)offset; // Note: memory streams larger than 2gb not supported 818 } 819 820 long memory_getFileLength(void* userData) nothrow @nogc 821 { 822 MemoryContext* context = cast(MemoryContext*)userData; 823 return cast(long)(context.size); 824 } 825 826 int memory_read(void* outData, int bytes, void* userData) nothrow @nogc 827 { 828 MemoryContext* context = cast(MemoryContext*)userData; 829 size_t cursor = context.cursor; 830 size_t size = context.size; 831 size_t available = size - cursor; 832 if (bytes < available) 833 { 834 outData[0..bytes] = context.buffer[cursor..cursor + bytes]; 835 context.cursor += bytes; 836 return bytes; 837 } 838 else 839 { 840 outData[0..available] = context.buffer[cursor..cursor + available]; 841 context.cursor = context.size; 842 return cast(int)available; 843 } 844 } 845 846 int memory_write_limited(void* inData, int bytes, void* userData) nothrow @nogc 847 { 848 MemoryContext* context = cast(MemoryContext*)userData; 849 size_t cursor = context.cursor; 850 size_t size = context.size; 851 size_t available = size - cursor; 852 ubyte* buffer = context.buffer; 853 ubyte* source = cast(ubyte*) inData; 854 855 if (cursor + bytes > available) 856 { 857 bytes = cast(int)(available - cursor); 858 } 859 860 buffer[cursor..(cursor + bytes)] = source[0..bytes]; 861 context.size += bytes; 862 context.cursor += bytes; 863 return bytes; 864 } 865 866 int memory_write_append(void* inData, int bytes, void* userData) nothrow @nogc 867 { 868 MemoryContext* context = cast(MemoryContext*)userData; 869 size_t cursor = context.cursor; 870 size_t size = context.size; 871 size_t available = size - cursor; 872 ubyte* buffer = context.buffer; 873 ubyte* source = cast(ubyte*) inData; 874 875 if (cursor + bytes > available) 876 { 877 size_t oldSize = context.capacity; 878 size_t newSize = cursor + bytes; 879 if (newSize < oldSize * 2 + 1) 880 newSize = oldSize * 2 + 1; 881 buffer = cast(ubyte*) realloc(buffer, newSize); 882 context.capacity = newSize; 883 884 assert( cursor + bytes <= available ); 885 } 886 887 buffer[cursor..(cursor + bytes)] = source[0..bytes]; 888 context.size += bytes; 889 context.cursor += bytes; 890 return bytes; 891 } 892 893 bool memory_skip(int bytes, void* userData) nothrow @nogc 894 { 895 MemoryContext* context = cast(MemoryContext*)userData; 896 context.cursor += bytes; 897 return context.cursor <= context.size; 898 } 899 900 bool memory_flush(void* userData) nothrow @nogc 901 { 902 // do nothing, no flushign to do for memory 903 return true; 904 } 905 906 907 // Decoder context 908 struct DecoderContext 909 { 910 void* userDataIO; 911 IOCallbacks* callbacks; 912 } 913 914 // MP3 decoder read callback 915 static int mp3ReadDelegate(void[] buf, void* userDataDecoder) @nogc nothrow 916 { 917 DecoderContext* context = cast(DecoderContext*) userDataDecoder; 918 919 // read bytes into the buffer, return number of bytes read or 0 for EOF, -1 on error 920 // will never be called with empty buffer, or buffer more than 128KB 921 922 int bytes = context.callbacks.read(buf.ptr, cast(int)(buf.length), context.userDataIO); 923 return bytes; 924 } 925 926 927 // FLAC decoder read callbacks 928 929 size_t flac_read(void* pUserData, void* pBufferOut, size_t bytesToRead) @nogc nothrow 930 { 931 DecoderContext* context = cast(DecoderContext*) pUserData; 932 return context.callbacks.read(pBufferOut, cast(int)(bytesToRead), context.userDataIO); 933 } 934 935 bool flac_seek(void* pUserData, int offset, drflac_seek_origin origin) @nogc nothrow 936 { 937 DecoderContext* context = cast(DecoderContext*) pUserData; 938 if (origin == drflac_seek_origin_start) 939 { 940 context.callbacks.seek(offset, false, context.userDataIO); 941 } 942 else if (origin == drflac_seek_origin_current) 943 { 944 context.callbacks.seek(offset, true, context.userDataIO); 945 } 946 return true; 947 }