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