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