OXYGENHUB
◆ main openmsx / src / sound / WavData.hh
3.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
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 /*freq*/) const {}
23
		uint16_t operator()(uint16_t x) const { return x; }
24
	};
25
26
public:
27
	/** Construct empty wav. */
28
	WavData() = default;
29
30
	/** Construct from .wav file and optional filter. */
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
	// Read and check header
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
	// Skip any extra format bytes
96
	size_t pos = 20 + header->fmtSize;
97
98
	// Find 'data' chunk
99
	struct DataHeader {
100
		std::array<char, 4> dataID;
101
		Endian::L32 chunkSize;
102
	};
103
	const DataHeader* dataHeader;
104
	while (true) {
105
		// Read chunk header
106
		dataHeader = read<DataHeader>(raw, pos);
107
		pos += sizeof(DataHeader);
108
		if (std::string_view{dataHeader->dataID.data(), 4} == "data") break;
109
		// Skip non-data chunk
110
		pos += dataHeader->chunkSize;
111
	}
112
113
	// Read and convert sample data
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; // discard all but the first channel
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
} // namespace openmsx
133
134
#endif