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.vorbis; 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 opened 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 opened 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 opened 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 /// Returns: `true` if using operations that are accetpable in an audio thread (eg: no file I/O). 222 bool realtimeSafe() @nogc 223 { 224 return fileContext is null; 225 } 226 227 ~this() @nogc 228 { 229 cleanUp(); 230 } 231 232 void cleanUp() @nogc 233 { 234 // Write the last needed bytes if needed 235 finalizeEncodingIfNeeded(); 236 237 version(decodeMP3) 238 { 239 if (_mp3DecoderNew !is null) 240 { 241 mp3dec_ex_close(_mp3DecoderNew); 242 free(_mp3DecoderNew); 243 _mp3DecoderNew = null; 244 } 245 if (_mp3io !is null) 246 { 247 free(_mp3io); 248 _mp3io = null; 249 } 250 } 251 252 version(decodeFLAC) 253 { 254 if (_flacDecoder !is null) 255 { 256 drflac_close(_flacDecoder); 257 _flacDecoder = null; 258 } 259 } 260 261 version(decodeOGG) 262 { 263 if (_oggDecoder !is null) 264 { 265 destroyFree(_oggDecoder); 266 _oggDecoder = null; 267 } 268 _oggBuffer.reallocBuffer(0); 269 } 270 271 version(decodeOPUS) 272 { 273 if (_opusDecoder !is null) 274 { 275 opusClose(_opusDecoder); 276 _opusDecoder = null; 277 } 278 _opusBuffer = null; 279 } 280 281 version(decodeWAV) 282 { 283 if (_wavDecoder !is null) 284 { 285 destroyFree(_wavDecoder); 286 _wavDecoder = null; 287 } 288 } 289 290 version(decodeXM) 291 { 292 if (_xmDecoder !is null) 293 { 294 xm_free_context(_xmDecoder); 295 _xmDecoder = null; 296 } 297 if (_xmContent != null) 298 { 299 free(_xmContent); 300 _xmContent = null; 301 } 302 } 303 304 version(decodeMOD) 305 { 306 if (_modDecoder !is null) 307 { 308 free(_modDecoder); 309 _modDecoder = null; 310 _modContent.reallocBuffer(0); 311 } 312 } 313 314 version(encodeWAV) 315 { 316 if (_wavEncoder !is null) 317 { 318 destroyFree(_wavEncoder); 319 _wavEncoder = null; 320 } 321 } 322 323 if (_decoderContext) 324 { 325 destroyFree(_decoderContext); 326 _decoderContext = null; 327 } 328 329 if (fileContext !is null) 330 { 331 if (fileContext.file !is null) 332 { 333 int result = fclose(fileContext.file); 334 if (result) 335 throw mallocNew!Exception("Closing of audio file errored"); 336 } 337 destroyFree(fileContext); 338 fileContext = null; 339 } 340 341 if (memoryContext !is null) 342 { 343 // TODO destroy buffer if any is owned 344 destroyFree(memoryContext); 345 memoryContext = null; 346 } 347 348 if (_io !is null) 349 { 350 destroyFree(_io); 351 _io = null; 352 } 353 } 354 355 /// Returns: File format of this stream. 356 AudioFileFormat getFormat() nothrow @nogc 357 { 358 return _format; 359 } 360 361 /// Returns: File format of this stream. 362 int getNumChannels() nothrow @nogc 363 { 364 return _numChannels; 365 } 366 367 /// Returns: Length of this stream in frames. 368 /// Note: may return `audiostreamUnknownLength` if the length is unknown. 369 long getLengthInFrames() nothrow @nogc 370 { 371 return _lengthInFrames; 372 } 373 374 /// Returns: Sample-rate of this stream in Hz. 375 float getSamplerate() nothrow @nogc 376 { 377 return _sampleRate; 378 } 379 380 /// Read interleaved float samples. 381 /// `outData` must have enough room for `frames` * `channels` decoded samples. 382 int readSamplesFloat(float* outData, int frames) @nogc 383 { 384 // If you fail here, you are using this `AudioStream` for decoding: 385 // - after it has been destroyed, 386 // - or it was created for encoding instead 387 assert(_io && _io.read !is null); 388 389 final switch(_format) 390 { 391 case AudioFileFormat.opus: 392 { 393 version(decodeOPUS) 394 { 395 try 396 { 397 int decoded = 0; 398 while (decoded < frames) 399 { 400 // Is there any sample left in _opusBuffer? 401 // If not decode some frames. 402 if (_opusBuffer is null || _opusBuffer.length == 0) 403 { 404 _opusBuffer = _opusDecoder.readFrame(); 405 if (_opusBuffer is null) 406 break; 407 } 408 409 int samplesInBuffer = cast(int) _opusBuffer.length; 410 int framesInBuffer = samplesInBuffer / _numChannels; 411 if (framesInBuffer == 0) 412 break; 413 414 // Frames to pull are min( frames left to decode, frames available) 415 int framesToDecode = frames - decoded; 416 int framesToUse = framesToDecode < framesInBuffer ? framesToDecode : framesInBuffer; 417 assert(framesToUse != 0); 418 419 int samplesToUse = framesToUse * _numChannels; 420 int outOffset = decoded*_numChannels; 421 for (int n = 0; n < samplesToUse; ++n) 422 { 423 outData[outOffset + n] = _opusBuffer[n] / 32767.0f; 424 } 425 _opusBuffer = _opusBuffer[samplesToUse..$]; // reduce size of intermediate buffer 426 decoded += framesToUse; 427 } 428 return decoded; 429 } 430 catch(Exception e) 431 { 432 destroyFree(e); 433 return 0; // decoding might fail, in which case return zero samples 434 } 435 } 436 } 437 438 case AudioFileFormat.flac: 439 { 440 version(decodeFLAC) 441 { 442 assert(_flacDecoder !is null); 443 444 int* integerData = cast(int*)outData; 445 int samples = cast(int) drflac_read_s32(_flacDecoder, frames, integerData); 446 447 // "Samples are always output as interleaved signed 32-bit PCM." 448 // Convert to float with type-punning. Note that this looses some precision. 449 double factor = 1.0 / int.max; 450 foreach(n; 0..samples) 451 { 452 outData[n] = integerData[n] * factor; 453 } 454 return samples / _numChannels; 455 } 456 else 457 { 458 assert(false); // Impossible 459 } 460 } 461 462 case AudioFileFormat.ogg: 463 { 464 version(decodeOGG) 465 { 466 assert(_oggDecoder !is null); 467 return _oggDecoder.stb_vorbis_get_samples_float_interleaved(_numChannels, outData, frames * _numChannels); 468 } 469 else 470 { 471 assert(false); // Impossible 472 } 473 } 474 475 case AudioFileFormat.mp3: 476 { 477 version(decodeMP3) 478 { 479 assert(_mp3DecoderNew !is null); 480 481 int samplesNeeded = frames * _numChannels; 482 int result = cast(int) mp3dec_ex_read(_mp3DecoderNew, outData, samplesNeeded); 483 if (result < 0) // error 484 return 0; 485 return result / _numChannels; 486 } 487 else 488 { 489 assert(false); // Impossible 490 } 491 } 492 case AudioFileFormat.wav: 493 version(decodeWAV) 494 { 495 assert(_wavDecoder !is null); 496 int readFrames = _wavDecoder.readSamples(outData, frames); 497 return readFrames; 498 } 499 else 500 { 501 assert(false); // Impossible 502 } 503 504 case AudioFileFormat.xm: 505 version(decodeXM) 506 { 507 assert(_xmDecoder !is null); 508 509 if (xm_get_loop_count(_xmDecoder) >= 1) 510 return 0; // song is finished 511 512 xm_generate_samples(_xmDecoder, outData, frames); 513 return frames; // Note: XM decoder pads end with zeroes. 514 } 515 else 516 { 517 assert(false); // Impossible 518 } 519 520 case AudioFileFormat.mod: 521 version(decodeMOD) 522 { 523 if (pocketmod_loop_count(_modDecoder) >= 1) 524 return 0; // end stream after MOD finishes, looping not supported 525 assert(_modDecoder !is null); 526 int bytesReturned = pocketmod_render(_modDecoder, outData, frames * 2 * 4); 527 assert((bytesReturned % 8) == 0); 528 return bytesReturned / 8; 529 } 530 else 531 { 532 assert(false); // Impossible 533 } 534 535 case AudioFileFormat.unknown: 536 // One shouldn't ever get there, since in this case 537 // opening has failed. 538 assert(false); 539 } 540 } 541 ///ditto 542 int readSamplesFloat(float[] outData) @nogc 543 { 544 assert( (outData.length % _numChannels) == 0); 545 return readSamplesFloat(outData.ptr, cast(int)(outData.length / _numChannels) ); 546 } 547 548 /// Write interleaved float samples. 549 /// `inData` must have enough data for `frames` * `channels` samples. 550 int writeSamplesFloat(float* inData, int frames) nothrow @nogc 551 { 552 // If you fail here, you are using this `AudioStream` for encoding: 553 // - after it has been destroyed, 554 // - or after encoding has been finalized with 555 // - or it was created for encoding instead 556 assert(_io && _io.write !is null); 557 558 final switch(_format) 559 { 560 case AudioFileFormat.mp3: 561 case AudioFileFormat.flac: 562 case AudioFileFormat.ogg: 563 case AudioFileFormat.opus: 564 case AudioFileFormat.mod: 565 case AudioFileFormat.xm: 566 case AudioFileFormat.unknown: 567 { 568 assert(false); // Shouldn't have arrived here, such encoding aren't supported. 569 } 570 case AudioFileFormat.wav: 571 { 572 version(encodeWAV) 573 { 574 return _wavEncoder.writeSamples(inData, frames); 575 } 576 else 577 { 578 assert(false, "no support for WAV encoding"); 579 } 580 } 581 } 582 } 583 ///ditto 584 int writeSamplesFloat(float[] inData) nothrow @nogc 585 { 586 assert( (inData.length % _numChannels) == 0); 587 return writeSamplesFloat(inData.ptr, cast(int)(inData.length / _numChannels)); 588 } 589 590 /// Call `fflush()` on written samples, if any. 591 /// It is only useful for streamable output formats, that may want to flush things to disk. 592 void flush() nothrow @nogc 593 { 594 assert( _io && (_io.write !is null) ); 595 _io.flush(userData); 596 } 597 598 /// Finalize encoding. After finalization, further writes are not possible anymore 599 /// however the stream is considered complete and valid for storage. 600 void finalizeEncoding() @nogc 601 { 602 // If you crash here, it's because `finalizeEncoding` has been called twice. 603 assert( _io && (_io.write !is null) ); 604 605 final switch(_format) with (AudioFileFormat) 606 { 607 case mp3: 608 case flac: 609 case ogg: 610 case opus: 611 case mod: 612 case xm: 613 assert(false); // unsupported output encoding 614 case wav: 615 { 616 _wavEncoder.finalizeEncoding(); 617 break; 618 } 619 case unknown: 620 assert(false); 621 } 622 _io.write = null; // prevents further encodings 623 } 624 625 // Finalize encoding and get internal buffer. 626 // This can be called multiple times, in which cases the stream is finalized only the first time. 627 const(ubyte)[] finalizeAndGetEncodedResult() @nogc 628 { 629 // only callable while appending, else it's a programming error 630 assert( (memoryContext !is null) && ( memoryContext.bufferCanGrow ) ); 631 632 finalizeEncodingIfNeeded(); 633 return memoryContext.buffer[0..memoryContext.size]; 634 } 635 636 private: 637 IOCallbacks* _io; 638 639 // This type of context is a closure to remember where the data is. 640 void* userData; // is equal to either fileContext or memoryContext 641 FileContext* fileContext; 642 MemoryContext* memoryContext; 643 644 // This type of context is a closure to remember where _io and user Data is. 645 DecoderContext* _decoderContext; 646 647 AudioFileFormat _format; 648 float _sampleRate; 649 int _numChannels; 650 long _lengthInFrames; 651 652 // Decoders 653 version(decodeMP3) 654 { 655 mp3dec_ex_t* _mp3DecoderNew; // allocated on heap since it's a 16kb object 656 mp3dec_io_t* _mp3io; 657 } 658 version(decodeFLAC) 659 { 660 drflac* _flacDecoder; 661 } 662 version(decodeOGG) 663 { 664 ubyte[] _oggBuffer; // all allocations from the ogg decoder 665 VorbisDecoder _oggDecoder; 666 } 667 version(decodeWAV) 668 { 669 WAVDecoder _wavDecoder; 670 } 671 version(decodeMOD) 672 { 673 pocketmod_context* _modDecoder = null; 674 ubyte[] _modContent = null; // whole buffer, copied 675 } 676 version(decodeXM) 677 { 678 xm_context_t* _xmDecoder = null; 679 ubyte* _xmContent = null; 680 } 681 682 version(decodeOPUS) 683 { 684 OpusFile _opusDecoder; 685 short[] _opusBuffer; 686 } 687 688 // Encoder 689 version(encodeWAV) 690 { 691 WAVEncoder _wavEncoder; 692 } 693 694 bool isOpenedForWriting() nothrow @nogc 695 { 696 // Note: 697 // * when opened for reading, I/O operations given are: seek/tell/getFileLength/read. 698 // * when opened for writing, I/O operations given are: seek/tell/write/flush. 699 return (_io !is null) && (_io.read is null); 700 } 701 702 void startDecoding() @nogc 703 { 704 // Create a decoder context 705 _decoderContext = mallocNew!DecoderContext; 706 _decoderContext.userDataIO = userData; 707 _decoderContext.callbacks = _io; 708 709 version(decodeOPUS) 710 { 711 try 712 { 713 _opusDecoder = opusOpen(_io, userData); 714 assert(_opusDecoder !is null); 715 _format = AudioFileFormat.opus; 716 _sampleRate = _opusDecoder.rate; // Note: Opus file are always 48Khz 717 _numChannels = _opusDecoder.channels(); 718 _lengthInFrames = _opusDecoder.smpduration(); 719 return; 720 } 721 catch(Exception e) 722 { 723 destroyFree(e); 724 } 725 _opusDecoder = null; 726 } 727 728 version(decodeFLAC) 729 { 730 _io.seek(0, false, userData); 731 732 // Is it a FLAC? 733 { 734 drflac_read_proc onRead = &flac_read; 735 drflac_seek_proc onSeek = &flac_seek; 736 void* pUserData = _decoderContext; 737 _flacDecoder = drflac_open (onRead, onSeek, _decoderContext); 738 if (_flacDecoder !is null) 739 { 740 _format = AudioFileFormat.flac; 741 _sampleRate = _flacDecoder.sampleRate; 742 _numChannels = _flacDecoder.channels; 743 _lengthInFrames = _flacDecoder.totalSampleCount / _numChannels; 744 return; 745 } 746 } 747 } 748 749 version(decodeWAV) 750 { 751 // Check if it's a WAV. 752 753 _io.seek(0, false, userData); 754 755 try 756 { 757 _wavDecoder = mallocNew!WAVDecoder(_io, userData); 758 _wavDecoder.scan(); 759 760 // WAV detected 761 _format = AudioFileFormat.wav; 762 _sampleRate = _wavDecoder._sampleRate; 763 _numChannels = _wavDecoder._channels; 764 _lengthInFrames = _wavDecoder._lengthInFrames; 765 return; 766 } 767 catch(Exception e) 768 { 769 // not a WAV 770 destroyFree(e); 771 } 772 destroyFree(_wavDecoder); 773 _wavDecoder = null; 774 } 775 776 version(decodeOGG) 777 { 778 _io.seek(0, false, userData); 779 780 // Is it an OGG? 781 { 782 //"In my test files the maximal-size usage is ~150KB", so let's take a bit more 783 _oggBuffer.reallocBuffer(200 * 1024); 784 785 stb_vorbis_alloc alloc; 786 alloc.alloc_buffer = cast(ubyte*)(_oggBuffer.ptr); 787 alloc.alloc_buffer_length_in_bytes = cast(int)(_oggBuffer.length); 788 789 int error; 790 _oggDecoder = mallocNew!VorbisDecoder(_io, userData); 791 if (_oggDecoder.error == STBVorbisError.no_error) 792 { 793 _format = AudioFileFormat.ogg; 794 _sampleRate = _oggDecoder.sampleRate; 795 _numChannels = _oggDecoder.chans; 796 _lengthInFrames = audiostreamUnknownLength;//stb_vorbis_stream_length_in_samples(_oggDecoder); 797 return; 798 } 799 else 800 { 801 destroyFree(_oggDecoder); 802 _oggDecoder = null; 803 } 804 805 } 806 } 807 808 version(decodeMP3) 809 { 810 // Check if it's a MP3. 811 { 812 _io.seek(0, false, userData); 813 814 ubyte* scratchBuffer = cast(ubyte*) malloc(MINIMP3_BUF_SIZE*2); 815 scope(exit) free(scratchBuffer); 816 817 _mp3io = cast(mp3dec_io_t*) malloc(mp3dec_io_t.sizeof); 818 _mp3io.read = &mp3_io_read; 819 _mp3io.read_data = _decoderContext; 820 _mp3io.seek = &mp3_io_seek; 821 _mp3io.seek_data = _decoderContext; 822 823 if ( mp3dec_detect_cb(_mp3io, scratchBuffer, MINIMP3_BUF_SIZE*2) == 0 ) 824 { 825 // This is a MP3. Try to open a stream. 826 827 // Allocate a mp3dec_ex_t object 828 _mp3DecoderNew = cast(mp3dec_ex_t*) malloc(mp3dec_ex_t.sizeof); 829 830 int result = mp3dec_ex_open_cb(_mp3DecoderNew, _mp3io, MP3D_SEEK_TO_SAMPLE); 831 832 if (0 == result) 833 { 834 // MP3 detected 835 // but it seems we need to iterate all frames to know the length... 836 _format = AudioFileFormat.mp3; 837 _sampleRate = _mp3DecoderNew.info.hz; 838 _numChannels = _mp3DecoderNew.info.channels; 839 _lengthInFrames = _mp3DecoderNew.samples / _numChannels; 840 return; 841 } 842 else 843 { 844 free(_mp3DecoderNew); 845 _mp3DecoderNew = null; 846 free(_mp3io); 847 _mp3io = null; 848 } 849 } 850 } 851 } 852 853 version(decodeXM) 854 { 855 { 856 // we need the first 60 bytes to check if XM 857 char[60] xmHeader; 858 int bytes; 859 860 _io.seek(0, false, userData); 861 long lenBytes = _io.getFileLength(userData); 862 if (lenBytes < 60) 863 goto not_a_xm; 864 865 bytes = _io.read(xmHeader.ptr, 60, userData); 866 if (bytes != 60) 867 goto not_a_xm; 868 869 if (0 != xm_check_sanity_preload(xmHeader.ptr, 60)) 870 goto not_a_xm; 871 872 _xmContent = cast(ubyte*) malloc(cast(int)lenBytes); 873 _io.seek(0, false, userData); 874 bytes = _io.read(_xmContent, cast(int)lenBytes, userData); 875 if (bytes != cast(int)lenBytes) 876 goto not_a_xm; 877 878 if (0 == xm_create_context_safe(&_xmDecoder, cast(const(char)*)_xmContent, cast(size_t)lenBytes, 44100)) 879 { 880 assert(_xmDecoder !is null); 881 882 xm_set_max_loop_count(_xmDecoder, 1); 883 884 _format = AudioFileFormat.xm; 885 _sampleRate = 44100.0f; 886 _numChannels = 2; 887 _lengthInFrames = audiostreamUnknownLength; 888 return; 889 } 890 891 not_a_xm: 892 assert(_xmDecoder == null); 893 free(_xmContent); 894 _xmContent = null; 895 } 896 } 897 898 version(decodeMOD) 899 { 900 { 901 // we need either the first 1084 or 600 bytes if available 902 _io.seek(0, false, userData); 903 long lenBytes = _io.getFileLength(userData); 904 if (lenBytes >= 600) 905 { 906 int headerBytes = lenBytes > 1084 ? 1084 : cast(int)lenBytes; 907 908 ubyte[1084] header; 909 int bytes = _io.read(header.ptr, headerBytes, userData); 910 911 if (_pocketmod_ident(null, header.ptr, bytes)) 912 { 913 // This is a MOD, allocate a proper context, and read the whole file. 914 _modDecoder = cast(pocketmod_context*) malloc(pocketmod_context.sizeof); 915 916 // Read whole .mod in a buffer, since the decoder work all from memory 917 _io.seek(0, false, userData); 918 _modContent.reallocBuffer(cast(size_t)lenBytes); 919 bytes = _io.read(_modContent.ptr, cast(int)lenBytes, userData); 920 921 if (pocketmod_init(_modDecoder, _modContent.ptr, bytes, 44100)) 922 { 923 _format = AudioFileFormat.mod; 924 _sampleRate = 44100.0f; 925 _numChannels = 2; 926 _lengthInFrames = audiostreamUnknownLength; 927 return; 928 } 929 } 930 } 931 } 932 } 933 934 _format = AudioFileFormat.unknown; 935 _sampleRate = float.nan; 936 _numChannels = 0; 937 _lengthInFrames = -1; 938 939 throw mallocNew!Exception("Cannot decode stream: unrecognized encoding."); 940 } 941 942 void startEncoding(AudioFileFormat format, float sampleRate, int numChannels) @nogc 943 { 944 _format = format; 945 _sampleRate = sampleRate; 946 _numChannels = numChannels; 947 948 final switch(format) with (AudioFileFormat) 949 { 950 case mp3: 951 throw mallocNew!Exception("Unsupported encoding format: MP3"); 952 case flac: 953 throw mallocNew!Exception("Unsupported encoding format: FLAC"); 954 case ogg: 955 throw mallocNew!Exception("Unsupported encoding format: OGG"); 956 case opus: 957 throw mallocNew!Exception("Unsupported encoding format: Opus"); 958 case mod: 959 throw mallocNew!Exception("Unsupported encoding format: MOD"); 960 case xm: 961 throw mallocNew!Exception("Unsupported encoding format: XM"); 962 case wav: 963 { 964 // Note: fractional sample rates not supported by WAV, signal an integer one 965 int isampleRate = cast(int)(sampleRate + 0.5f); 966 _wavEncoder = mallocNew!WAVEncoder(_io, userData, isampleRate, numChannels ); 967 break; 968 } 969 case unknown: 970 throw mallocNew!Exception("Can't encode using 'unknown' coding"); 971 } 972 } 973 974 void finalizeEncodingIfNeeded() @nogc 975 { 976 if (_io && (_io.write !is null)) // if we have been encoding something 977 { 978 finalizeEncoding(); 979 } 980 } 981 } 982 983 // AudioStream should be able to go on a smallish 32-bit stack, 984 // and malloc the rest on the heap when needed. 985 static assert(AudioStream.sizeof <= 256); 986 987 private: // not meant to be imported at all 988 989 990 991 // Internal object for audio-formats 992 993 994 // File callbacks 995 // The file callbacks are using the C stdlib. 996 997 struct FileContext // this is what is passed to I/O when used in file mode 998 { 999 // Used when streaming of writing a file 1000 FILE* file = null; 1001 1002 // Size of the file in bytes, only used when reading/writing a file. 1003 long fileSize; 1004 1005 // Initialize this context 1006 void initialize(const(char)[] path, bool forWrite) @nogc 1007 { 1008 CString strZ = CString(path); 1009 file = fopen(strZ.storage, forWrite ? "wb".ptr : "rb".ptr); 1010 // finds the size of the file 1011 fseek(file, 0, SEEK_END); 1012 fileSize = ftell(file); 1013 fseek(file, 0, SEEK_SET); 1014 } 1015 } 1016 1017 long file_tell(void* userData) nothrow @nogc 1018 { 1019 FileContext* context = cast(FileContext*)userData; 1020 return ftell(context.file); 1021 } 1022 1023 void file_seek(long offset, bool relative, void* userData) nothrow @nogc 1024 { 1025 FileContext* context = cast(FileContext*)userData; 1026 assert(offset <= int.max); 1027 fseek(context.file, cast(int)offset, relative ? SEEK_CUR : SEEK_SET); // Limitations: file larger than 2gb not supported 1028 } 1029 1030 long file_getFileLength(void* userData) nothrow @nogc 1031 { 1032 FileContext* context = cast(FileContext*)userData; 1033 return context.fileSize; 1034 } 1035 1036 int file_read(void* outData, int bytes, void* userData) nothrow @nogc 1037 { 1038 FileContext* context = cast(FileContext*)userData; 1039 size_t bytesRead = fread(outData, 1, bytes, context.file); 1040 return cast(int)bytesRead; 1041 } 1042 1043 int file_write(void* inData, int bytes, void* userData) nothrow @nogc 1044 { 1045 FileContext* context = cast(FileContext*)userData; 1046 size_t bytesWritten = fwrite(inData, 1, bytes, context.file); 1047 return cast(int)bytesWritten; 1048 } 1049 1050 bool file_skip(int bytes, void* userData) nothrow @nogc 1051 { 1052 FileContext* context = cast(FileContext*)userData; 1053 return (0 == fseek(context.file, bytes, SEEK_CUR)); 1054 } 1055 1056 bool file_flush(void* userData) nothrow @nogc 1057 { 1058 FileContext* context = cast(FileContext*)userData; 1059 return ( fflush(context.file) == 0 ); 1060 } 1061 1062 // Memory read callback 1063 // Using the read buffer instead 1064 1065 struct MemoryContext 1066 { 1067 bool bufferIsOwned; 1068 bool bufferCanGrow; 1069 1070 // Buffer 1071 ubyte* buffer = null; 1072 1073 size_t size; // current buffer size 1074 size_t cursor; // where we are in the buffer 1075 size_t capacity; // max buffer size before realloc 1076 1077 void initializeWithConstantInput(const(ubyte)* data, size_t length) nothrow @nogc 1078 { 1079 // Make a copy of the input buffer, since it could be temporary. 1080 bufferIsOwned = true; 1081 bufferCanGrow = false; 1082 1083 buffer = mallocDup(data[0..length]).ptr; // Note: the copied slice is made mutable. 1084 size = length; 1085 cursor = 0; 1086 capacity = length; 1087 } 1088 1089 void initializeWithExternalOutputBuffer(ubyte* data, size_t length) nothrow @nogc 1090 { 1091 bufferIsOwned = false; 1092 bufferCanGrow = false; 1093 buffer = data; 1094 size = 0; 1095 cursor = 0; 1096 capacity = length; 1097 } 1098 1099 void initializeWithInternalGrowableBuffer() nothrow @nogc 1100 { 1101 bufferIsOwned = true; 1102 bufferCanGrow = true; 1103 buffer = null; 1104 size = 0; 1105 cursor = 0; 1106 capacity = 0; 1107 } 1108 1109 ~this() 1110 { 1111 if (bufferIsOwned) 1112 { 1113 if (buffer !is null) 1114 { 1115 free(buffer); 1116 buffer = null; 1117 } 1118 } 1119 } 1120 } 1121 1122 long memory_tell(void* userData) nothrow @nogc 1123 { 1124 MemoryContext* context = cast(MemoryContext*)userData; 1125 return cast(long)(context.cursor); 1126 } 1127 1128 void memory_seek(long offset, bool relative, void* userData) nothrow @nogc 1129 { 1130 MemoryContext* context = cast(MemoryContext*)userData; 1131 if (relative) offset += context.cursor; 1132 if (offset >= context.size) // can't seek past end of buffer, stick to the end so that read return 0 byte 1133 offset = context.size; 1134 context.cursor = cast(size_t)offset; // Note: memory streams larger than 2gb not supported 1135 } 1136 1137 long memory_getFileLength(void* userData) nothrow @nogc 1138 { 1139 MemoryContext* context = cast(MemoryContext*)userData; 1140 return cast(long)(context.size); 1141 } 1142 1143 int memory_read(void* outData, int bytes, void* userData) nothrow @nogc 1144 { 1145 MemoryContext* context = cast(MemoryContext*)userData; 1146 size_t cursor = context.cursor; 1147 size_t size = context.size; 1148 size_t available = size - cursor; 1149 if (bytes < available) 1150 { 1151 outData[0..bytes] = context.buffer[cursor..cursor + bytes]; 1152 context.cursor += bytes; 1153 return bytes; 1154 } 1155 else 1156 { 1157 outData[0..available] = context.buffer[cursor..cursor + available]; 1158 context.cursor = context.size; 1159 return cast(int)available; 1160 } 1161 } 1162 1163 int memory_write_limited(void* inData, int bytes, void* userData) nothrow @nogc 1164 { 1165 MemoryContext* context = cast(MemoryContext*)userData; 1166 size_t cursor = context.cursor; 1167 size_t size = context.size; 1168 size_t available = size - cursor; 1169 ubyte* buffer = context.buffer; 1170 ubyte* source = cast(ubyte*) inData; 1171 1172 if (cursor + bytes > available) 1173 { 1174 bytes = cast(int)(available - cursor); 1175 } 1176 1177 buffer[cursor..(cursor + bytes)] = source[0..bytes]; 1178 context.size += bytes; 1179 context.cursor += bytes; 1180 return bytes; 1181 } 1182 1183 int memory_write_append(void* inData, int bytes, void* userData) nothrow @nogc 1184 { 1185 MemoryContext* context = cast(MemoryContext*)userData; 1186 size_t cursor = context.cursor; 1187 size_t size = context.size; 1188 size_t available = size - cursor; 1189 ubyte* buffer = context.buffer; 1190 ubyte* source = cast(ubyte*) inData; 1191 1192 if (cursor + bytes > available) 1193 { 1194 size_t oldSize = context.capacity; 1195 size_t newSize = cursor + bytes; 1196 if (newSize < oldSize * 2 + 1) 1197 newSize = oldSize * 2 + 1; 1198 buffer = cast(ubyte*) realloc(buffer, newSize); 1199 context.capacity = newSize; 1200 1201 assert( cursor + bytes <= available ); 1202 } 1203 1204 buffer[cursor..(cursor + bytes)] = source[0..bytes]; 1205 context.size += bytes; 1206 context.cursor += bytes; 1207 return bytes; 1208 } 1209 1210 bool memory_skip(int bytes, void* userData) nothrow @nogc 1211 { 1212 MemoryContext* context = cast(MemoryContext*)userData; 1213 context.cursor += bytes; 1214 return context.cursor <= context.size; 1215 } 1216 1217 bool memory_flush(void* userData) nothrow @nogc 1218 { 1219 // do nothing, no flushign to do for memory 1220 return true; 1221 } 1222 1223 1224 // Decoder context 1225 struct DecoderContext 1226 { 1227 void* userDataIO; 1228 IOCallbacks* callbacks; 1229 } 1230 1231 // MP3 decoder read callback 1232 static int mp3ReadDelegate(void[] buf, void* userDataDecoder) @nogc nothrow 1233 { 1234 DecoderContext* context = cast(DecoderContext*) userDataDecoder; 1235 1236 // read bytes into the buffer, return number of bytes read or 0 for EOF, -1 on error 1237 // will never be called with empty buffer, or buffer more than 128KB 1238 1239 int bytes = context.callbacks.read(buf.ptr, cast(int)(buf.length), context.userDataIO); 1240 return bytes; 1241 } 1242 1243 1244 // FLAC decoder read callbacks 1245 1246 size_t flac_read(void* pUserData, void* pBufferOut, size_t bytesToRead) @nogc nothrow 1247 { 1248 DecoderContext* context = cast(DecoderContext*) pUserData; 1249 return context.callbacks.read(pBufferOut, cast(int)(bytesToRead), context.userDataIO); 1250 } 1251 1252 bool flac_seek(void* pUserData, int offset, drflac_seek_origin origin) @nogc nothrow 1253 { 1254 DecoderContext* context = cast(DecoderContext*) pUserData; 1255 if (origin == drflac_seek_origin_start) 1256 { 1257 context.callbacks.seek(offset, false, context.userDataIO); 1258 } 1259 else if (origin == drflac_seek_origin_current) 1260 { 1261 context.callbacks.seek(offset, true, context.userDataIO); 1262 } 1263 return true; 1264 } 1265 1266 // MP3 decoder read callbacks 1267 1268 size_t mp3_io_read(void *buf, size_t size, void *user_data) @nogc nothrow 1269 { 1270 DecoderContext* context = cast(DecoderContext*) user_data; 1271 return context.callbacks.read(buf, cast(int)(size), context.userDataIO); 1272 } 1273 1274 int mp3_io_seek(ulong position, void *user_data) @nogc nothrow 1275 { 1276 DecoderContext* context = cast(DecoderContext*) user_data; 1277 context.callbacks.seek(position, false, context.userDataIO); 1278 return 0; // doesn't detect seeking errors 1279 }