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