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 
37 namespace openmsx {
38 
39 MSXMixer::MSXMixer(Mixer& mixer_, MSXMotherBoard& motherBoard_,
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 
80 MSXMixer::SoundDeviceInfo::SoundDeviceInfo(unsigned numChannels)
81  : channelSettings(numChannels)
82 {
83 }
84 
85 void 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());
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 
139 void 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 
161 void 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
195 static 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
212 static 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
230 static 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
243 static 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
257 static 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
270 static 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
298 constexpr auto R = 511.0f / 512.0f;
299 
300 // No new input, previous output was (non-zero) mono.
301 static 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.
316 static 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 std::tuple(tl0, tr0);
330 }
331 
332 // New input is mono, previous output was also mono.
333 static 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
349 static inline std::tuple<float, float>
350 filterStereoMono(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 std::tuple(tl0, tr0);
365 }
366 
367 // New input is stereo, (previous output either mono/stereo)
368 static inline std::tuple<float, float>
369 filterStereoStereo(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 std::tuple(tl0, tr0);
383 }
384 
385 // We have both mono and stereo input (and produce stereo output)
386 static inline std::tuple<float, float>
387 filterBothStereo(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 std::tuple(tl0, tr0);
402 }
403 
404 static bool approxEqual(float x, float y)
405 {
406  constexpr float threshold = 1.0f / 32768;
407  return std::abs(x - y) < threshold;
408 }
409 
410 void 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 }
571 void MSXMixer::reschedule()
572 {
574  reschedule2();
575 }
576 void MSXMixer::reschedule2()
577 {
578  unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512;
579  setSyncPoint(prevTime.getFastAdd(size));
580 }
581 
582 void 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);
593  }
594 }
595 
597 {
598  if ((recorder != nullptr) != (newRecorder != nullptr)) {
599  setSynchronousMode(newRecorder != nullptr);
600  }
601  recorder = newRecorder;
602 }
603 
604 void 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 {
619  UNREACHABLE;
620  }
621 }
622 
623 void 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  }
636  UNREACHABLE;
637 }
638 
639 void 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  }
650  UNREACHABLE;
651 }
652 
653 void 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 
667 void MSXMixer::update(const ThrottleManager& /*throttleManager*/) noexcept
668 {
669  //reInit();
670  // TODO Should this be removed?
671 }
672 
673 void 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 
717 void 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 
730 void 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 
748 SoundDevice* 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 
755 MSXMixer::SoundDeviceInfoTopic::SoundDeviceInfoTopic(
756  InfoCommand& machineInfoCommand)
757  : InfoTopic(machineInfoCommand, "sounddevice")
758 {
759 }
760 
761 void MSXMixer::SoundDeviceInfoTopic::execute(
762  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 
784 std::string MSXMixer::SoundDeviceInfoTopic::help(span<const TclObject> /*tokens*/) const
785 {
786  return "Shows a list of available sound devices.\n";
787 }
788 
789 void 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:123
virtual void update(UpdateType type, std::string_view name, std::string_view value)=0
EmuTime getFastAdd(unsigned n) const
unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
Definition: DynamicClock.hh:51
void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
void setFreq(unsigned freq)
Change the frequency at which this clock ticks.
This class contains settings that are used by several other class (including some singletons).
int getInt() const noexcept
void mute()
TODO This methods (un)mute the sound.
Definition: MSXMixer.cc: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
virtual void setOutputRate(unsigned sampleRate)=0
When a SoundDevice registers itself with the Mixer, the Mixer sets the required sampleRate through th...
const std::string & getName() const
Get the unique name that identifies this sound device.
Definition: SoundDevice.hh:29
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
Definition: span.hh:126
constexpr index_type size() const noexcept
Definition: span.hh:296
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:207
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:287
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:118
bool any_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:155
auto find(InputRange &&range, const T &value)
Definition: ranges.hh:130
size_t size(std::string_view utf8)
constexpr auto transform(Range &&range, UnaryOp op)
Definition: view.hh:392
#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: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:659
#define UNREACHABLE
Definition: unreachable.hh:38
#define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH)
Definition: vla.hh:44
constexpr auto end(const zstring_view &x)
Definition: zstring_view.hh:84