openMSX
WavData.cc
Go to the documentation of this file.
1 #include "WavData.hh"
2 #include "MSXException.hh"
3 #include "endian.hh"
4 
5 namespace openmsx {
6 
7 template<typename T>
8 static const T* read(span<uint8_t> raw, size_t offset, size_t count = 1)
9 {
10  if ((offset + count * sizeof(T)) > raw.size()) {
11  throw MSXException("Read beyond end of wav file.");
12  }
13  return reinterpret_cast<const T*>(raw.data() + offset);
14 }
15 
17 {
18  // Read and check header
19  auto raw = file.mmap();
20  struct WavHeader {
21  char riffID[4];
22  Endian::L32 riffSize;
23  char riffType[4];
24  char fmtID[4];
25  Endian::L32 fmtSize;
26  Endian::L16 wFormatTag;
27  Endian::L16 wChannels;
28  Endian::L32 dwSamplesPerSec;
29  Endian::L32 dwAvgBytesPerSec;
30  Endian::L16 wBlockAlign;
31  Endian::L16 wBitsPerSample;
32  };
33  const auto* header = read<WavHeader>(raw, 0);
34  if (memcmp(header->riffID, "RIFF", 4) ||
35  memcmp(header->riffType, "WAVE", 4) ||
36  memcmp(header->fmtID, "fmt ", 4)) {
37  throw MSXException("Invalid WAV file.");
38  }
39  unsigned bits = header->wBitsPerSample;
40  if ((header->wFormatTag != 1) || ((bits != 8) && (bits != 16))) {
41  throw MSXException("WAV format unsupported, must be 8 or 16 bit PCM.");
42  }
43  freq = header->dwSamplesPerSec;
44  unsigned channels = header->wChannels;
45 
46  // Skip any extra format bytes
47  size_t pos = 20 + header->fmtSize;
48 
49  // Find 'data' chunk
50  struct DataHeader {
51  char dataID[4];
52  Endian::L32 chunkSize;
53  };
54  const DataHeader* dataHeader;
55  while (true) {
56  // Read chunk header
57  dataHeader = read<DataHeader>(raw, pos);
58  pos += sizeof(DataHeader);
59  if (!memcmp(dataHeader->dataID, "data", 4)) break;
60  // Skip non-data chunk
61  pos += dataHeader->chunkSize;
62  }
63 
64  // Read and convert sample data
65  length = dataHeader->chunkSize / ((bits / 8) * channels);
66  buffer.resize(length);
67  auto convertLoop = [&](const auto* in, auto convertFunc) {
68  for (unsigned i = 0; i < length; ++i) {
69  buffer[i] = convertFunc(*in);
70  in += channels; // discard all but the first channel
71  }
72  };
73  if (bits == 8) {
74  convertLoop(read<uint8_t>(raw, pos, length * channels),
75  [](uint8_t u8) { return (int16_t(u8) - 0x80) << 8; });
76  } else {
77  convertLoop(read<Endian::L16>(raw, pos, length * channels),
78  [](Endian::L16 s16) { return int16_t(s16); });
79  }
80 }
81 
82 } // namespace openmsx
Definition: span.hh:34
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
WavData()=default
Construct empty wav.
auto count(InputRange &&range, const T &value)
Definition: ranges.hh:197
span< uint8_t > mmap()
Map file in memory.
Definition: File.cc:93