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