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