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