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