1 /** 2 Supports Microsoft WAV audio file format. 3 4 Copyright: Guillaume Piolat 2015-2020. 5 License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 module audioformats.wav; 8 9 import dplug.core.nogc; 10 import audioformats.io; 11 12 13 version(decodeWAV) 14 { 15 /// Use both for scanning and decoding 16 final class WAVDecoder 17 { 18 public: 19 @nogc: 20 21 static immutable ubyte[16] KSDATAFORMAT_SUBTYPE_IEEE_FLOAT = 22 [3, 0, 0, 0, 0, 0, 16, 0, 128, 0, 0, 170, 0, 56, 155, 113]; 23 24 this(IOCallbacks* io, void* userData) nothrow 25 { 26 _io = io; 27 _userData = userData; 28 } 29 30 // After scan, we know _sampleRate, _lengthInFrames, and _channels, and can call `readSamples` 31 void scan() 32 { 33 // check RIFF header 34 { 35 uint chunkId, chunkSize; 36 _io.readRIFFChunkHeader(_userData, chunkId, chunkSize); 37 if (chunkId != RIFFChunkId!"RIFF") 38 throw mallocNew!Exception("Expected RIFF chunk."); 39 40 if (chunkSize < 4) 41 throw mallocNew!Exception("RIFF chunk is too small to contain a format."); 42 43 if (_io.read_uint_BE(_userData) != RIFFChunkId!"WAVE") 44 throw mallocNew!Exception("Expected WAVE format."); 45 } 46 47 bool foundFmt = false; 48 bool foundData = false; 49 50 int byteRate; 51 int blockAlign; 52 int bitsPerSample; 53 54 while (!_io.nothingToReadAnymore(_userData)) 55 { 56 // Some corrupted WAV files in the wild finish with one 57 // extra 0 byte after an AFAn chunk, very odd 58 if (_io.remainingBytesToRead(_userData) == 1) 59 { 60 if (_io.peek_ubyte(_userData) == 0) 61 break; 62 } 63 64 // Question: is there any reason to parse the whole WAV file? This prevents streaming. 65 66 uint chunkId, chunkSize; 67 _io.readRIFFChunkHeader(_userData, chunkId, chunkSize); 68 if (chunkId == RIFFChunkId!"fmt ") 69 { 70 if (foundFmt) 71 throw mallocNew!Exception("Found several 'fmt ' chunks in RIFF file."); 72 73 foundFmt = true; 74 75 if (chunkSize < 16) 76 throw mallocNew!Exception("Expected at least 16 bytes in 'fmt ' chunk."); // found in real-world for the moment: 16 or 40 bytes 77 78 _audioFormat = _io.read_ushort_LE(_userData); 79 bool isWFE = _audioFormat == WAVE_FORMAT_EXTENSIBLE; 80 81 if (_audioFormat != LinearPCM && _audioFormat != FloatingPointIEEE && !isWFE) 82 throw mallocNew!Exception("Unsupported audio format, only PCM and IEEE float and WAVE_FORMAT_EXTENSIBLE are supported."); 83 84 _channels = _io.read_ushort_LE(_userData); 85 86 _sampleRate = _io.read_uint_LE(_userData); 87 if (_sampleRate <= 0) 88 throw mallocNew!Exception("Unsupported sample-rate."); // we do not support sample-rate higher than 2^31hz 89 90 uint bytesPerSec = _io.read_uint_LE(_userData); 91 int bytesPerFrame = _io.read_ushort_LE(_userData); 92 bitsPerSample = _io.read_ushort_LE(_userData); 93 94 if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 && bitsPerSample != 32) 95 throw mallocNew!Exception("Unsupported bitdepth"); 96 97 if (bytesPerFrame != (bitsPerSample / 8) * _channels) 98 throw mallocNew!Exception("Invalid bytes-per-second, data might be corrupted."); 99 100 // Sometimes there is no cbSize 101 if (chunkSize >= 18) 102 { 103 ushort cbSize = _io.read_ushort_LE(_userData); 104 105 if (isWFE) 106 { 107 if (cbSize >= 22) 108 { 109 ushort wReserved = _io.read_ushort_LE(_userData); 110 uint dwChannelMask = _io.read_uint_LE(_userData); 111 ubyte[16] SubFormat = _io.read_guid(_userData); 112 113 if (SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT) 114 { 115 _audioFormat = FloatingPointIEEE; 116 } 117 else 118 throw mallocNew!Exception("Unsupported GUID in WAVE_FORMAT_EXTENSIBLE."); 119 } 120 else 121 throw mallocNew!Exception("Unsupported WAVE_FORMAT_EXTENSIBLE."); 122 123 _io.skip(chunkSize - (18 + 2 + 4 + 16), _userData); 124 } 125 else 126 { 127 _io.skip(chunkSize - 18, _userData); 128 } 129 } 130 else 131 { 132 _io.skip(chunkSize - 16, _userData); 133 } 134 135 } 136 else if (chunkId == RIFFChunkId!"data") 137 { 138 if (foundData) 139 throw mallocNew!Exception("Found several 'data' chunks in RIFF file."); 140 141 if (!foundFmt) 142 throw mallocNew!Exception("'fmt ' chunk expected before the 'data' chunk."); 143 144 _bytePerSample = bitsPerSample / 8; 145 uint frameSize = _channels * _bytePerSample; 146 if (chunkSize % frameSize != 0) 147 throw mallocNew!Exception("Remaining bytes in 'data' chunk, inconsistent with audio data type."); 148 149 uint numFrames = chunkSize / frameSize; 150 _lengthInFrames = numFrames; 151 152 _samplesOffsetInFile = _io.tell(_userData); 153 154 _io.skip(chunkSize, _userData); // skip, will read later 155 foundData = true; 156 } 157 else 158 { 159 // ignore unknown chunks 160 _io.skip(chunkSize, _userData); 161 } 162 } 163 164 if (!foundFmt) 165 throw mallocNew!Exception("'fmt ' chunk not found."); 166 167 if (!foundData) 168 throw mallocNew!Exception("'data' chunk not found."); 169 170 // Get ready to decode 171 _io.seek(_samplesOffsetInFile, false, _userData); 172 _framePosition = 0; // seek to start 173 } 174 175 /// Returns: false in case of failure. 176 bool seekPosition(int absoluteFrame) 177 { 178 if (absoluteFrame < 0) 179 return false; 180 if (absoluteFrame > _lengthInFrames) 181 return false; 182 uint frameSize = _channels * _bytePerSample; 183 long pos = _samplesOffsetInFile + absoluteFrame * frameSize; 184 _io.seek(pos, false, _userData); 185 _framePosition = absoluteFrame; 186 return true; 187 } 188 189 /// Returns: position in absolute number of frames since beginning. 190 int tellPosition() 191 { 192 return _framePosition; 193 } 194 195 // read interleaved samples 196 // `outData` should have enough room for frames * _channels 197 // Returs: Frames actually read. 198 int readSamples(T)(T* outData, int maxFrames) nothrow 199 { 200 assert(_framePosition <= _lengthInFrames); 201 int available = _lengthInFrames - _framePosition; 202 203 // How much frames can we decode? 204 int frames = maxFrames; 205 if (frames > available) 206 frames = available; 207 _framePosition += frames; 208 209 int numSamples = frames * _channels; 210 211 uint n = 0; 212 213 try 214 { 215 if (_audioFormat == FloatingPointIEEE) 216 { 217 if (_bytePerSample == 4) 218 { 219 for (n = 0; n < numSamples; ++n) 220 outData[n] = _io.read_float_LE(_userData); 221 } 222 else if (_bytePerSample == 8) 223 { 224 for (n = 0; n < numSamples; ++n) 225 outData[n] = _io.read_double_LE(_userData); 226 } 227 else 228 throw mallocNew!Exception("Unsupported bit-depth for floating point data, should be 32 or 64."); 229 } 230 else if (_audioFormat == LinearPCM) 231 { 232 if (_bytePerSample == 1) 233 { 234 for (n = 0; n < numSamples; ++n) 235 { 236 ubyte b = _io.read_ubyte(_userData); 237 outData[n] = (b - 128) / 127.0; 238 } 239 } 240 else if (_bytePerSample == 2) 241 { 242 for (n = 0; n < numSamples; ++n) 243 { 244 short s = _io.read_ushort_LE(_userData); 245 outData[n] = s / 32767.0; 246 } 247 } 248 else if (_bytePerSample == 3) 249 { 250 for (n = 0; n < numSamples; ++n) 251 { 252 int s = _io.read_24bits_LE(_userData); 253 // duplicate sign bit 254 s = (s << 8) >> 8; 255 outData[n] = s / 8388607.0; 256 } 257 } 258 else if (_bytePerSample == 4) 259 { 260 for (n = 0; n < numSamples; ++n) 261 { 262 int s = _io.read_uint_LE(_userData); 263 outData[n] = s / 2147483648.0; 264 } 265 } 266 else 267 throw mallocNew!Exception("Unsupported bit-depth for integer PCM data, should be 8, 16, 24 or 32 bits."); 268 } 269 else 270 assert(false); // should have been handled earlier, crash 271 } 272 catch(Exception e) 273 { 274 destroyFree(e); // well this is really unexpected, since no read should fail in this loop 275 return 0; 276 } 277 278 // Return number of integer samples read 279 return frames; 280 } 281 282 package: 283 int _sampleRate; 284 int _channels; 285 int _audioFormat; 286 int _bytePerSample; 287 long _samplesOffsetInFile; 288 uint _lengthInFrames; 289 uint _framePosition; 290 291 private: 292 void* _userData; 293 IOCallbacks* _io; 294 } 295 } 296 297 298 version(encodeWAV) 299 { 300 /// Use both for scanning and decoding 301 final class WAVEncoder 302 { 303 public: 304 @nogc: 305 enum Format 306 { 307 fp32le, 308 fp64le, 309 } 310 311 this(IOCallbacks* io, void* userData, int sampleRate, int numChannels, Format format) 312 { 313 _io = io; 314 _userData = userData; 315 _channels = numChannels; 316 _format = format; 317 318 // Avoids a number of edge cases. 319 if (_channels < 0 || _channels > 1024) 320 throw mallocNew!Exception("Can't save a WAV with this numnber of channels."); 321 322 // RIFF header 323 // its size will be overwritten at finalizing 324 _riffLengthOffset = _io.tell(_userData) + 4; 325 _io.writeRIFFChunkHeader(_userData, RIFFChunkId!"RIFF", 0); 326 _io.write_uint_BE(_userData, RIFFChunkId!"WAVE"); 327 328 // 'fmt ' sub-chunk 329 _io.writeRIFFChunkHeader(_userData, RIFFChunkId!"fmt ", 0x10); 330 _io.write_ushort_LE(_userData, FloatingPointIEEE); 331 _io.write_ushort_LE(_userData, cast(ushort)(_channels)); 332 _io.write_uint_LE(_userData, sampleRate); 333 334 size_t bytesPerSec = sampleRate * cast(size_t) frameSize(); 335 _io.write_uint_LE(_userData, cast(uint)(bytesPerSec)); 336 337 int bytesPerFrame = frameSize(); 338 _io.write_ushort_LE(_userData, cast(ushort)bytesPerFrame); 339 340 _io.write_ushort_LE(_userData, cast(ushort)(sampleSize() * 8)); 341 342 // data sub-chunk 343 _dataLengthOffset = _io.tell(_userData) + 4; 344 _io.writeRIFFChunkHeader(_userData, RIFFChunkId!"data", 0); // write 0 but temporarily, this will be overwritten at finalizing 345 _writtenFrames = 0; 346 } 347 348 // read interleaved samples 349 // `inSamples` should have enough room for frames * _channels 350 int writeSamples(T)(T* inSamples, int frames) nothrow 351 { 352 int n = 0; 353 try 354 { 355 int samples = frames * _channels; 356 357 final switch(_format) 358 { 359 case Format.fp32le: 360 for ( ; n < samples; ++n) 361 { 362 _io.write_float_LE(_userData, inSamples[n]); 363 } 364 break; 365 case Format.fp64le: 366 for ( ; n < samples; ++n) 367 { 368 _io.write_double_LE(_userData, inSamples[n]); 369 } 370 break; 371 } 372 _writtenFrames += frames; 373 } 374 catch(Exception e) 375 { 376 destroyFree(e); 377 } 378 return n; 379 } 380 381 int sampleSize() 382 { 383 final switch(_format) 384 { 385 case Format.fp32le: return 4; 386 case Format.fp64le: return 8; 387 } 388 } 389 390 int frameSize() 391 { 392 return sampleSize() * _channels; 393 } 394 395 void finalizeEncoding() 396 { 397 size_t bytesOfData = frameSize() * _writtenFrames; 398 399 // write final number of samples for the 'RIFF' chunk 400 { 401 uint riffLength = cast(uint)( 4 + (4 + 4 + 16) + (4 + 4 + bytesOfData) ); 402 _io.seek(_riffLengthOffset, false, _userData); 403 _io.write_uint_LE(_userData, riffLength); 404 } 405 406 // write final number of samples for the 'data' chunk 407 { 408 _io.seek(_dataLengthOffset, false, _userData); 409 _io.write_uint_LE(_userData, cast(uint)bytesOfData ); 410 } 411 } 412 413 private: 414 void* _userData; 415 IOCallbacks* _io; 416 Format _format; 417 int _channels; 418 int _writtenFrames; 419 long _riffLengthOffset, _dataLengthOffset; 420 } 421 } 422 423 424 private: 425 426 // wFormatTag 427 immutable int LinearPCM = 0x0001; 428 immutable int FloatingPointIEEE = 0x0003; 429 immutable int WAVE_FORMAT_EXTENSIBLE = 0xFFFE;