| 1 |
#ifndef WAVDATA_HH |
| 2 |
#define WAVDATA_HH |
| 3 |
|
| 4 |
#include "File.hh" |
| 5 |
#include "MSXException.hh" |
| 6 |
#include "MemBuffer.hh" |
| 7 |
|
| 8 |
#include "endian.hh" |
| 9 |
#include "one_of.hh" |
| 10 |
#include "xrange.hh" |
| 11 |
|
| 12 |
#include <array> |
| 13 |
#include <bit> |
| 14 |
#include <cstdint> |
| 15 |
#include <span> |
| 16 |
|
| 17 |
namespace openmsx { |
| 18 |
|
| 19 |
class WavData |
| 20 |
{ |
| 21 |
struct NoFilter { |
| 22 |
void setFreq(unsigned ) const {} |
| 23 |
uint16_t operator()(uint16_t x) const { return x; } |
| 24 |
}; |
| 25 |
|
| 26 |
public: |
| 27 |
|
| 28 |
WavData() = default; |
| 29 |
|
| 30 |
|
| 31 |
template<typename Filter = NoFilter> |
| 32 |
explicit WavData(zstring_view filename, Filter filter = {}) |
| 33 |
: WavData(File(filename), filter) {} |
| 34 |
|
| 35 |
template<typename Filter = NoFilter> |
| 36 |
explicit WavData(File file, Filter filter = {}); |
| 37 |
|
| 38 |
[[nodiscard]] unsigned getFreq() const { return freq; } |
| 39 |
[[nodiscard]] size_t getSize() const { return buffer.size(); } |
| 40 |
[[nodiscard]] int16_t getSample(size_t pos) const { |
| 41 |
return (pos < buffer.size()) ? buffer[pos] : int16_t(0); |
| 42 |
} |
| 43 |
|
| 44 |
private: |
| 45 |
template<typename T> |
| 46 |
[[nodiscard]] static const T* read(std::span<const uint8_t> raw, size_t offset, size_t count = 1); |
| 47 |
|
| 48 |
private: |
| 49 |
MemBuffer<int16_t> buffer; |
| 50 |
unsigned freq = 0; |
| 51 |
}; |
| 52 |
|
| 53 |
|
| 54 |
|
| 55 |
template<typename T> |
| 56 |
inline const T* WavData::read(std::span<const uint8_t> raw, size_t offset, size_t count) |
| 57 |
{ |
| 58 |
if ((offset + count * sizeof(T)) > raw.size()) { |
| 59 |
throw MSXException("Read beyond end of wav file."); |
| 60 |
} |
| 61 |
return std::bit_cast<const T*>(raw.data() + offset); |
| 62 |
} |
| 63 |
|
| 64 |
template<typename Filter> |
| 65 |
inline WavData::WavData(File file, Filter filter) |
| 66 |
{ |
| 67 |
|
| 68 |
auto raw = file.mmap<const uint8_t>(); |
| 69 |
struct WavHeader { |
| 70 |
std::array<char, 4> riffID; |
| 71 |
Endian::L32 riffSize; |
| 72 |
std::array<char, 4> riffType; |
| 73 |
std::array<char, 4> fmtID; |
| 74 |
Endian::L32 fmtSize; |
| 75 |
Endian::L16 wFormatTag; |
| 76 |
Endian::L16 wChannels; |
| 77 |
Endian::L32 dwSamplesPerSec; |
| 78 |
Endian::L32 dwAvgBytesPerSec; |
| 79 |
Endian::L16 wBlockAlign; |
| 80 |
Endian::L16 wBitsPerSample; |
| 81 |
}; |
| 82 |
const auto* header = read<WavHeader>(raw, 0); |
| 83 |
if ((std::string_view{header->riffID.data(), 4} != "RIFF") || |
| 84 |
(std::string_view{header->riffType.data(), 4} != "WAVE") || |
| 85 |
(std::string_view{header->fmtID.data(), 4} != "fmt ")) { |
| 86 |
throw MSXException("Invalid WAV file."); |
| 87 |
} |
| 88 |
unsigned bits = header->wBitsPerSample; |
| 89 |
if ((header->wFormatTag != 1) || (bits != one_of(8u, 16u))) { |
| 90 |
throw MSXException("WAV format unsupported, must be 8 or 16 bit PCM."); |
| 91 |
} |
| 92 |
freq = header->dwSamplesPerSec; |
| 93 |
unsigned channels = header->wChannels; |
| 94 |
|
| 95 |
|
| 96 |
size_t pos = 20 + header->fmtSize; |
| 97 |
|
| 98 |
|
| 99 |
struct DataHeader { |
| 100 |
std::array<char, 4> dataID; |
| 101 |
Endian::L32 chunkSize; |
| 102 |
}; |
| 103 |
const DataHeader* dataHeader; |
| 104 |
while (true) { |
| 105 |
|
| 106 |
dataHeader = read<DataHeader>(raw, pos); |
| 107 |
pos += sizeof(DataHeader); |
| 108 |
if (std::string_view{dataHeader->dataID.data(), 4} == "data") break; |
| 109 |
|
| 110 |
pos += dataHeader->chunkSize; |
| 111 |
} |
| 112 |
|
| 113 |
|
| 114 |
size_t length = dataHeader->chunkSize / ((bits / 8) * channels); |
| 115 |
buffer.resize(length); |
| 116 |
filter.setFreq(freq); |
| 117 |
auto convertLoop = [&](const auto* in, auto convertFunc) { |
| 118 |
for (auto i : xrange(length)) { |
| 119 |
buffer[i] = filter(convertFunc(*in)); |
| 120 |
in += channels; |
| 121 |
} |
| 122 |
}; |
| 123 |
if (bits == 8) { |
| 124 |
convertLoop(read<uint8_t>(raw, pos, length * channels), |
| 125 |
[](uint8_t u8) { return int16_t((int16_t(u8) - 0x80) << 8); }); |
| 126 |
} else { |
| 127 |
convertLoop(read<Endian::L16>(raw, pos, length * channels), |
| 128 |
[](Endian::L16 s16) { return int16_t(s16); }); |
| 129 |
} |
| 130 |
} |
| 131 |
|
| 132 |
} |
| 133 |
|
| 134 |
#endif |