OXYGENHUB
◆ main openmsx / src / cassette / WavImage.cc
4.3 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "WavImage.hh"
2
3
#include "File.hh"
4
#include "FilePool.hh"
5
#include "Filename.hh"
6
7
#include "Math.hh"
8
#include "narrow.hh"
9
#include "xrange.hh"
10
11
#include <algorithm>
12
#include <array>
13
#include <cassert>
14
#include <map>
15
16
namespace openmsx {
17
18
// DC-removal filter
19
//   y(n) = x(n) - x(n-1) + R * y(n-1)
20
// see comments in MSXMixer.cc for more details
21
class DCFilter {
22
public:
23
	void setFreq(unsigned sampleFreq) {
24
		const float cutOffFreq = 800.0f; // trial-and-error
25
		R = 1.0f - ((float(2 * Math::pi) * cutOffFreq) / narrow_cast<float>(sampleFreq));
26
	}
27
	[[nodiscard]] int16_t operator()(int16_t x) {
28
		float t1 = R * t0 + narrow_cast<float>(x);
29
		auto y = Math::clipToInt16(narrow_cast<int>(t1 - t0));
30
		t0 = t1;
31
		return y;
32
	}
33
private:
34
	float R = 0.0f;
35
	float t0 = 0.0f;
36
};
37
38
39
class WavImageCache
40
{
41
public:
42
	struct WavInfo {
43
		WavData wav;
44
		Sha1Sum sum;
45
	};
46
47
	WavImageCache(const WavImageCache&) = delete;
48
	WavImageCache(WavImageCache&&) = delete;
49
	WavImageCache& operator=(const WavImageCache&) = delete;
50
	WavImageCache& operator=(WavImageCache&&) = delete;
51
52
	static WavImageCache& instance();
53
	const WavInfo& get(const std::string& filename, FilePool& filePool);
54
	void release(const WavData* wav);
55
56
private:
57
	WavImageCache() = default;
58
	~WavImageCache();
59
60
	// typically contains very few elements, but values need stable addresses
61
	struct Entry {
62
		unsigned refCount = 0;
63
		WavInfo info;
64
	};
65
	std::map<std::string, Entry, std::less<>> cache;
66
};
67
68
WavImageCache::~WavImageCache()
69
{
70
	assert(cache.empty());
71
}
72
73
WavImageCache& WavImageCache::instance()
74
{
75
	static WavImageCache wavImageCache;
76
	return wavImageCache;
77
}
78
79
const WavImageCache::WavInfo& WavImageCache::get(const std::string& filename, FilePool& filePool)
80
{
81
	// Reading file or parsing as .wav may throw, so only create cache
82
	// entry after all went well.
83
	auto it = cache.find(filename);
84
	if (it == cache.end()) {
85
		File file(filename);
86
		Entry entry;
87
		entry.info.sum = filePool.getSha1Sum(file, filename);
88
		entry.info.wav = WavData(std::move(file), DCFilter{});
89
		it = cache.try_emplace(filename, std::move(entry)).first;
90
	}
91
	auto& entry = it->second;
92
	++entry.refCount;
93
	return entry.info;
94
95
}
96
97
void WavImageCache::release(const WavData* wav)
98
{
99
	// cache contains very few entries, so linear search is ok
100
	auto it = std::ranges::find(cache, wav, [](auto& pr) { return &pr.second.info.wav; });
101
	assert(it != end(cache));
102
	auto& entry = it->second;
103
	--entry.refCount; // decrease reference count
104
	if (entry.refCount == 0) {
105
		cache.erase(it);
106
	}
107
}
108
109
110
WavImage::WavImage(const Filename& filename, FilePool& filePool)
111
{
112
	const auto& entry = WavImageCache::instance().get(filename.getResolved(), filePool);
113
	wav = &entry.wav;
114
	setSha1Sum(entry.sum);
115
	clock.setFreq(wav->getFreq());
116
	// Note: type detection not implemented yet for WAV images
117
	setFirstFileType(FileType::UNKNOWN, filename);
118
}
119
120
WavImage::~WavImage()
121
{
122
	WavImageCache::instance().release(wav);
123
}
124
125
int16_t WavImage::getSampleAt(EmuTime time) const
126
{
127
	// The WAV file is typically sampled at 44kHz, but the MSX may sample
128
	// the signal at arbitrary moments in time. Initially we would simply
129
	// returned the closest older sample point (sample-and-hold
130
	// resampling). Now we perform cubic interpolation between the 4
131
	// surrounding sample points. Presumably this results in more accurately
132
	// timed zero-crossings of the signal.
133
	//
134
	// Thanks to 'p_gimeno' for figuring out that cubic resampling makes
135
	// the tape "Ingrid's back" sha1:9493e8851e9f173b67670a9a3de4645918ef436f
136
	// work in openMSX (with sample-and-hold it didn't work).
137
	auto [sample, x] = clock.getTicksTillAsIntFloat(time);
138
	std::array<float, 4> p = {
139
		float(wav->getSample(sample - 1)), // intentional: underflow wraps to UINT_MAX
140
		float(wav->getSample(sample + 0)),
141
		float(wav->getSample(sample + 1)),
142
		float(wav->getSample(sample + 2))
143
	};
144
	return Math::clipToInt16(int(Math::cubicHermite(p, x)));
145
}
146
147
EmuTime WavImage::getEndTime() const
148
{
149
	DynamicClock clk(clock);
150
	clk += wav->getSize();
151
	return clk.getTime();
152
}
153
154
unsigned WavImage::getFrequency() const
155
{
156
	return clock.getFreq();
157
}
158
159
void WavImage::fillBuffer(unsigned pos, std::span<float*, 1> bufs, unsigned num) const
160
{
161
	if (pos < wav->getSize()) {
162
		for (auto i : xrange(num)) {
163
			bufs[0][i] = wav->getSample(pos + i);
164
		}
165
	} else {
166
		bufs[0] = nullptr;
167
	}
168
}
169
170
float WavImage::getAmplificationFactorImpl() const
171
{
172
	return 1.0f / 32768;
173
}
174
175
} // namespace openmsx