OXYGENHUB
◆ main openmsx / src / sound / SoundDevice.cc
10.2 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "SoundDevice.hh"
2
3
#include "MSXMixer.hh"
4
#include "Mixer.hh"
5
6
#include "DeviceConfig.hh"
7
#include "Filename.hh"
8
#include "MSXException.hh"
9
#include "XMLElement.hh"
10
11
#include "StringOp.hh"
12
#include "inplace_buffer.hh"
13
#include "narrow.hh"
14
#include "one_of.hh"
15
#include "xrange.hh"
16
17
#include <algorithm>
18
#include <bit>
19
#include <cassert>
20
#include <cmath>
21
22
namespace openmsx {
23
24
[[nodiscard]] static std::string makeUnique(const MSXMixer& mixer, std::string_view name)
25
{
26
	std::string result(name);
27
	if (mixer.findDevice(result)) {
28
		unsigned n = 0;
29
		do {
30
			result = strCat(name, " (", ++n, ')');
31
		} while (mixer.findDevice(result));
32
	}
33
	return result;
34
}
35
36
void SoundDevice::addFill(float*& buf, float val, unsigned num)
37
{
38
	// Note: in the past we tried to optimize this by always producing
39
	// a multiple of 4 output values. In the general case a SoundDevice is
40
	// allowed to do this, but only at the end of the sound buffer. This
41
	// method can also be called in the middle of a buffer (so multiple
42
	// times per buffer), in such case it does go wrong.
43
	assert(num > 0);
44
	do {
45
		*buf++ += val;
46
	} while (--num);
47
}
48
49
SoundDevice::SoundDevice(MSXMixer& mixer_, std::string_view name_, static_string_view description_,
50
			 unsigned numChannels_, unsigned inputRate, bool stereo_)
51
	: mixer(mixer_)
52
	, name(makeUnique(mixer, name_))
53
	, description(description_)
54
	, numChannels(numChannels_)
55
	, stereo(stereo_ ? 2 : 1)
56
{
57
	assert(numChannels <= MAX_CHANNELS);
58
	assert(stereo == one_of(1u, 2u));
59
60
	setInputRate(inputRate);
61
62
	// initially no channels are muted
63
	std::ranges::fill(channelMuted, false);
64
	std::ranges::fill(channelBalance, Balance{.left = 1.0f, .right = 1.0f});
65
}
66
67
SoundDevice::~SoundDevice() = default;
68
69
float SoundDevice::getAmplificationFactorImpl() const
70
{
71
	return 1.0f / 32768.0f;
72
}
73
74
void SoundDevice::registerSound(const DeviceConfig& config)
75
{
76
	const auto& soundConfig = config.getChild("sound");
77
	float volume = narrow<float>(soundConfig.getChildDataAsInt("volume", 0)) * (1.0f / 32767.0f);
78
	int devBalance = 0;
79
	std::string_view mode = soundConfig.getChildData("mode", "mono");
80
	if (mode == "mono") {
81
		devBalance = 0;
82
	} else if (mode == "left") {
83
		devBalance = -100;
84
	} else if (mode == "right") {
85
		devBalance = 100;
86
	} else {
87
		throw MSXException("balance \"", mode, "\" illegal");
88
	}
89
90
	for (const auto* b : soundConfig.getChildren("balance")) {
91
		auto balance = StringOp::stringTo<int>(b->getData());
92
		if (!balance) {
93
			throw MSXException("balance ", b->getData(), " illegal");
94
		}
95
		if ((*balance < -100) || (balance > 100)) {
96
			throw MSXException("balance must be between -100...100: ", *balance);
97
		}
98
99
		const auto* channel = b->findAttribute("channel");
100
		if (!channel) {
101
			devBalance = *balance;
102
			continue;
103
		}
104
105
		auto channels = StringOp::parseRange(channel->getValue(), 1, numChannels);
106
		channels.foreachSetBit([&](size_t c) {
107
			setBalance(unsigned(c - 1), narrow_cast<float>(*balance) * (1.0f / 100.0f));
108
		});
109
	}
110
111
	mixer.registerSound(*this, volume, devBalance, numChannels);
112
}
113
114
void SoundDevice::setBalance(unsigned channel, float balance)
115
{
116
	assert(channel < numChannels);
117
	assert(-1.0f <= balance);
118
	assert(balance <= 1.0f);
119
120
	if (balance != 0.0f) balanceCenter = false;
121
	auto left  = std::sqrt(1.0f - balance);
122
	auto right = std::sqrt(1.0f + balance);
123
	channelBalance[channel] = Balance{.left = left, .right = right};
124
}
125
126
void SoundDevice::postSetBalance()
127
{
128
	mixer.updateSoftwareVolume(*this);
129
}
130
131
void SoundDevice::unregisterSound()
132
{
133
	mixer.unregisterSound(*this);
134
}
135
136
void SoundDevice::updateStream(EmuTime time)
137
{
138
	mixer.updateStream(time);
139
}
140
141
void SoundDevice::setSoftwareVolume(float volume, EmuTime time)
142
{
143
	setSoftwareVolume(volume, volume, time);
144
}
145
146
void SoundDevice::setSoftwareVolume(float left, float right, EmuTime time)
147
{
148
	updateStream(time);
149
	softwareVolumeLeft  = left;
150
	softwareVolumeRight = right;
151
	mixer.updateSoftwareVolume(*this);
152
}
153
154
void SoundDevice::recordChannel(unsigned channel, const std::string& filename)
155
{
156
	assert(channel < numChannels);
157
	bool wasRecording = writer[channel].has_value();
158
	if (!filename.empty()) {
159
		writer[channel].emplace(
160
			filename, stereo, inputSampleRate);
161
	} else {
162
		writer[channel].reset();
163
	}
164
	bool recording = writer[channel].has_value();
165
	if (recording != wasRecording) {
166
		if (recording) {
167
			if (numRecordChannels == 0) {
168
				mixer.setSynchronousMode(true);
169
			}
170
			++numRecordChannels;
171
			assert(numRecordChannels <= numChannels);
172
		} else {
173
			assert(numRecordChannels > 0);
174
			--numRecordChannels;
175
			if (numRecordChannels == 0) {
176
				mixer.setSynchronousMode(false);
177
			}
178
		}
179
	}
180
}
181
182
void SoundDevice::muteChannel(unsigned channel, bool muted)
183
{
184
	assert(channel < numChannels);
185
	channelMuted[channel] = muted;
186
}
187
188
std::span<const float> SoundDevice::getLastBuffer(unsigned channel)
189
{
190
	assert(channel < numChannels);
191
	auto& buf = channelBuffers[channel];
192
193
	buf.requestCounter = inputSampleRate; // active for ~1 second
194
195
	unsigned requestedSize = getLastBufferSize();
196
	if (buf.stopIdx < requestedSize) return {}; // not enough samples (yet)
197
	if (buf.silent >= requestedSize) return {}; // silent
198
	return {&buf.buffer[buf.stopIdx - requestedSize], requestedSize};
199
}
200
201
bool SoundDevice::mixChannels(float* dataOut, size_t samples)
202
{
203
	if (samples == 0) return true;
204
	size_t outputStereo = isStereo() ? 2 : 1;
205
206
	inplace_buffer<float*, MAX_CHANNELS> bufs(uninitialized_tag{}, numChannels);
207
208
	// TODO optimization: All channels with the same balance (according to
209
	// channelBalance[]) could use the same buffer when balanceCenter is
210
	// false
211
	auto needSeparateBuffer = [&](unsigned channel) {
212
		return channelBuffers[channel].requestCounter != 0
213
		    || channelMuted[channel]
214
		    || writer[channel]
215
		    || !balanceCenter;
216
	};
217
	bool anySeparateChannel = false;
218
	auto size = narrow<unsigned>(samples * stereo);
219
	auto padded = (size + 3) & ~3; // round up to multiple of 4
220
	for (auto i : xrange(numChannels)) {
221
		auto& cb = channelBuffers[i];
222
		if (!needSeparateBuffer(i)) {
223
			// no need to keep this channel separate
224
			bufs[i] = dataOut;
225
			cb.stopIdx = 0; // no valid last data
226
		} else {
227
			anySeparateChannel = true;
228
			cb.requestCounter = (cb.requestCounter < samples) ? 0 : unsigned(cb.requestCounter - samples);
229
230
			if (auto remainingSize = narrow<unsigned>(cb.buffer.size() - cb.stopIdx);
231
			    remainingSize < padded) {
232
				// not enough space at the tail of the buffer
233
				unsigned lastBufferSize = getLastBufferSize();
234
				if (auto allocateSize = 2 * std::max(lastBufferSize, padded);
235
				    cb.buffer.size() < allocateSize) [[unlikely]] {
236
					// increase buffer size
237
					cb.buffer.resize(allocateSize);
238
				}
239
				unsigned reuse = lastBufferSize >= size ? lastBufferSize - size : 0;
240
				if (cb.stopIdx > reuse) {
241
					// move existing data to front
242
					memmove(&cb.buffer[0], &cb.buffer[cb.stopIdx - reuse], reuse * sizeof(float));
243
				}
244
				cb.stopIdx = reuse;
245
			}
246
			auto* ptr = &cb.buffer[cb.stopIdx];
247
			bufs[i] = ptr;
248
			std::ranges::fill(std::span{ptr, size}, 0.0f);
249
			cb.stopIdx += size;
250
		}
251
	}
252
253
	static_assert(sizeof(float) == sizeof(uint32_t));
254
	if ((numChannels != 1) || anySeparateChannel) {
255
		// The generateChannels() method of SoundDevices with more than
256
		// one channel will _add_ the generated channel data in the
257
		// provided buffers. Those with only one channel will directly
258
		// replace the content of the buffer. For the former we must
259
		// start from a buffer containing all zeros.
260
		std::ranges::fill(std::span{dataOut, outputStereo * samples}, 0.0f);
261
	}
262
263
	generateChannels(bufs, narrow<unsigned>(samples));
264
265
	if (!anySeparateChannel) {
266
		return std::ranges::any_of(xrange(numChannels),
267
		                      [&](auto i) { return bufs[i]; });
268
	}
269
270
	for (auto i : xrange(numChannels)) {
271
		// record channels
272
		if (writer[i]) {
273
			assert(bufs[i] != dataOut);
274
			if (bufs[i]) {
275
				auto amp = getAmplificationFactor();
276
				if (stereo == 1) {
277
					writer[i]->write(
278
						std::span{bufs[i], samples},
279
						amp.left);
280
				} else {
281
					writer[i]->write(
282
						std::span{std::bit_cast<const StereoFloat*>(bufs[i]), samples},
283
						amp.left, amp.right);
284
				}
285
			} else {
286
				writer[i]->writeSilence(narrow<unsigned>(stereo * samples));
287
			}
288
		}
289
		// update silent info in channelBuffers
290
		auto& cb = channelBuffers[i];
291
		if (bufs[i]) {
292
			cb.silent = 0;
293
		} else {
294
			cb.silent += narrow<unsigned>(samples);
295
		}
296
	}
297
298
	// remove muted channels (explicitly by user or by device itself)
299
	bool anyUnmuted = false;
300
	unsigned numMix = 0;
301
	inplace_buffer<Balance, MAX_CHANNELS> mixBalance(uninitialized_tag{}, numChannels);
302
	for (auto i : xrange(numChannels)) {
303
		if (bufs[i] && !channelMuted[i]) {
304
			anyUnmuted = true;
305
			if (bufs[i] != dataOut) {
306
				bufs[numMix] = bufs[i];
307
				mixBalance[numMix] = channelBalance[i];
308
				++numMix;
309
			}
310
		}
311
	}
312
313
	if (numMix == 0) {
314
		// all extra channels muted
315
		return anyUnmuted;
316
	}
317
318
	// actually mix channels
319
	if (!balanceCenter) {
320
		size_t i = 0;
321
		do {
322
			float left0  = 0.0f;
323
			float right0 = 0.0f;
324
			float left1  = 0.0f;
325
			float right1 = 0.0f;
326
			unsigned j = 0;
327
			do {
328
				left0  += bufs[j][i + 0] * mixBalance[j].left;
329
				left1  += bufs[j][i + 1] * mixBalance[j].left;
330
				right0 += bufs[j][i + 0] * mixBalance[j].right;
331
				right1 += bufs[j][i + 1] * mixBalance[j].right;
332
				j++;
333
			} while (j < numMix);
334
			dataOut[i * 2 + 0] = left0;
335
			dataOut[i * 2 + 1] = right0;
336
			dataOut[i * 2 + 2] = left1;
337
			dataOut[i * 2 + 3] = right1;
338
			i += 2;
339
		} while (i < samples);
340
341
		return true;
342
	}
343
344
	// In the past we had ARM and x86-SSE2 optimized assembly routines for
345
	// the stuff below. Currently this code is only rarely used anymore
346
	// (only when recording or muting individual sound chip channels), so
347
	// it's not worth the extra complexity anymore.
348
	size_t num = samples * stereo;
349
	size_t i = 0;
350
	do {
351
		auto out0 = dataOut[i + 0];
352
		auto out1 = dataOut[i + 1];
353
		auto out2 = dataOut[i + 2];
354
		auto out3 = dataOut[i + 3];
355
		unsigned j = 0;
356
		do {
357
			out0 += bufs[j][i + 0];
358
			out1 += bufs[j][i + 1];
359
			out2 += bufs[j][i + 2];
360
			out3 += bufs[j][i + 3];
361
			++j;
362
		} while (j < numMix);
363
		dataOut[i + 0] = out0;
364
		dataOut[i + 1] = out1;
365
		dataOut[i + 2] = out2;
366
		dataOut[i + 3] = out3;
367
		i += 4;
368
	} while (i < num);
369
370
	return true;
371
}
372
373
const DynamicClock& SoundDevice::getHostSampleClock() const
374
{
375
	return mixer.getHostSampleClock();
376
}
377
double SoundDevice::getEffectiveSpeed() const
378
{
379
	return mixer.getEffectiveSpeed();
380
}
381
382
} // namespace openmsx