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 "one_of.hh"
21#include "outer.hh"
22#include "ranges.hh"
23#include "unreachable.hh"
24#include "view.hh"
25#include "vla.hh"
26#include "xrange.hh"
27#include <cassert>
28#include <cmath>
29#include <cstring>
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 , recorder(nullptr)
51 , synchronousCounter(0)
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
80MSXMixer::SoundDeviceInfo::SoundDeviceInfo(unsigned numChannels)
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::SOUNDDEVICE, 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::SOUNDDEVICE, 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 union {
164 float mixBuffer[8192 * 2]; // make sure buffer is 32-bit aligned
165#ifdef __SSE2__
166 __m128i dummy2; // and optionally also 128-bit
167#endif
168 };
169
170 unsigned count = prevTime.getTicksTill(time);
171 assert(count <= 8192);
172
173 // call generate() even if count==0 and even if muted
174 generate(mixBuffer, time, count);
175
176 if (!muteCount && fragmentSize) {
177 mixer.uploadBuffer(*this, mixBuffer, count);
178 }
179
180 if (recorder) {
181 recorder->addWave(count, mixBuffer);
182 }
183
184 prevTime += count;
185}
186
187
188// Various (inner) loops that multiply one buffer by a constant and add the
189// result to a second buffer. Either buffer can be mono or stereo, so if
190// necessary the mono buffer is expanded to stereo. It's possible the
191// accumulation buffer is still empty (as-if it contains zeros), in that case
192// we skip the accumulation step.
193
194// buf[0:n] *= f
195static inline void mul(float* buf, int n, float f)
196{
197 // C++ version, unrolled 4x,
198 // this allows gcc/clang to do much better auto-vectorization
199 // Note that this can process upto 3 samples too many, but that's OK.
200 assume_SSE_aligned(buf);
201 int i = 0;
202 do {
203 buf[i + 0] *= f;
204 buf[i + 1] *= f;
205 buf[i + 2] *= f;
206 buf[i + 3] *= f;
207 i += 4;
208 } while (i < n);
209}
210
211// acc[0:n] += mul[0:n] * f
212static inline void mulAcc(
213 float* __restrict acc, const float* __restrict mul, int n, float f)
214{
215 // C++ version, unrolled 4x, see comments above.
216 assume_SSE_aligned(acc);
217 assume_SSE_aligned(mul);
218 int i = 0;
219 do {
220 acc[i + 0] += mul[i + 0] * f;
221 acc[i + 1] += mul[i + 1] * f;
222 acc[i + 2] += mul[i + 2] * f;
223 acc[i + 3] += mul[i + 3] * f;
224 i += 4;
225 } while (i < n);
226}
227
228// buf[0:2n+0:2] = buf[0:n] * l
229// buf[1:2n+1:2] = buf[0:n] * r
230static inline void mulExpand(float* buf, int n, float l, float r)
231{
232 int i = n;
233 do {
234 --i; // back-to-front
235 auto t = buf[i];
236 buf[2 * i + 0] = l * t;
237 buf[2 * i + 1] = r * t;
238 } while (i != 0);
239}
240
241// acc[0:2n+0:2] += mul[0:n] * l
242// acc[1:2n+1:2] += mul[0:n] * r
243static inline void mulExpandAcc(
244 float* __restrict acc, const float* __restrict mul, int n,
245 float l, float r)
246{
247 int i = 0;
248 do {
249 auto t = mul[i];
250 acc[2 * i + 0] += l * t;
251 acc[2 * i + 1] += r * t;
252 } while (++i < n);
253}
254
255// buf[0:2n+0:2] = buf[0:2n+0:2] * l1 + buf[1:2n+1:2] * l2
256// buf[1:2n+1:2] = buf[0:2n+0:2] * r1 + buf[1:2n+1:2] * r2
257static inline void mulMix2(float* buf, int n, float l1, float l2, float r1, float r2)
258{
259 int i = 0;
260 do {
261 auto t1 = buf[2 * i + 0];
262 auto t2 = buf[2 * i + 1];
263 buf[2 * i + 0] = l1 * t1 + l2 * t2;
264 buf[2 * i + 1] = r1 * t1 + r2 * t2;
265 } while (++i < n);
266}
267
268// acc[0:2n+0:2] += mul[0:2n+0:2] * l1 + mul[1:2n+1:2] * l2
269// acc[1:2n+1:2] += mul[0:2n+0:2] * r1 + mul[1:2n+1:2] * r2
270static inline void mulMix2Acc(
271 float* __restrict acc, const float* __restrict mul, int n,
272 float l1, float l2, float r1, float r2)
273{
274 int i = 0;
275 do {
276 auto t1 = mul[2 * i + 0];
277 auto t2 = mul[2 * i + 1];
278 acc[2 * i + 0] += l1 * t1 + l2 * t2;
279 acc[2 * i + 1] += r1 * t1 + r2 * t2;
280 } while (++i < n);
281}
282
283
284// DC removal filter routines:
285//
286// formula:
287// y(n) = x(n) - x(n-1) + R * y(n-1)
288// implemented as:
289// t1 = R * t0 + x(n) mathematically equivalent, has
290// y(n) = t1 - t0 the same number of operations but
291// t0 = t1 requires only one state variable
292// see: http://en.wikipedia.org/wiki/Digital_filter#Direct_Form_I
293// with:
294// R = 1 - (2*pi * cut-off-frequency / samplerate)
295// we take R = 511/512
296// 44100Hz --> cutoff freq = 14Hz
297// 22050Hz 7Hz
298constexpr auto R = 511.0f / 512.0f;
299
300// No new input, previous output was (non-zero) mono.
301static inline float filterMonoNull(float t0, float* __restrict out, int n)
302{
303 assert(n > 0);
304 int i = 0;
305 do {
306 auto t1 = R * t0;
307 auto s = t1 - t0;
308 out[2 * i + 0] = s;
309 out[2 * i + 1] = s;
310 t0 = t1;
311 } while (++i < n);
312 return t0;
313}
314
315// No new input, previous output was (non-zero) stereo.
316static inline std::tuple<float, float> filterStereoNull(
317 float tl0, float tr0, float* __restrict out, int n)
318{
319 assert(n > 0);
320 int i = 0;
321 do {
322 float tl1 = R * tl0;
323 float tr1 = R * tr0;
324 out[2 * i + 0] = tl1 - tl0;
325 out[2 * i + 1] = tr1 - tr0;
326 tl0 = tl1;
327 tr0 = tr1;
328 } while (++i < n);
329 return {tl0, tr0};
330}
331
332// New input is mono, previous output was also mono.
333static inline float filterMonoMono(float t0, const float* __restrict in,
334 float* __restrict out, int n)
335{
336 assert(n > 0);
337 int i = 0;
338 do {
339 auto t1 = R * t0 + in[i];
340 auto s = t1 - t0;
341 out[2 * i + 0] = s;
342 out[2 * i + 1] = s;
343 t0 = t1;
344 } while (++i < n);
345 return t0;
346}
347
348// New input is mono, previous output was stereo
349static inline std::tuple<float, float>
350filterStereoMono(float tl0, float tr0, const float* __restrict in,
351 float* __restrict out, int n)
352{
353 assert(n > 0);
354 int i = 0;
355 do {
356 auto x = in[i];
357 auto tl1 = R * tl0 + x;
358 auto tr1 = R * tr0 + x;
359 out[2 * i + 0] = tl1 - tl0;
360 out[2 * i + 1] = tr1 - tr0;
361 tl0 = tl1;
362 tr0 = tr1;
363 } while (++i < n);
364 return {tl0, tr0};
365}
366
367// New input is stereo, (previous output either mono/stereo)
368static inline std::tuple<float, float>
369filterStereoStereo(float tl0, float tr0, const float* __restrict in,
370 float* __restrict out, int n)
371{
372 assert(n > 0);
373 int i = 0;
374 do {
375 auto tl1 = R * tl0 + in[2 * i + 0];
376 auto tr1 = R * tr0 + in[2 * i + 1];
377 out[2 * i + 0] = tl1 - tl0;
378 out[2 * i + 1] = tr1 - tr0;
379 tl0 = tl1;
380 tr0 = tr1;
381 } while (++i < n);
382 return {tl0, tr0};
383}
384
385// We have both mono and stereo input (and produce stereo output)
386static inline std::tuple<float, float>
387filterBothStereo(float tl0, float tr0, const float* __restrict inM,
388 const float* __restrict inS, float* __restrict out, int n)
389{
390 assert(n > 0);
391 int i = 0;
392 do {
393 auto m = inM[i];
394 auto tl1 = R * tl0 + inS[2 * i + 0] + m;
395 auto tr1 = R * tr0 + inS[2 * i + 1] + m;
396 out[2 * i + 0] = tl1 - tl0;
397 out[2 * i + 1] = tr1 - tr0;
398 tl0 = tl1;
399 tr0 = tr1;
400 } while (++i < n);
401 return {tl0, tr0};
402}
403
404static bool approxEqual(float x, float y)
405{
406 constexpr float threshold = 1.0f / 32768;
407 return std::abs(x - y) < threshold;
408}
409
410void MSXMixer::generate(float* output, EmuTime::param time, unsigned samples)
411{
412 // The code below is specialized for a lot of cases (before this
413 // routine was _much_ shorter). This is done because this routine
414 // ends up relatively high (top 5) in a profile run.
415 // After these specialization this routine runs about two times
416 // faster for the common cases (mono output or no sound at all).
417 // In total emulation time this gave a speedup of about 2%.
418
419 // When samples==0, call updateBuffer() but skip all further processing
420 // (handling this as a special case allows to simplify the code below).
421 if (samples == 0) {
422 ALIGNAS_SSE float dummyBuf[4];
423 for (auto& info : infos) {
424 bool ignore = info.device->updateBuffer(0, dummyBuf, time);
425 (void)ignore;
426 }
427 return;
428 }
429
430 // +3 to allow processing samples in groups of 4 (and upto 3 samples
431 // more than requested).
432 VLA_SSE_ALIGNED(float, monoBuf, samples + 3);
433 VLA_SSE_ALIGNED(float, stereoBuf, 2 * samples + 3);
434 VLA_SSE_ALIGNED(float, tmpBuf, 2 * samples + 3);
435
436 constexpr unsigned HAS_MONO_FLAG = 1;
437 constexpr unsigned HAS_STEREO_FLAG = 2;
438 unsigned usedBuffers = 0;
439
440 // FIXME: The Infos should be ordered such that all the mono
441 // devices are handled first
442 for (auto& info : infos) {
443 SoundDevice& device = *info.device;
444 auto l1 = info.left1;
445 auto r1 = info.right1;
446 if (!device.isStereo()) {
447 if (l1 == r1) {
448 if (!(usedBuffers & HAS_MONO_FLAG)) {
449 if (device.updateBuffer(samples, monoBuf, time)) {
450 usedBuffers |= HAS_MONO_FLAG;
451 mul(monoBuf, samples, l1);
452 }
453 } else {
454 if (device.updateBuffer(samples, tmpBuf, time)) {
455 mulAcc(monoBuf, tmpBuf, samples, l1);
456 }
457 }
458 } else {
459 if (!(usedBuffers & HAS_STEREO_FLAG)) {
460 if (device.updateBuffer(samples, stereoBuf, time)) {
461 usedBuffers |= HAS_STEREO_FLAG;
462 mulExpand(stereoBuf, samples, l1, r1);
463 }
464 } else {
465 if (device.updateBuffer(samples, tmpBuf, time)) {
466 mulExpandAcc(stereoBuf, tmpBuf, samples, l1, r1);
467 }
468 }
469 }
470 } else {
471 auto l2 = info.left2;
472 auto r2 = info.right2;
473 if (l1 == r2) {
474 assert(l2 == 0.0f);
475 assert(r1 == 0.0f);
476 if (!(usedBuffers & HAS_STEREO_FLAG)) {
477 if (device.updateBuffer(samples, stereoBuf, time)) {
478 usedBuffers |= HAS_STEREO_FLAG;
479 mul(stereoBuf, 2 * samples, l1);
480 }
481 } else {
482 if (device.updateBuffer(samples, tmpBuf, time)) {
483 mulAcc(stereoBuf, tmpBuf, 2 * samples, l1);
484 }
485 }
486 } else {
487 if (!(usedBuffers & HAS_STEREO_FLAG)) {
488 if (device.updateBuffer(samples, stereoBuf, time)) {
489 usedBuffers |= HAS_STEREO_FLAG;
490 mulMix2(stereoBuf, samples, l1, l2, r1, r2);
491 }
492 } else {
493 if (device.updateBuffer(samples, tmpBuf, time)) {
494 mulMix2Acc(stereoBuf, tmpBuf, samples, l1, l2, r1, r2);
495 }
496 }
497 }
498 }
499 }
500
501 // DC removal filter
502 switch (usedBuffers) {
503 case 0: // no new input
504 if (approxEqual(tl0, tr0)) {
505 if (approxEqual(tl0, 0.0f)) {
506 // Output was zero, new input is zero,
507 // after DC-filter output will still be zero.
508 memset(output, 0, 2 * samples * sizeof(float));
509 tl0 = tr0 = 0.0f;
510 } else {
511 // Output was not zero, but it was the same left and right.
512 tl0 = filterMonoNull(tl0, output, samples);
513 tr0 = tl0;
514 }
515 } else {
516 std::tie(tl0, tr0) = filterStereoNull(tl0, tr0, output, samples);
517 }
518 break;
519
520 case HAS_MONO_FLAG: // only mono
521 if (approxEqual(tl0, tr0)) {
522 // previous output was also mono
523 tl0 = filterMonoMono(tl0, monoBuf, output, samples);
524 tr0 = tl0;
525 } else {
526 // previous output was stereo, rarely triggers but needed for correctness
527 std::tie(tl0, tr0) = filterStereoMono(tl0, tr0, monoBuf, output, samples);
528 }
529 break;
530
531 case HAS_STEREO_FLAG: // only stereo
532 std::tie(tl0, tr0) = filterStereoStereo(tl0, tr0, stereoBuf, output, samples);
533 break;
534
535 default: // mono + stereo
536 std::tie(tl0, tr0) = filterBothStereo(tl0, tr0, monoBuf, stereoBuf, output, samples);
537 }
538}
539
541{
542 return ranges::any_of(infos, [](auto& info) {
543 return info.device->isStereo() ||
544 info.balanceSetting->getInt() != 0;
545 });
546}
547
549{
550 if (muteCount == 0) {
551 mixer.unregisterMixer(*this);
552 }
553 ++muteCount;
554}
555
557{
558 --muteCount;
559 if (muteCount == 0) {
560 tl0 = tr0 = 0.0f;
561 mixer.registerMixer(*this);
562 }
563}
564
566{
567 prevTime.reset(getCurrentTime());
568 prevTime.setFreq(hostSampleRate / getEffectiveSpeed());
569 reschedule();
570}
571void MSXMixer::reschedule()
572{
574 reschedule2();
575}
576void MSXMixer::reschedule2()
577{
578 unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512;
579 setSyncPoint(prevTime.getFastAdd(size));
580}
581
582void MSXMixer::setMixerParams(unsigned newFragmentSize, unsigned newSampleRate)
583{
584 // TODO old code checked that values did actually change,
585 // investigate if this optimization is worth it
586 hostSampleRate = newSampleRate;
587 fragmentSize = newFragmentSize;
588
589 reInit(); // must come before call to setOutputRate()
590
591 for (auto& info : infos) {
592 info.device->setOutputRate(newSampleRate, speedManager.getSpeed());
593 }
594}
595
597{
598 if ((recorder != nullptr) != (newRecorder != nullptr)) {
599 setSynchronousMode(newRecorder != nullptr);
600 }
601 recorder = newRecorder;
602}
603
604void MSXMixer::update(const Setting& setting) noexcept
605{
606 if (&setting == &masterVolume) {
607 updateMasterVolume();
608 } else if (dynamic_cast<const IntegerSetting*>(&setting)) {
609 auto it = find_if_unguarded(infos,
610 [&](const SoundDeviceInfo& i) {
611 return &setting == one_of(i.volumeSetting .get(),
612 i.balanceSetting.get()); });
613 updateVolumeParams(*it);
614 } else if (dynamic_cast<const StringSetting*>(&setting)) {
615 changeRecordSetting(setting);
616 } else if (dynamic_cast<const BooleanSetting*>(&setting)) {
617 changeMuteSetting(setting);
618 } else {
620 }
621}
622
623void MSXMixer::changeRecordSetting(const Setting& setting)
624{
625 for (auto& info : infos) {
626 for (auto&& [channel, settings] : enumerate(info.channelSettings)) {
627 if (settings.record.get() == &setting) {
628 info.device->recordChannel(
629 unsigned(channel),
631 settings.record->getString()))));
632 return;
633 }
634 }
635 }
637}
638
639void MSXMixer::changeMuteSetting(const Setting& setting)
640{
641 for (auto& info : infos) {
642 for (auto&& [channel, settings] : enumerate(info.channelSettings)) {
643 if (settings.mute.get() == &setting) {
644 info.device->muteChannel(
645 unsigned(channel), settings.mute->getBoolean());
646 return;
647 }
648 }
649 }
651}
652
653void MSXMixer::update(const SpeedManager& /*speedManager*/) noexcept
654{
655 if (synchronousCounter == 0) {
656 setMixerParams(fragmentSize, hostSampleRate);
657 } else {
658 // Avoid calling reInit() while recording because
659 // each call causes a small hiccup in the sound (and
660 // while recording this call anyway has no effect).
661 // This was noticeable while sliding the speed slider
662 // in catapult (because this causes many changes in
663 // the speed setting).
664 }
665}
666
667void MSXMixer::update(const ThrottleManager& /*throttleManager*/) noexcept
668{
669 //reInit();
670 // TODO Should this be removed?
671}
672
673void MSXMixer::updateVolumeParams(SoundDeviceInfo& info)
674{
675 int mVolume = masterVolume.getInt();
676 int dVolume = info.volumeSetting->getInt();
677 float volume = info.defaultVolume * mVolume * dVolume / (100.0f * 100.0f);
678 int balance = info.balanceSetting->getInt();
679 auto [l1, r1, l2, r2] = [&] {
680 if (info.device->isStereo()) {
681 if (balance < 0) {
682 float b = (balance + 100.0f) / 100.0f;
683 return std::tuple{
684 /*l1 =*/ volume,
685 /*r1 =*/ 0.0f,
686 /*l2 =*/ volume * sqrtf(std::max(0.0f, 1.0f - b)),
687 /*r2 =*/ volume * sqrtf(std::max(0.0f, b))
688 };
689 } else {
690 float b = balance / 100.0f;
691 return std::tuple{
692 /*l1 =*/ volume * sqrtf(std::max(0.0f, 1.0f - b)),
693 /*r1 =*/ volume * sqrtf(std::max(0.0f, b)),
694 /*l2 =*/ 0.0f,
695 /*r2 =*/ volume
696 };
697 }
698 } else {
699 // make sure that in case of rounding errors
700 // we don't take sqrt() of negative numbers
701 float b = (balance + 100.0f) / 200.0f;
702 return std::tuple{
703 /*l1 =*/ volume * sqrtf(std::max(0.0f, 1.0f - b)),
704 /*r1 =*/ volume * sqrtf(std::max(0.0f, b)),
705 /*l2 =*/ 0.0f, // dummy
706 /*r2 =*/ 0.0f // dummy
707 };
708 }
709 }();
710 auto [ampL, ampR] = info.device->getAmplificationFactor();
711 info.left1 = l1 * ampL;
712 info.right1 = r1 * ampR;
713 info.left2 = l2 * ampL;
714 info.right2 = r2 * ampR;
715}
716
717void MSXMixer::updateMasterVolume()
718{
719 for (auto& p : infos) {
720 updateVolumeParams(p);
721 }
722}
723
725{
726 auto it = find_unguarded(infos, &device, &SoundDeviceInfo::device);
727 updateVolumeParams(*it);
728}
729
730void MSXMixer::executeUntil(EmuTime::param time)
731{
732 updateStream(time);
733 reschedule2();
734
735 // This method gets called very regularly, typically 44100/512 = 86x
736 // per second (even if sound is muted and even with sound_driver=null).
737 // This rate is constant in real-time (compared to e.g. the VDP sync
738 // points that are constant in emutime). So we can use this to
739 // regularly exit from the main CPU emulation loop. Without this there
740 // were problems like described in 'bug#563 Console very slow when
741 // setting speed to low values like 1'.
742 motherBoard.exitCPULoopSync();
743}
744
745
746// Sound device info
747
748SoundDevice* MSXMixer::findDevice(std::string_view name) const
749{
750 auto it = ranges::find(infos, name,
751 [](auto& i) { return i.device->getName(); });
752 return (it != end(infos)) ? it->device : nullptr;
753}
754
755MSXMixer::SoundDeviceInfoTopic::SoundDeviceInfoTopic(
756 InfoCommand& machineInfoCommand)
757 : InfoTopic(machineInfoCommand, "sounddevice")
758{
759}
760
761void MSXMixer::SoundDeviceInfoTopic::execute(
762 std::span<const TclObject> tokens, TclObject& result) const
763{
764 auto& msxMixer = OUTER(MSXMixer, soundDeviceInfo);
765 switch (tokens.size()) {
766 case 2:
767 result.addListElements(view::transform(
768 msxMixer.infos,
769 [](auto& info) { return info.device->getName(); }));
770 break;
771 case 3: {
772 SoundDevice* device = msxMixer.findDevice(tokens[2].getString());
773 if (!device) {
774 throw CommandException("Unknown sound device");
775 }
776 result = device->getDescription();
777 break;
778 }
779 default:
780 throw CommandException("Too many parameters");
781 }
782}
783
784std::string MSXMixer::SoundDeviceInfoTopic::help(std::span<const TclObject> /*tokens*/) const
785{
786 return "Shows a list of available sound devices.\n";
787}
788
789void MSXMixer::SoundDeviceInfoTopic::tabCompletion(std::vector<std::string>& tokens) const
790{
791 if (tokens.size() == 3) {
792 completeString(tokens, view::transform(
793 OUTER(MSXMixer, soundDeviceInfo).infos,
794 [](auto& info) -> std::string_view { return info.device->getName(); }));
795 }
796}
797
798} // namespace openmsx
BaseSetting * setting
Definition: Interpreter.cc:27
TclObject t
#define ALIGNAS_SSE
Definition: aligned.hh:24
Definition: one_of.hh:7
void addWave(unsigned num, float *data)
Definition: AviRecorder.cc:124
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:548
void setRecorder(AviRecorder *recorder)
Definition: MSXMixer.cc:596
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:724
unsigned getSampleRate() const
Definition: MSXMixer.hh:117
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:748
bool needStereoRecording() const
Definition: MSXMixer.cc:540
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:582
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:103
void uploadBuffer(MSXMixer &msxMixer, float *buffer, unsigned len)
Upload new sample data.
Definition: Mixer.cc:148
void unregisterMixer(MSXMixer &mixer)
Unregister per-machine mixer.
Definition: Mixer.cc:110
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:29
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:146
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:283
string expandTilde(string path)
Expand the '~' character to the users home directory.
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr auto R
Definition: MSXMixer.cc:298
constexpr KeyMatrixPosition x
Keyboard bindings.
Definition: Keyboard.cc:127
bool any_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:169
auto find(InputRange &&range, const T &value)
Definition: ranges.hh:144
size_t size(std::string_view utf8)
constexpr auto transform(Range &&range, UnaryOp op)
Definition: view.hh:378
#define OUTER(type, member)
Definition: outer.hh:41
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:617
#define UNREACHABLE
Definition: unreachable.hh:38
#define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH)
Definition: vla.hh:44
constexpr auto end(const zstring_view &x)