| 23 | // --------------------------------------------------------------------------- |
| 24 | |
| 25 | function _parseWavHeader(buf) { |
| 26 | const view = new DataView(buf); |
| 27 | const tag = (off) => String.fromCharCode(...new Uint8Array(buf, off, 4)); |
| 28 | |
| 29 | if (tag(0) !== "RIFF" || tag(8) !== "WAVE") return null; |
| 30 | |
| 31 | let audioFormat = 1, channels = 2, sampleRate = 44100, bitsPerSample = 16; |
| 32 | let dataOffset = -1, dataSize = 0; |
| 33 | |
| 34 | let off = 12; |
| 35 | while (off + 8 <= buf.byteLength) { |
| 36 | const id = tag(off); |
| 37 | const size = view.getUint32(off + 4, true); |
| 38 | if (id === "fmt ") { |
| 39 | audioFormat = view.getUint16(off + 8, true); |
| 40 | channels = view.getUint16(off + 10, true); |
| 41 | sampleRate = view.getUint32(off + 12, true); |
| 42 | bitsPerSample = view.getUint16(off + 22, true); |
| 43 | } else if (id === "data") { |
| 44 | dataOffset = off + 8; |
| 45 | dataSize = size; |
| 46 | break; |
| 47 | } |
| 48 | off += 8 + size + (size & 1); // chunks are word-aligned |
| 49 | } |
| 50 | |
| 51 | if (dataOffset < 0) return null; |
| 52 | |
| 53 | const bytesPerFrame = channels * (bitsPerSample >> 3); |
| 54 | return { |
| 55 | audioFormat, channels, sampleRate, bitsPerSample, |
| 56 | dataOffset, dataSize, bytesPerFrame, |
| 57 | duration: dataSize / (bytesPerFrame * sampleRate), |
| 58 | }; |
| 59 | } |
| 60 | |
| 61 | // Convert raw interleaved PCM bytes to an AudioBuffer. |
| 62 | // Fast paths for the common cases (stereo 16-bit, stereo float32). |