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 this(IOCallbacks* io, void* userData) nothrow 21 { 22 _io = io; 23 _userData = userData; 24 } 25 26 // After scan, we know _sampleRate, _lengthInFrames, and _channels, and can call `readSamples` 27 void scan() 28 { 29 // check RIFF header 30 { 31 uint chunkId, chunkSize; 32 _io.readRIFFChunkHeader(_userData, chunkId, chunkSize); 33 if (chunkId != RIFFChunkId!"RIFF") 34 throw mallocNew!Exception("Expected RIFF chunk."); 35 36 if (chunkSize < 4) 37 throw mallocNew!Exception("RIFF chunk is too small to contain a format."); 38 39 if (_io.read_uint_BE(_userData) != RIFFChunkId!"WAVE") 40 throw mallocNew!Exception("Expected WAVE format."); 41 } 42 43 bool foundFmt = false; 44 bool foundData = false; 45 46 int byteRate; 47 int blockAlign; 48 int bitsPerSample; 49 50 while (!_io.nothingToReadAnymore(_userData)) 51 { 52 // Some corrupted WAV files in the wild finish with one 53 // extra 0 byte after an AFAn chunk, very odd 54 if (_io.remainingBytesToRead(_userData) == 1) 55 { 56 if (_io.peek_ubyte(_userData) == 0) 57 break; 58 } 59 60 // Question: is there any reason to parse the whole WAV file? This prevents streaming. 61 62 uint chunkId, chunkSize; 63 _io.readRIFFChunkHeader(_userData, chunkId, chunkSize); 64 if (chunkId == RIFFChunkId!"fmt ") 65 { 66 if (foundFmt) 67 throw mallocNew!Exception("Found several 'fmt ' chunks in RIFF file."); 68 69 foundFmt = true; 70 71 if (chunkSize < 16) 72 throw mallocNew!Exception("Expected at least 16 bytes in 'fmt ' chunk."); // found in real-world for the moment: 16 or 40 bytes 73 74 _audioFormat = _io.read_ushort_LE(_userData); 75 if (_audioFormat == WAVE_FORMAT_EXTENSIBLE) 76 throw mallocNew!Exception("No support for format WAVE_FORMAT_EXTENSIBLE yet."); // Reference: http://msdn.microsoft.com/en-us/windows/hardware/gg463006.aspx 77 78 if (_audioFormat != LinearPCM && _audioFormat != FloatingPointIEEE) 79 throw mallocNew!Exception("Unsupported audio format, only PCM and IEEE float are supported."); 80 81 _channels = _io.read_ushort_LE(_userData); 82 83 _sampleRate = _io.read_uint_LE(_userData); 84 if (_sampleRate <= 0) 85 throw mallocNew!Exception("Unsupported sample-rate."); // we do not support sample-rate higher than 2^31hz 86 87 uint bytesPerSec = _io.read_uint_LE(_userData); 88 int bytesPerFrame = _io.read_ushort_LE(_userData); 89 bitsPerSample = _io.read_ushort_LE(_userData); 90 91 if (bitsPerSample != 8 && bitsPerSample != 16 && bitsPerSample != 24 && bitsPerSample != 32) 92 throw mallocNew!Exception("Unsupported bitdepth"); 93 94 if (bytesPerFrame != (bitsPerSample / 8) * _channels) 95 throw mallocNew!Exception("Invalid bytes-per-second, data might be corrupted."); 96 97 _io.skip(chunkSize - 16, _userData); 98 } 99 else if (chunkId == RIFFChunkId!"data") 100 { 101 if (foundData) 102 throw mallocNew!Exception("Found several 'data' chunks in RIFF file."); 103 104 if (!foundFmt) 105 throw mallocNew!Exception("'fmt ' chunk expected before the 'data' chunk."); 106 107 _bytePerSample = bitsPerSample / 8; 108 uint frameSize = _channels * _bytePerSample; 109 if (chunkSize % frameSize != 0) 110 throw mallocNew!Exception("Remaining bytes in 'data' chunk, inconsistent with audio data type."); 111 112 uint numFrames = chunkSize / frameSize; 113 _lengthInFrames = numFrames; 114 115 _samplesOffsetInFile = _io.tell(_userData); 116 117 _io.skip(chunkSize, _userData); // skip, will read later 118 foundData = true; 119 } 120 else 121 { 122 // ignore unknown chunks 123 _io.skip(chunkSize, _userData); 124 } 125 } 126 127 if (!foundFmt) 128 throw mallocNew!Exception("'fmt ' chunk not found."); 129 130 if (!foundData) 131 throw mallocNew!Exception("'data' chunk not found."); 132 133 // Get ready to decode 134 _io.seek(_samplesOffsetInFile, false, _userData); 135 _framePosition = 0; // seek to start 136 } 137 138 // read interleaved samples 139 // `outData` should have enough room for frames * _channels 140 // Returs: Frames actually read. 141 int readSamples(float* outData, int maxFrames) nothrow 142 { 143 assert(_framePosition <= _lengthInFrames); 144 int available = _lengthInFrames - _framePosition; 145 146 // How much frames can we decode? 147 int frames = maxFrames; 148 if (frames > available) 149 frames = available; 150 _framePosition += frames; 151 152 int numSamples = frames * _channels; 153 154 uint n = 0; 155 156 try 157 { 158 if (_audioFormat == FloatingPointIEEE) 159 { 160 if (_bytePerSample == 4) 161 { 162 for (n = 0; n < numSamples; ++n) 163 outData[n] = _io.read_float_LE(_userData); 164 } 165 else if (_bytePerSample == 8) 166 { 167 for (n = 0; n < numSamples; ++n) 168 outData[n] = _io.read_double_LE(_userData); 169 } 170 else 171 throw mallocNew!Exception("Unsupported bit-depth for floating point data, should be 32 or 64."); 172 } 173 else if (_audioFormat == LinearPCM) 174 { 175 if (_bytePerSample == 1) 176 { 177 for (n = 0; n < numSamples; ++n) 178 { 179 ubyte b = _io.read_ubyte(_userData); 180 outData[n] = (b - 128) / 127.0; 181 } 182 } 183 else if (_bytePerSample == 2) 184 { 185 for (n = 0; n < numSamples; ++n) 186 { 187 short s = _io.read_ushort_LE(_userData); 188 outData[n] = s / 32767.0; 189 } 190 } 191 else if (_bytePerSample == 3) 192 { 193 for (n = 0; n < numSamples; ++n) 194 { 195 int s = _io.read_24bits_LE(_userData); 196 // duplicate sign bit 197 s = (s << 8) >> 8; 198 outData[n] = s / 8388607.0; 199 } 200 } 201 else if (_bytePerSample == 4) 202 { 203 for (n = 0; n < numSamples; ++n) 204 { 205 int s = _io.read_uint_LE(_userData); 206 outData[n] = s / 2147483648.0; 207 } 208 } 209 else 210 throw mallocNew!Exception("Unsupported bit-depth for integer PCM data, should be 8, 16, 24 or 32 bits."); 211 } 212 else 213 assert(false); // should have been handled earlier, crash 214 } 215 catch(Exception e) 216 { 217 destroyFree(e); // well this is really unexpected, since no read should fail in this loop 218 return 0; 219 } 220 221 // Return number of integer samples read 222 return frames; 223 } 224 225 package: 226 int _sampleRate; 227 int _channels; 228 int _audioFormat; 229 int _bytePerSample; 230 long _samplesOffsetInFile; 231 uint _lengthInFrames; 232 uint _framePosition; 233 234 private: 235 void* _userData; 236 IOCallbacks* _io; 237 } 238 } 239 240 241 version(encodeWAV) 242 { 243 /// Use both for scanning and decoding 244 final class WAVEncoder 245 { 246 public: 247 @nogc: 248 this(IOCallbacks* io, void* userData, int sampleRate, int numChannels) 249 { 250 _io = io; 251 _userData = userData; 252 _channels = numChannels; 253 254 // Avoids a number of edge cases. 255 if (_channels < 0 || _channels > 1024) 256 throw mallocNew!Exception("Can't save a WAV with this numnber of channels."); 257 258 // RIFF header 259 // its size will be overwritten at finalizing 260 _riffLengthOffset = _io.tell(_userData) + 4; 261 _io.writeRIFFChunkHeader(_userData, RIFFChunkId!"RIFF", 0); 262 _io.write_uint_BE(_userData, RIFFChunkId!"WAVE"); 263 264 // 'fmt ' sub-chunk 265 _io.writeRIFFChunkHeader(_userData, RIFFChunkId!"fmt ", 0x10); 266 _io.write_ushort_LE(_userData, FloatingPointIEEE); 267 _io.write_ushort_LE(_userData, cast(ushort)(_channels)); 268 _io.write_uint_LE(_userData, sampleRate); 269 270 size_t bytesPerSec = sampleRate * _channels * float.sizeof; 271 _io.write_uint_LE(_userData, cast(uint)(bytesPerSec)); 272 273 int bytesPerFrame = cast(int)(_channels * float.sizeof); 274 _io.write_ushort_LE(_userData, cast(ushort)bytesPerFrame); 275 276 _io.write_ushort_LE(_userData, 32); 277 278 // data sub-chunk 279 _dataLengthOffset = _io.tell(_userData) + 4; 280 _io.writeRIFFChunkHeader(_userData, RIFFChunkId!"data", 0); // write 0 but temporarily, this will be overwritten at finalizing 281 _writtenFrames = 0; 282 } 283 284 // read interleaved samples 285 // `inSamples` should have enough room for frames * _channels 286 int writeSamples(float* inSamples, int frames) nothrow 287 { 288 int n = 0; 289 try 290 { 291 int samples = frames * _channels; 292 for ( ; n < samples; ++n) 293 { 294 _io.write_float_LE(_userData, inSamples[n]); 295 } 296 _writtenFrames += frames; 297 } 298 catch(Exception e) 299 { 300 destroyFree(e); 301 } 302 return n; 303 } 304 305 void finalizeEncoding() 306 { 307 size_t bytesOfData = float.sizeof * _channels * _writtenFrames; 308 309 // write final number of samples for the 'RIFF' chunk 310 { 311 uint riffLength = cast(uint)( 4 + (4 + 4 + 16) + (4 + 4 + bytesOfData) ); 312 _io.seek(_riffLengthOffset, false, _userData); 313 _io.write_uint_LE(_userData, riffLength); 314 } 315 316 // write final number of samples for the 'data' chunk 317 { 318 _io.seek(_dataLengthOffset, false, _userData); 319 _io.write_uint_LE(_userData, cast(uint)bytesOfData ); 320 } 321 } 322 323 private: 324 void* _userData; 325 IOCallbacks* _io; 326 int _channels; 327 int _writtenFrames; 328 long _riffLengthOffset, _dataLengthOffset; 329 } 330 } 331 332 333 private: 334 335 // wFormatTag 336 immutable int LinearPCM = 0x0001; 337 immutable int FloatingPointIEEE = 0x0003; 338 immutable int WAVE_FORMAT_EXTENSIBLE = 0xFFFE;