OXYGENHUB
◆ main openmsx / src / sound / SamplePlayer.cc
3.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "SamplePlayer.hh"
2
3
#include "DeviceConfig.hh"
4
#include "FileContext.hh"
5
#include "MSXCliComm.hh"
6
#include "MSXException.hh"
7
#include "serialize.hh"
8
9
#include "narrow.hh"
10
#include "xrange.hh"
11
12
#include <cassert>
13
#include <memory>
14
15
namespace openmsx {
16
17
static constexpr unsigned DUMMY_INPUT_RATE = 44100; // actual rate depends on .wav files
18
19
[[nodiscard]] static auto loadSamples(
20
	std::string_view name, const DeviceConfig& config,
21
	std::string_view baseName, std::string_view alternativeName,
22
	unsigned numSamples)
23
{
24
	dynarray<WavData> result(numSamples); // initialize with empty WAVs
25
26
	bool alreadyWarned = false;
27
	const auto& context = systemFileContext();
28
	for (auto i : xrange(numSamples)) {
29
		try {
30
			auto filename = tmpStrCat(baseName, i, ".wav");
31
			result[i] = WavData(context.resolve(filename));
32
		} catch (MSXException& e1) {
33
			try {
34
				if (alternativeName.empty()) throw;
35
				auto filename = tmpStrCat(
36
					alternativeName, i, ".wav");
37
				result[i] = WavData(context.resolve(filename));
38
			} catch (MSXException& /*e2*/) {
39
				if (!alreadyWarned) {
40
					alreadyWarned = true;
41
					// print message from the 1st error
42
					config.getCliComm().printWarning(
43
						"Couldn't read ", name, " sample data: ",
44
						e1.getMessage(),
45
						". Continuing without sample data.");
46
				}
47
			}
48
		}
49
	}
50
	return result;
51
}
52
53
SamplePlayer::SamplePlayer(const std::string& name_, static_string_view desc,
54
                           const DeviceConfig& config,
55
                           std::string_view samplesBaseName, unsigned numSamples,
56
                           std::string_view alternativeName)
57
	: ResampledSoundDevice(config.getMotherBoard(), name_, desc, 1, DUMMY_INPUT_RATE, false)
58
	, samples(loadSamples(name_, config, samplesBaseName, alternativeName, numSamples))
59
{
60
	registerSound(config);
61
	reset();
62
}
63
64
SamplePlayer::~SamplePlayer()
65
{
66
	unregisterSound();
67
}
68
69
void SamplePlayer::reset()
70
{
71
	currentSampleNum = unsigned(-1);
72
	stopRepeat();
73
}
74
75
void SamplePlayer::play(unsigned sampleNum)
76
{
77
	assert(sampleNum < samples.size());
78
	currentSampleNum = sampleNum;
79
	index = 0;
80
	setWavParams();
81
}
82
83
void SamplePlayer::setWavParams()
84
{
85
	if ((currentSampleNum < samples.size()) &&
86
	    samples[currentSampleNum].getSize()) {
87
		const auto& wav = samples[currentSampleNum];
88
		bufferSize = narrow<unsigned>(wav.getSize());
89
90
		unsigned freq = wav.getFreq();
91
		if (freq != getInputRate()) {
92
			// this potentially switches resampler, so there might be
93
			// some dropped samples if this is done in the middle of
94
			// playing, though this shouldn't happen often (or at all)
95
			setInputRate(freq);
96
			createResampler();
97
		}
98
	} else {
99
		reset();
100
	}
101
}
102
103
void SamplePlayer::repeat(unsigned sampleNum)
104
{
105
	assert(sampleNum < samples.size());
106
	nextSampleNum = sampleNum;
107
	if (!isPlaying()) {
108
		doRepeat();
109
	}
110
}
111
112
void SamplePlayer::generateChannels(std::span<float*> bufs, unsigned num)
113
{
114
	// Single channel device: replace content of bufs[0] (not add to it).
115
	assert(bufs.size() == 1);
116
	if (!isPlaying()) {
117
		bufs[0] = nullptr;
118
		return;
119
	}
120
121
	const auto& wav = samples[currentSampleNum];
122
	for (auto i : xrange(num)) {
123
		if (index >= bufferSize) {
124
			if (nextSampleNum != unsigned(-1)) {
125
				doRepeat();
126
			} else {
127
				currentSampleNum = unsigned(-1);
128
				// fill remaining buffer with zeros
129
				do {
130
					bufs[0][i++] = 0.0f;
131
				} while (i < num);
132
				break;
133
			}
134
		}
135
		bufs[0][i] = narrow<float>(3 * wav.getSample(index++));
136
	}
137
}
138
139
void SamplePlayer::doRepeat()
140
{
141
	play(nextSampleNum);
142
}
143
144
template<typename Archive>
145
void SamplePlayer::serialize(Archive& ar, unsigned /*version*/)
146
{
147
	ar.serialize("index",            index,
148
	             "currentSampleNum", currentSampleNum,
149
	             "nextSampleNum",    nextSampleNum);
150
	if constexpr (Archive::IS_LOADER) {
151
		setWavParams();
152
	}
153
}
154
INSTANTIATE_SERIALIZE_METHODS(SamplePlayer);
155
156
} // namespace openmsx