OXYGENHUB
◆ main openmsx / src / sound / MSXMixer.cc
23.8 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#include "MSXMixer.hh"
2
3
#include "Mixer.hh"
4
#include "SoundDevice.hh"
5
6
#include "AviRecorder.hh"
7
#include "BooleanSetting.hh"
8
#include "CommandException.hh"
9
#include "FileOperations.hh"
10
#include "Filename.hh"
11
#include "GlobalSettings.hh"
12
#include "IntegerSetting.hh"
13
#include "MSXCliComm.hh"
14
#include "MSXCommandController.hh"
15
#include "MSXMotherBoard.hh"
16
#include "StringSetting.hh"
17
#include "TclObject.hh"
18
#include "ThrottleManager.hh"
19
20
#include "Math.hh"
21
#include "aligned.hh"
22
#include "enumerate.hh"
23
#include "inplace_buffer.hh"
24
#include "narrow.hh"
25
#include "one_of.hh"
26
#include "outer.hh"
27
#include "ranges.hh"
28
#include "stl.hh"
29
#include "unreachable.hh"
30
31
#include <algorithm>
32
#include <cassert>
33
#include <cmath>
34
#include <memory>
35
#include <ranges>
36
#include <tuple>
37
38
namespace openmsx {
39
40
MSXMixer::MSXMixer(Mixer& mixer_, MSXMotherBoard& motherBoard_,
41
                   GlobalSettings& globalSettings)
42
	: Schedulable(motherBoard_.getScheduler())
43
	, mixer(mixer_)
44
	, motherBoard(motherBoard_)
45
	, commandController(motherBoard.getMSXCommandController())
46
	, masterVolume(mixer.getMasterVolume())
47
	, speedManager(globalSettings.getSpeedManager())
48
	, throttleManager(globalSettings.getThrottleManager())
49
	, prevTime(getCurrentTime(), 44100)
50
	, soundDeviceInfo(commandController.getMachineInfoCommand())
51
{
52
	reschedule2();
53
54
	masterVolume.attach(*this);
55
	speedManager.attach(*this);
56
	throttleManager.attach(*this);
57
}
58
59
MSXMixer::~MSXMixer()
60
{
61
	if (recorder) {
62
		recorder->stop();
63
	}
64
	assert(infos.empty());
65
66
	throttleManager.detach(*this);
67
	speedManager.detach(*this);
68
	masterVolume.detach(*this);
69
70
	mute(); // calls Mixer::unregisterMixer()
71
}
72
73
MSXMixer::SoundDeviceInfo::SoundDeviceInfo(unsigned numChannels)
74
	: channelSettings(numChannels)
75
{
76
}
77
78
void MSXMixer::registerSound(SoundDevice& device, float volume,
79
                             int balance, unsigned numChannels)
80
{
81
	// TODO read volume/balance(mode) from config file
82
	const std::string& name = device.getName();
83
	SoundDeviceInfo info(numChannels);
84
	info.device = &device;
85
	info.defaultVolume = volume;
86
	info.volumeSetting = std::make_unique<IntegerSetting>(
87
		commandController, tmpStrCat(name, "_volume"),
88
		"the volume of this sound chip", 75, 0, 100);
89
	info.balanceSetting = std::make_unique<IntegerSetting>(
90
		commandController, tmpStrCat(name, "_balance"),
91
		"the balance of this sound chip", balance, -100, 100);
92
93
	info.volumeSetting->attach(*this);
94
	info.balanceSetting->attach(*this);
95
96
	for (auto&& [i, channelSettings] : enumerate(info.channelSettings)) {
97
		auto ch_name = tmpStrCat(name, "_ch", i + 1);
98
99
		channelSettings.record = std::make_unique<StringSetting>(
100
			commandController, tmpStrCat(ch_name, "_record"),
101
			"filename to record this channel to",
102
			std::string_view{}, Setting::Save::NO);
103
		channelSettings.record->attach(*this);
104
105
		channelSettings.mute = std::make_unique<BooleanSetting>(
106
			commandController, tmpStrCat(ch_name, "_mute"),
107
			"sets mute-status of individual sound channels",
108
			false, Setting::Save::NO);
109
		channelSettings.mute->attach(*this);
110
	}
111
112
	device.setOutputRate(getSampleRate(), speedManager.getSpeed());
113
	auto& i = infos.emplace_back(std::move(info));
114
	updateVolumeParams(i);
115
116
	commandController.getCliComm().update(CliComm::UpdateType::SOUND_DEVICE, device.getName(), "add");
117
}
118
119
void MSXMixer::unregisterSound(SoundDevice& device)
120
{
121
	auto it = rfind_unguarded(infos, &device, &SoundDeviceInfo::device);
122
	it->volumeSetting->detach(*this);
123
	it->balanceSetting->detach(*this);
124
	for (auto& s : it->channelSettings) {
125
		s.record->detach(*this);
126
		s.mute->detach(*this);
127
	}
128
	move_pop_back(infos, it);
129
	commandController.getCliComm().update(CliComm::UpdateType::SOUND_DEVICE, device.getName(), "remove");
130
}
131
132
void MSXMixer::setSynchronousMode(bool synchronous)
133
{
134
	// TODO ATM synchronous is not used anymore
135
	if (synchronous) {
136
		++synchronousCounter;
137
		if (synchronousCounter == 1) {
138
			setMixerParams(fragmentSize, hostSampleRate);
139
		}
140
	} else {
141
		assert(synchronousCounter > 0);
142
		--synchronousCounter;
143
		if (synchronousCounter == 0) {
144
			setMixerParams(fragmentSize, hostSampleRate);
145
		}
146
	}
147
}
148
149
double MSXMixer::getEffectiveSpeed() const
150
{
151
	return synchronousCounter ? 1.0 : speedManager.getSpeed();
152
}
153
154
void MSXMixer::updateStream(EmuTime time)
155
{
156
	unsigned count = prevTime.getTicksTill(time);
157
	assert(count <= 8192);
158
	inplace_buffer<StereoFloat, 8192> mixBuffer(uninitialized_tag{}, count);
159
160
	// call generate() even if count==0 and even if muted
161
	generate(mixBuffer, time);
162
163
	if (!muteCount && fragmentSize) {
164
		mixer.uploadBuffer(*this, mixBuffer);
165
	}
166
167
	if (recorder) {
168
		recorder->addWave(mixBuffer);
169
	}
170
171
	prevTime += count;
172
}
173
174
175
// Various (inner) loops that multiply one buffer by a constant and add the
176
// result to a second buffer. Either buffer can be mono or stereo, so if
177
// necessary the mono buffer is expanded to stereo. It's possible the
178
// accumulation buffer is still empty (as-if it contains zeros), in that case
179
// we skip the accumulation step.
180
181
// buf[0:n] *= f
182
static inline void mul(float* buf, size_t n, float f)
183
{
184
	// C++ version, unrolled 4x,
185
	//   this allows gcc/clang to do much better auto-vectorization
186
	// Note that this can process upto 3 samples too many, but that's OK.
187
	size_t i = 0;
188
	do {
189
		buf[i + 0] *= f;
190
		buf[i + 1] *= f;
191
		buf[i + 2] *= f;
192
		buf[i + 3] *= f;
193
		i += 4;
194
	} while (i < n);
195
}
196
static inline void mul(std::span<float> buf, float f)
197
{
198
	assert(!buf.empty());
199
	mul(buf.data(), buf.size(), f);
200
}
201
static inline void mul(std::span<StereoFloat> buf, float f)
202
{
203
	assert(!buf.empty());
204
	mul(&buf.data()->left, 2 * buf.size(), f);
205
}
206
207
// acc[0:n] += mul[0:n] * f
208
static inline void mulAcc(
209
	float* __restrict acc, const float* __restrict mul, size_t n, float f)
210
{
211
	// C++ version, unrolled 4x, see comments above.
212
	size_t i = 0;
213
	do {
214
		acc[i + 0] += mul[i + 0] * f;
215
		acc[i + 1] += mul[i + 1] * f;
216
		acc[i + 2] += mul[i + 2] * f;
217
		acc[i + 3] += mul[i + 3] * f;
218
		i += 4;
219
	} while (i < n);
220
}
221
static inline void mulAcc(std::span<float> acc, std::span<const float> mul, float f)
222
{
223
	assert(!acc.empty());
224
	assert(acc.size() == mul.size());
225
	mulAcc(acc.data(), mul.data(), acc.size(), f);
226
}
227
static inline void mulAcc(std::span<StereoFloat> acc, std::span<const StereoFloat> mul, float f)
228
{
229
	assert(!acc.empty());
230
	assert(acc.size() == mul.size());
231
	mulAcc(&acc.data()->left, &mul.data()->left, 2 * acc.size(), f);
232
}
233
234
// buf[0:2n+0:2] = buf[0:n] * l
235
// buf[1:2n+1:2] = buf[0:n] * r
236
static inline void mulExpand(float* buf, size_t n, float l, float r)
237
{
238
	size_t i = n;
239
	do {
240
		--i; // back-to-front
241
		auto t = buf[i];
242
		buf[2 * i + 0] = l * t;
243
		buf[2 * i + 1] = r * t;
244
	} while (i != 0);
245
}
246
static inline void mulExpand(std::span<StereoFloat> buf, float l, float r)
247
{
248
	mulExpand(&buf.data()->left, buf.size(), l, r);
249
}
250
251
// acc[0:2n+0:2] += mul[0:n] * l
252
// acc[1:2n+1:2] += mul[0:n] * r
253
static inline void mulExpandAcc(
254
	float* __restrict acc, const float* __restrict mul, size_t n,
255
	float l, float r)
256
{
257
	size_t i = 0;
258
	do {
259
		auto t = mul[i];
260
		acc[2 * i + 0] += l * t;
261
		acc[2 * i + 1] += r * t;
262
	} while (++i < n);
263
}
264
static inline void mulExpandAcc(
265
	std::span<StereoFloat> acc, std::span<const float> mul, float l, float r)
266
{
267
	assert(!acc.empty());
268
	assert(acc.size() == mul.size());
269
	mulExpandAcc(&acc.data()->left, mul.data(), acc.size(), l, r);
270
}
271
272
// buf[0:2n+0:2] = buf[0:2n+0:2] * l1 + buf[1:2n+1:2] * l2
273
// buf[1:2n+1:2] = buf[0:2n+0:2] * r1 + buf[1:2n+1:2] * r2
274
static inline void mulMix2(std::span<StereoFloat> buf, float l1, float l2, float r1, float r2)
275
{
276
	assert(!buf.empty());
277
	for (auto& s : buf) {
278
		auto t1 = s.left;
279
		auto t2 = s.right;
280
		s.left  = l1 * t1 + l2 * t2;
281
		s.right = r1 * t1 + r2 * t2;
282
	}
283
}
284
285
// acc[0:2n+0:2] += mul[0:2n+0:2] * l1 + mul[1:2n+1:2] * l2
286
// acc[1:2n+1:2] += mul[0:2n+0:2] * r1 + mul[1:2n+1:2] * r2
287
static inline void mulMix2Acc(
288
	std::span<StereoFloat> acc, std::span<const StereoFloat> mul,
289
	float l1, float l2, float r1, float r2)
290
{
291
	assert(!acc.empty());
292
	assert(acc.size() == mul.size());
293
	auto n = acc.size();
294
	size_t i = 0;
295
	do {
296
		auto t1 = mul[i].left;
297
		auto t2 = mul[i].right;
298
		acc[i].left  += l1 * t1 + l2 * t2;
299
		acc[i].right += r1 * t1 + r2 * t2;
300
	} while (++i < n);
301
}
302
303
304
// DC removal filter routines:
305
//
306
//  formula:
307
//     y(n) = x(n) - x(n-1) + R * y(n-1)
308
//  implemented as:
309
//     t1 = R * t0 + x(n)    mathematically equivalent, has
310
//     y(n) = t1 - t0        the same number of operations but
311
//     t0 = t1               requires only one state variable
312
//    see: http://en.wikipedia.org/wiki/Digital_filter#Direct_Form_I
313
//  with:
314
//     R = 1 - (2*pi * cut-off-frequency / sample-rate)
315
//  we take R = 511/512
316
//   44100Hz --> cutoff freq = 14Hz
317
//   22050Hz                     7Hz
318
static constexpr auto R = 511.0f / 512.0f;
319
320
// No new input, previous output was (non-zero) mono.
321
static inline float filterMonoNull(float t0, std::span<StereoFloat> out)
322
{
323
	assert(!out.empty());
324
	for (auto& o : out) {
325
		auto t1 = R * t0;
326
		auto s = t1 - t0;
327
		o.left = s;
328
		o.right = s;
329
		t0 = t1;
330
	}
331
	return t0;
332
}
333
334
// No new input, previous output was (non-zero) stereo.
335
static inline std::tuple<float, float> filterStereoNull(
336
	float tl0, float tr0, std::span<StereoFloat> out)
337
{
338
	assert(!out.empty());
339
	for (auto& o : out) {
340
		float tl1 = R * tl0;
341
		float tr1 = R * tr0;
342
		o.left  = tl1 - tl0;
343
		o.right = tr1 - tr0;
344
		tl0 = tl1;
345
		tr0 = tr1;
346
	}
347
	return {tl0, tr0};
348
}
349
350
// New input is mono, previous output was also mono.
351
static inline float filterMonoMono(
352
	float t0, std::span<const float> in, std::span<StereoFloat> out)
353
{
354
	assert(in.size() == out.size());
355
	assert(!out.empty());
356
	for (auto [i, o] : std::views::zip(in, out)) {
357
		auto t1 = R * t0 + i;
358
		auto s = t1 - t0;
359
		o.left  = s;
360
		o.right = s;
361
		t0 = t1;
362
	}
363
	return t0;
364
}
365
366
// New input is mono, previous output was stereo
367
static inline std::tuple<float, float>
368
filterStereoMono(float tl0, float tr0,
369
                 std::span<const float> in,
370
                 std::span<StereoFloat> out)
371
{
372
	assert(in.size() == out.size());
373
	assert(!out.empty());
374
	for (auto [i, o] : std::views::zip(in, out)) {
375
		auto tl1 = R * tl0 + i;
376
		auto tr1 = R * tr0 + i;
377
		o.left  = tl1 - tl0;
378
		o.right = tr1 - tr0;
379
		tl0 = tl1;
380
		tr0 = tr1;
381
	}
382
	return {tl0, tr0};
383
}
384
385
// New input is stereo, (previous output either mono/stereo)
386
static inline std::tuple<float, float>
387
filterStereoStereo(float tl0, float tr0,
388
                   std::span<const StereoFloat> in,
389
                   std::span<StereoFloat> out)
390
{
391
	assert(in.size() == out.size());
392
	assert(!out.empty());
393
	for (auto [i, o] : std::views::zip(in, out)) {
394
		auto tl1 = R * tl0 + i.left;
395
		auto tr1 = R * tr0 + i.right;
396
		o.left  = tl1 - tl0;
397
		o.right = tr1 - tr0;
398
		tl0 = tl1;
399
		tr0 = tr1;
400
	}
401
	return {tl0, tr0};
402
}
403
404
// We have both mono and stereo input (and produce stereo output)
405
static inline std::tuple<float, float>
406
filterBothStereo(float tl0, float tr0,
407
                 std::span<const float> inM,
408
                 std::span<const StereoFloat> inS,
409
                 std::span<StereoFloat> out)
410
{
411
	assert(inM.size() == out.size());
412
	assert(inS.size() == out.size());
413
	assert(!out.empty());
414
	for (auto [im, is, o] : std::views::zip(inM, inS, out)) {
415
		auto tl1 = R * tl0 + is.left  + im;
416
		auto tr1 = R * tr0 + is.right + im;
417
		o.left  = tl1 - tl0;
418
		o.right = tr1 - tr0;
419
		tl0 = tl1;
420
		tr0 = tr1;
421
	}
422
	return {tl0, tr0};
423
}
424
425
static bool approxEqual(float x, float y)
426
{
427
	constexpr float threshold = 1.0f / 32768;
428
	return std::abs(x - y) < threshold;
429
}
430
431
void MSXMixer::generate(std::span<StereoFloat> output, EmuTime time)
432
{
433
	Math::DenormalGuard noDenormals; // flush denormals to zero in this scope
434
435
	// The code below is specialized for a lot of cases (before this
436
	// routine was _much_ shorter). This is done because this routine
437
	// ends up relatively high (top 5) in a profile run.
438
	// After these specialization this routine runs about two times
439
	// faster for the common cases (mono output or no sound at all).
440
	// In total emulation time this gave a speedup of about 2%.
441
442
	// When samples==0, call updateBuffer() but skip all further processing
443
	// (handling this as a special case allows to simplify the code below).
444
	auto samples = output.size(); // per channel
445
	assert(samples <= 8192);
446
	if (samples == 0) {
447
		ALIGNAS_SSE std::array<float, 4> dummyBuf;
448
		for (auto& info : infos) {
449
			bool ignore = info.device->updateBuffer(0, dummyBuf.data(), time);
450
			(void)ignore;
451
		}
452
		return;
453
	}
454
455
	// +3 to allow processing samples in groups of 4 (and upto 3 samples
456
	// more than requested).
457
	inplace_buffer<float,       8192 + 3> monoBufExtra  (uninitialized_tag{}, samples + 3);
458
	inplace_buffer<StereoFloat, 8192 + 3> stereoBufExtra(uninitialized_tag{}, samples + 3);
459
	inplace_buffer<StereoFloat, 8192 + 3> tmpBufExtra   (uninitialized_tag{}, samples + 3);
460
	auto* monoBufPtr   = monoBufExtra.data();
461
	auto* stereoBufPtr = &stereoBufExtra.data()->left;
462
	auto* tmpBufPtr    = &tmpBufExtra.data()->left; // can be used either for mono or stereo data
463
	auto monoBuf      = subspan(monoBufExtra,   0, samples);
464
	auto stereoBuf    = subspan(stereoBufExtra, 0, samples);
465
	auto tmpBufStereo = subspan(tmpBufExtra,    0, samples); // StereoFloat
466
	auto tmpBufMono   = std::span{tmpBufPtr, samples};      // float
467
468
	constexpr unsigned HAS_MONO_FLAG = 1;
469
	constexpr unsigned HAS_STEREO_FLAG = 2;
470
	unsigned usedBuffers = 0;
471
472
	// TODO: The Infos should be ordered such that all the mono
473
	// devices are handled first
474
	for (auto& info : infos) {
475
		SoundDevice& device = *info.device;
476
		auto l1 = info.left1;
477
		auto r1 = info.right1;
478
		if (!device.isStereo()) {
479
			// device generates mono output
480
			if (l1 == r1) {
481
				// no re-panning (means mono remains mono)
482
				if (!(usedBuffers & HAS_MONO_FLAG)) {
483
					// generate in 'monoBuf' (because it was still empty)
484
					// then multiply in-place
485
					if (device.updateBuffer(samples, monoBufPtr, time)) {
486
						usedBuffers |= HAS_MONO_FLAG;
487
						mul(monoBuf, l1);
488
					}
489
				} else {
490
					// generate in 'tmpBuf' (as mono data)
491
					// then multiply-accumulate into 'monoBuf'
492
					if (device.updateBuffer(samples, tmpBufPtr, time)) {
493
						mulAcc(monoBuf, tmpBufMono, l1);
494
					}
495
				}
496
			} else {
497
				// re-panning -> mono expands to different left and right result
498
				if (!(usedBuffers & HAS_STEREO_FLAG)) {
499
					// 'stereoBuf' (which is still empty) is first filled with mono-data,
500
					// then in-place expanded to stereo-data
501
					if (device.updateBuffer(samples, stereoBufPtr, time)) {
502
						usedBuffers |= HAS_STEREO_FLAG;
503
						mulExpand(stereoBuf, l1, r1);
504
					}
505
				} else {
506
					// 'tmpBuf' is first filled with mono-data,
507
					// then expanded to stereo and mul-acc into 'stereoBuf'
508
					if (device.updateBuffer(samples, tmpBufPtr, time)) {
509
						mulExpandAcc(stereoBuf, tmpBufMono, l1, r1);
510
					}
511
				}
512
			}
513
		} else {
514
			// device generates stereo output
515
			auto l2 = info.left2;
516
			auto r2 = info.right2;
517
			if (l1 == r2) {
518
				// no re-panning
519
				assert(l2 == 0.0f);
520
				assert(r1 == 0.0f);
521
				if (!(usedBuffers & HAS_STEREO_FLAG)) {
522
					// generate in 'stereoBuf' (because it was still empty)
523
					// then multiply in-place
524
					if (device.updateBuffer(samples, stereoBufPtr, time)) {
525
						usedBuffers |= HAS_STEREO_FLAG;
526
						mul(stereoBuf, l1);
527
					}
528
				} else {
529
					// generate in 'tmpBuf' (as stereo data)
530
					// then multiply-accumulate into 'stereoBuf'
531
					if (device.updateBuffer(samples, tmpBufPtr, time)) {
532
						mulAcc(stereoBuf, tmpBufStereo, l1);
533
					}
534
				}
535
			} else {
536
				// re-panning, this means each of the individual left or right generated
537
				// channels gets distributed over both the left and right output channels
538
				if (!(usedBuffers & HAS_STEREO_FLAG)) {
539
					// generate in 'stereoBuf' (because it was still empty)
540
					// then mix in-place
541
					if (device.updateBuffer(samples, stereoBufPtr, time)) {
542
						usedBuffers |= HAS_STEREO_FLAG;
543
						mulMix2(stereoBuf, l1, l2, r1, r2);
544
					}
545
				} else {
546
					// 'tmpBuf' is first filled with stereo-data,
547
					// then mixed into stereoBuf
548
					if (device.updateBuffer(samples, tmpBufPtr, time)) {
549
						mulMix2Acc(stereoBuf, tmpBufStereo, l1, l2, r1, r2);
550
					}
551
				}
552
			}
553
		}
554
	}
555
556
	// DC removal filter
557
	switch (usedBuffers) {
558
	case 0: // no new input
559
		if (approxEqual(tl0, tr0)) {
560
			if (approxEqual(tl0, 0.0f)) {
561
				// Output was zero, new input is zero,
562
				// after DC-filter output will still be zero.
563
				std::ranges::fill(output, StereoFloat{});
564
				tl0 = tr0 = 0.0f;
565
			} else {
566
				// Output was not zero, but it was the same left and right.
567
				tl0 = filterMonoNull(tl0, output);
568
				tr0 = tl0;
569
			}
570
		} else {
571
			std::tie(tl0, tr0) = filterStereoNull(tl0, tr0, output);
572
		}
573
		break;
574
575
	case HAS_MONO_FLAG: // only mono
576
		if (approxEqual(tl0, tr0)) {
577
			// previous output was also mono
578
			tl0 = filterMonoMono(tl0, monoBuf, output);
579
			tr0 = tl0;
580
		} else {
581
			// previous output was stereo, rarely triggers but needed for correctness
582
			std::tie(tl0, tr0) = filterStereoMono(tl0, tr0, monoBuf, output);
583
		}
584
		break;
585
586
	case HAS_STEREO_FLAG: // only stereo
587
		std::tie(tl0, tr0) = filterStereoStereo(tl0, tr0, stereoBuf, output);
588
		break;
589
590
	default: // mono + stereo
591
		std::tie(tl0, tr0) = filterBothStereo(tl0, tr0, monoBuf, stereoBuf, output);
592
	}
593
}
594
595
bool MSXMixer::needStereoRecording() const
596
{
597
	return std::ranges::any_of(infos, [](auto& info) {
598
		return info.device->isStereo() ||
599
		       info.balanceSetting->getInt() != 0;
600
	});
601
}
602
603
void MSXMixer::mute()
604
{
605
	if (muteCount == 0) {
606
		mixer.unregisterMixer(*this);
607
	}
608
	++muteCount;
609
}
610
611
void MSXMixer::unmute()
612
{
613
	--muteCount;
614
	if (muteCount == 0) {
615
		tl0 = tr0 = 0.0f;
616
		mixer.registerMixer(*this);
617
	}
618
}
619
620
void MSXMixer::reInit()
621
{
622
	prevTime.reset(getCurrentTime());
623
	prevTime.setPeriod(EmuDuration::sec(getEffectiveSpeed() / double(hostSampleRate)));
624
	reschedule();
625
}
626
void MSXMixer::reschedule()
627
{
628
	removeSyncPoints();
629
	reschedule2();
630
}
631
void MSXMixer::reschedule2()
632
{
633
	unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512;
634
	setSyncPoint(prevTime.getFastAdd(size));
635
}
636
637
void MSXMixer::setMixerParams(unsigned newFragmentSize, unsigned newSampleRate)
638
{
639
	// TODO old code checked that values did actually change,
640
	//      investigate if this optimization is worth it
641
	hostSampleRate = newSampleRate;
642
	fragmentSize = newFragmentSize;
643
644
	reInit(); // must come before call to setOutputRate()
645
646
	for (auto& info : infos) {
647
		info.device->setOutputRate(newSampleRate, speedManager.getSpeed());
648
	}
649
}
650
651
void MSXMixer::setRecorder(AviRecorder* newRecorder)
652
{
653
	if ((recorder != nullptr) != (newRecorder != nullptr)) {
654
		setSynchronousMode(newRecorder != nullptr);
655
	}
656
	recorder = newRecorder;
657
}
658
659
void MSXMixer::update(const Setting& setting) noexcept
660
{
661
	if (&setting == &masterVolume) {
662
		updateMasterVolume();
663
	} else if (dynamic_cast<const IntegerSetting*>(&setting)) {
664
		auto it = find_if_unguarded(infos,
665
			[&](const SoundDeviceInfo& i) {
666
				return &setting == one_of(i.volumeSetting .get(),
667
				                          i.balanceSetting.get()); });
668
		updateVolumeParams(*it);
669
	} else if (dynamic_cast<const StringSetting*>(&setting)) {
670
		changeRecordSetting(setting);
671
	} else if (dynamic_cast<const BooleanSetting*>(&setting)) {
672
		changeMuteSetting(setting);
673
	} else {
674
		UNREACHABLE;
675
	}
676
}
677
678
void MSXMixer::changeRecordSetting(const Setting& setting)
679
{
680
	for (auto& info : infos) {
681
		for (auto&& [channel, settings] : enumerate(info.channelSettings)) {
682
			if (settings.record.get() == &setting) {
683
				info.device->recordChannel(
684
					unsigned(channel),
685
					FileOperations::expandTilde(std::string(
686
						 settings.record->getString())));
687
				return;
688
			}
689
		}
690
	}
691
	UNREACHABLE;
692
}
693
694
void MSXMixer::changeMuteSetting(const Setting& setting)
695
{
696
	for (auto& info : infos) {
697
		for (auto&& [channel, settings] : enumerate(info.channelSettings)) {
698
			if (settings.mute.get() == &setting) {
699
				info.device->muteChannel(
700
					unsigned(channel), settings.mute->getBoolean());
701
				return;
702
			}
703
		}
704
	}
705
	UNREACHABLE;
706
}
707
708
void MSXMixer::update(const SpeedManager& /*speedManager*/) noexcept
709
{
710
	if (synchronousCounter == 0) {
711
		setMixerParams(fragmentSize, hostSampleRate);
712
	} else {
713
		// Avoid calling reInit() while recording because
714
		// each call causes a small hiccup in the sound (and
715
		// while recording this call anyway has no effect).
716
		// This was noticeable while sliding the speed slider
717
		// in catapult (because this causes many changes in
718
		// the speed setting).
719
	}
720
}
721
722
void MSXMixer::update(const ThrottleManager& /*throttleManager*/) noexcept
723
{
724
	//reInit();
725
	// TODO Should this be removed?
726
}
727
728
void MSXMixer::updateVolumeParams(SoundDeviceInfo& info) const
729
{
730
	int mVolume = masterVolume.getInt();
731
	int dVolume = info.volumeSetting->getInt();
732
	float volume = info.defaultVolume * narrow<float>(mVolume) * narrow<float>(dVolume) * (1.0f / (100.0f * 100.0f));
733
	int balance = info.balanceSetting->getInt();
734
	auto [l1, r1, l2, r2] = [&] {
735
		if (info.device->isStereo()) {
736
			if (balance < 0) {
737
				float b = (narrow<float>(balance) + 100.0f) * (1.0f / 100.0f);
738
				return std::tuple{
739
					/*l1 =*/ volume,
740
					/*r1 =*/ 0.0f,
741
					/*l2 =*/ volume * sqrtf(std::max(0.0f, 1.0f - b)),
742
					/*r2 =*/ volume * sqrtf(std::max(0.0f,        b))
743
				};
744
			} else {
745
				float b = narrow<float>(balance) * (1.0f / 100.0f);
746
				return std::tuple{
747
					/*l1 =*/ volume * sqrtf(std::max(0.0f, 1.0f - b)),
748
					/*r1 =*/ volume * sqrtf(std::max(0.0f,        b)),
749
					/*l2 =*/ 0.0f,
750
					/*r2 =*/ volume
751
				};
752
			}
753
		} else {
754
			// make sure that in case of rounding errors
755
			// we don't take sqrt() of negative numbers
756
			float b = (narrow<float>(balance) + 100.0f) * (1.0f / 200.0f);
757
			return std::tuple{
758
				/*l1 =*/ volume * sqrtf(std::max(0.0f, 1.0f - b)),
759
				/*r1 =*/ volume * sqrtf(std::max(0.0f,        b)),
760
				/*l2 =*/ 0.0f, // dummy
761
				/*r2 =*/ 0.0f  // dummy
762
			};
763
		}
764
	}();
765
	auto [ampL, ampR] = info.device->getAmplificationFactor();
766
	info.left1  = l1 * ampL;
767
	info.right1 = r1 * ampR;
768
	info.left2  = l2 * ampL;
769
	info.right2 = r2 * ampR;
770
}
771
772
void MSXMixer::updateMasterVolume()
773
{
774
	for (auto& p : infos) {
775
		updateVolumeParams(p);
776
	}
777
}
778
779
void MSXMixer::updateSoftwareVolume(SoundDevice& device)
780
{
781
	auto it = find_unguarded(infos, &device, &SoundDeviceInfo::device);
782
	updateVolumeParams(*it);
783
}
784
785
void MSXMixer::executeUntil(EmuTime time)
786
{
787
	updateStream(time);
788
	reschedule2();
789
790
	// This method gets called very regularly, typically 44100/512 = 86x
791
	// per second (even if sound is muted and even with sound_driver=null).
792
	// This rate is constant in real-time (compared to e.g. the VDP sync
793
	// points that are constant in EmuTime). So we can use this to
794
	// regularly exit from the main CPU emulation loop. Without this there
795
	// were problems like described in 'bug#563 Console very slow when
796
	// setting speed to low values like 1'.
797
	motherBoard.exitCPULoopSync();
798
}
799
800
801
// Sound device info
802
803
const MSXMixer::SoundDeviceInfo* MSXMixer::findDeviceInfo(std::string_view name) const
804
{
805
	auto it = std::ranges::find(infos, name,
806
		[](auto& i) { return i.device->getName(); });
807
	return (it != end(infos)) ? std::to_address(it) : nullptr;
808
}
809
810
SoundDevice* MSXMixer::findDevice(std::string_view name) const
811
{
812
	auto* info = findDeviceInfo(name);
813
	return info ? info->device : nullptr;
814
}
815
816
MSXMixer::SoundDeviceInfoTopic::SoundDeviceInfoTopic(
817
		InfoCommand& machineInfoCommand)
818
	: InfoTopic(machineInfoCommand, "sounddevice")
819
{
820
}
821
822
void MSXMixer::SoundDeviceInfoTopic::execute(
823
	std::span<const TclObject> tokens, TclObject& result) const
824
{
825
	auto& msxMixer = OUTER(MSXMixer, soundDeviceInfo);
826
	switch (tokens.size()) {
827
	case 2:
828
		result.addListElements(std::views::transform(
829
			msxMixer.infos,
830
			[](const auto& info) { return info.device->getName(); }));
831
		break;
832
	case 3: {
833
		const auto* device = msxMixer.findDevice(tokens[2].getString());
834
		if (!device) {
835
			throw CommandException("Unknown sound device");
836
		}
837
		result = device->getDescription();
838
		break;
839
	}
840
	default:
841
		throw CommandException("Too many parameters");
842
	}
843
}
844
845
std::string MSXMixer::SoundDeviceInfoTopic::help(std::span<const TclObject> /*tokens*/) const
846
{
847
	return "Shows a list of available sound devices.\n";
848
}
849
850
void MSXMixer::SoundDeviceInfoTopic::tabCompletion(std::vector<std::string>& tokens) const
851
{
852
	if (tokens.size() == 3) {
853
		completeString(tokens, std::views::transform(
854
			OUTER(MSXMixer, soundDeviceInfo).infos,
855
			[](auto& info) -> std::string_view { return info.device->getName(); }));
856
	}
857
}
858
859
} // namespace openmsx