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 "outer.hh"
19 #include "ranges.hh"
20 #include "unreachable.hh"
21 #include "view.hh"
22 #include "vla.hh"
23 #include <cassert>
24 #include <cmath>
25 #include <cstring>
26 #include <memory>
27 #include <tuple>
28 
29 #ifdef __SSE2__
30 #include "emmintrin.h"
31 #endif
32 
33 using std::string;
34 using std::vector;
35 
36 namespace openmsx {
37 
38 MSXMixer::MSXMixer(Mixer& mixer_, MSXMotherBoard& motherBoard_,
39  GlobalSettings& globalSettings)
40  : Schedulable(motherBoard_.getScheduler())
41  , mixer(mixer_)
42  , motherBoard(motherBoard_)
43  , commandController(motherBoard.getMSXCommandController())
44  , masterVolume(mixer.getMasterVolume())
45  , speedSetting(globalSettings.getSpeedSetting())
46  , throttleManager(globalSettings.getThrottleManager())
47  , prevTime(getCurrentTime(), 44100)
48  , soundDeviceInfo(commandController.getMachineInfoCommand())
49  , recorder(nullptr)
50  , synchronousCounter(0)
51 {
52  hostSampleRate = 44100;
53  fragmentSize = 0;
54 
55  muteCount = 1;
56  unmute(); // calls Mixer::registerMixer()
57 
58  reschedule2();
59 
60  masterVolume.attach(*this);
61  speedSetting.attach(*this);
62  throttleManager.attach(*this);
63 }
64 
66 {
67  if (recorder) {
68  recorder->stop();
69  }
70  assert(infos.empty());
71 
72  throttleManager.detach(*this);
73  speedSetting.detach(*this);
74  masterVolume.detach(*this);
75 
76  mute(); // calls Mixer::unregisterMixer()
77 }
78 
79 void MSXMixer::registerSound(SoundDevice& device, float volume,
80  int balance, unsigned numChannels)
81 {
82  // TODO read volume/balance(mode) from config file
83  const string& name = device.getName();
84  SoundDeviceInfo info;
85  info.device = &device;
86  info.defaultVolume = volume;
87  info.volumeSetting = std::make_unique<IntegerSetting>(
88  commandController, name + "_volume",
89  "the volume of this sound chip", 75, 0, 100);
90  info.balanceSetting = std::make_unique<IntegerSetting>(
91  commandController, name + "_balance",
92  "the balance of this sound chip", balance, -100, 100);
93 
94  info.volumeSetting->attach(*this);
95  info.balanceSetting->attach(*this);
96 
97  for (unsigned i = 0; i < numChannels; ++i) {
98  SoundDeviceInfo::ChannelSettings channelSettings;
99  string ch_name = strCat(name, "_ch", i + 1);
100 
101  channelSettings.recordSetting = std::make_unique<StringSetting>(
102  commandController, ch_name + "_record",
103  "filename to record this channel to",
105  channelSettings.recordSetting->attach(*this);
106 
107  channelSettings.muteSetting = std::make_unique<BooleanSetting>(
108  commandController, ch_name + "_mute",
109  "sets mute-status of individual sound channels",
110  false, Setting::DONT_SAVE);
111  channelSettings.muteSetting->attach(*this);
112 
113  info.channelSettings.push_back(std::move(channelSettings));
114  }
115 
116  device.setOutputRate(getSampleRate());
117  infos.push_back(std::move(info));
118  updateVolumeParams(infos.back());
119 
120  commandController.getCliComm().update(CliComm::SOUNDDEVICE, device.getName(), "add");
121 }
122 
124 {
125  auto it = rfind_if_unguarded(infos,
126  [&](const SoundDeviceInfo& i) { return i.device == &device; });
127  it->volumeSetting->detach(*this);
128  it->balanceSetting->detach(*this);
129  for (auto& s : it->channelSettings) {
130  s.recordSetting->detach(*this);
131  s.muteSetting->detach(*this);
132  }
133  move_pop_back(infos, it);
134  commandController.getCliComm().update(CliComm::SOUNDDEVICE, device.getName(), "remove");
135 }
136 
137 void MSXMixer::setSynchronousMode(bool synchronous)
138 {
139  // TODO ATM synchronous is not used anymore
140  if (synchronous) {
141  ++synchronousCounter;
142  if (synchronousCounter == 1) {
143  setMixerParams(fragmentSize, hostSampleRate);
144  }
145  } else {
146  assert(synchronousCounter > 0);
147  --synchronousCounter;
148  if (synchronousCounter == 0) {
149  setMixerParams(fragmentSize, hostSampleRate);
150  }
151  }
152 }
153 
155 {
156  return synchronousCounter
157  ? 1.0
158  : speedSetting.getInt() / 100.0;
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 --> cutt-off freq = 14Hz
297 // 22050Hz 7Hz
298 static 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::make_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::make_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::make_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::make_tuple(tl0, tr0);
402 }
403 
404 static bool approxEqual(float x, float y)
405 {
406  static const 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  SSE_ALIGNED(float dummyBuf[4]);
423  for (auto& info : infos) {
424  info.device->updateBuffer(0, dummyBuf, time);
425  }
426  return;
427  }
428 
429  // +3 to allow processing samples in groups of 4 (and upto 3 samples
430  // more than requested).
431  VLA_SSE_ALIGNED(float, monoBuf, samples + 3);
432  VLA_SSE_ALIGNED(float, stereoBuf, 2 * samples + 3);
433  VLA_SSE_ALIGNED(float, tmpBuf, 2 * samples + 3);
434 
435  static const unsigned HAS_MONO_FLAG = 1;
436  static const unsigned HAS_STEREO_FLAG = 2;
437  unsigned usedBuffers = 0;
438 
439  // FIXME: The Infos should be ordered such that all the mono
440  // devices are handled first
441  for (auto& info : infos) {
442  SoundDevice& device = *info.device;
443  auto l1 = info.left1;
444  auto r1 = info.right1;
445  if (!device.isStereo()) {
446  if (l1 == r1) {
447  if (!(usedBuffers & HAS_MONO_FLAG)) {
448  if (device.updateBuffer(samples, monoBuf, time)) {
449  usedBuffers |= HAS_MONO_FLAG;
450  mul(monoBuf, samples, l1);
451  }
452  } else {
453  if (device.updateBuffer(samples, tmpBuf, time)) {
454  mulAcc(monoBuf, tmpBuf, samples, l1);
455  }
456  }
457  } else {
458  if (!(usedBuffers & HAS_STEREO_FLAG)) {
459  if (device.updateBuffer(samples, stereoBuf, time)) {
460  usedBuffers |= HAS_STEREO_FLAG;
461  mulExpand(stereoBuf, samples, l1, r1);
462  }
463  } else {
464  if (device.updateBuffer(samples, tmpBuf, time)) {
465  mulExpandAcc(stereoBuf, tmpBuf, samples, l1, r1);
466  }
467  }
468  }
469  } else {
470  auto l2 = info.left2;
471  auto r2 = info.right2;
472  if (l1 == r2) {
473  assert(l2 == 0.0f);
474  assert(r1 == 0.0f);
475  if (!(usedBuffers & HAS_STEREO_FLAG)) {
476  if (device.updateBuffer(samples, stereoBuf, time)) {
477  usedBuffers |= HAS_STEREO_FLAG;
478  mul(stereoBuf, 2 * samples, l1);
479  }
480  } else {
481  if (device.updateBuffer(samples, tmpBuf, time)) {
482  mulAcc(stereoBuf, tmpBuf, 2 * samples, l1);
483  }
484  }
485  } else {
486  if (!(usedBuffers & HAS_STEREO_FLAG)) {
487  if (device.updateBuffer(samples, stereoBuf, time)) {
488  usedBuffers |= HAS_STEREO_FLAG;
489  mulMix2(stereoBuf, samples, l1, l2, r1, r2);
490  }
491  } else {
492  if (device.updateBuffer(samples, tmpBuf, time)) {
493  mulMix2Acc(stereoBuf, tmpBuf, samples, l1, l2, r1, r2);
494  }
495  }
496  }
497  }
498  }
499 
500  // DC removal filter
501  switch (usedBuffers) {
502  case 0: // no new input
503  if (approxEqual(tl0, tr0)) {
504  if (approxEqual(tl0, 0.0f)) {
505  // Output was zero, new input is zero,
506  // after DC-filter output will still be zero.
507  memset(output, 0, 2 * samples * sizeof(float));
508  tl0 = tr0 = 0.0f;
509  } else {
510  // Output was not zero, but it was the same left and right.
511  tl0 = filterMonoNull(tl0, output, samples);
512  tr0 = tl0;
513  }
514  } else {
515  std::tie(tl0, tr0) = filterStereoNull(tl0, tr0, output, samples);
516  }
517  break;
518 
519  case HAS_MONO_FLAG: // only mono
520  if (approxEqual(tl0, tr0)) {
521  // previous output was also mono
522  tl0 = filterMonoMono(tl0, monoBuf, output, samples);
523  tr0 = tl0;
524  } else {
525  // previous output was stereo, rarely triggers but needed for correctness
526  std::tie(tl0, tr0) = filterStereoMono(tl0, tr0, monoBuf, output, samples);
527  }
528  break;
529 
530  case HAS_STEREO_FLAG: // only stereo
531  std::tie(tl0, tr0) = filterStereoStereo(tl0, tr0, stereoBuf, output, samples);
532  break;
533 
534  default: // mono + stereo
535  std::tie(tl0, tr0) = filterBothStereo(tl0, tr0, monoBuf, stereoBuf, output, samples);
536  }
537 }
538 
540 {
541  return ranges::any_of(infos, [](auto& info) {
542  return info.device->isStereo() ||
543  info.balanceSetting->getInt() != 0;
544  });
545 }
546 
548 {
549  if (muteCount == 0) {
550  mixer.unregisterMixer(*this);
551  }
552  ++muteCount;
553 }
554 
556 {
557  --muteCount;
558  if (muteCount == 0) {
559  tl0 = tr0 = 0.0f;
560  mixer.registerMixer(*this);
561  }
562 }
563 
565 {
566  prevTime.reset(getCurrentTime());
567  prevTime.setFreq(hostSampleRate / getEffectiveSpeed());
568  reschedule();
569 }
570 void MSXMixer::reschedule()
571 {
573  reschedule2();
574 }
575 void MSXMixer::reschedule2()
576 {
577  unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512;
578  setSyncPoint(prevTime.getFastAdd(size));
579 }
580 
581 void MSXMixer::setMixerParams(unsigned newFragmentSize, unsigned newSampleRate)
582 {
583  // TODO old code checked that values did actually change,
584  // investigate if this optimization is worth it
585  hostSampleRate = newSampleRate;
586  fragmentSize = newFragmentSize;
587 
588  reInit(); // must come before call to setOutputRate()
589 
590  for (auto& info : infos) {
591  info.device->setOutputRate(newSampleRate);
592  }
593 }
594 
596 {
597  if ((recorder != nullptr) != (newRecorder != nullptr)) {
598  setSynchronousMode(newRecorder != nullptr);
599  }
600  recorder = newRecorder;
601 }
602 
603 void MSXMixer::update(const Setting& setting)
604 {
605  if (&setting == &masterVolume) {
606  updateMasterVolume();
607  } else if (&setting == &speedSetting) {
608  if (synchronousCounter == 0) {
609  setMixerParams(fragmentSize, hostSampleRate);
610  } else {
611  // Avoid calling reInit() while recording because
612  // each call causes a small hiccup in the sound (and
613  // while recording this call anyway has no effect).
614  // This was noticable while sliding the speed slider
615  // in catapult (becuase this causes many changes in
616  // the speed setting).
617  }
618  } else if (dynamic_cast<const IntegerSetting*>(&setting)) {
619  auto it = find_if_unguarded(infos,
620  [&](const SoundDeviceInfo& i) {
621  return (i.volumeSetting .get() == &setting) ||
622  (i.balanceSetting.get() == &setting); });
623  updateVolumeParams(*it);
624  } else if (dynamic_cast<const StringSetting*>(&setting)) {
625  changeRecordSetting(setting);
626  } else if (dynamic_cast<const BooleanSetting*>(&setting)) {
627  changeMuteSetting(setting);
628  } else {
629  UNREACHABLE;
630  }
631 }
632 
633 void MSXMixer::changeRecordSetting(const Setting& setting)
634 {
635  for (auto& info : infos) {
636  unsigned channel = 0;
637  for (auto& s : info.channelSettings) {
638  if (s.recordSetting.get() == &setting) {
639  info.device->recordChannel(
640  channel,
641  Filename(s.recordSetting->getString().str()));
642  return;
643  }
644  ++channel;
645  }
646  }
647  UNREACHABLE;
648 }
649 
650 void MSXMixer::changeMuteSetting(const Setting& setting)
651 {
652  for (auto& info : infos) {
653  unsigned channel = 0;
654  for (auto& s : info.channelSettings) {
655  if (s.muteSetting.get() == &setting) {
656  info.device->muteChannel(
657  channel, s.muteSetting->getBoolean());
658  return;
659  }
660  ++channel;
661  }
662  }
663  UNREACHABLE;
664 }
665 
666 void MSXMixer::update(const ThrottleManager& /*throttleManager*/)
667 {
668  //reInit();
669  // TODO Should this be removed?
670 }
671 
672 void MSXMixer::updateVolumeParams(SoundDeviceInfo& info)
673 {
674  int mVolume = masterVolume.getInt();
675  int dVolume = info.volumeSetting->getInt();
676  float volume = info.defaultVolume * mVolume * dVolume / (100.0f * 100.0f);
677  int balance = info.balanceSetting->getInt();
678  float l1, r1, l2, r2;
679  if (info.device->isStereo()) {
680  if (balance < 0) {
681  float b = (balance + 100.0f) / 100.0f;
682  l1 = volume;
683  r1 = 0.0f;
684  l2 = volume * sqrtf(std::max(0.0f, 1.0f - b));
685  r2 = volume * sqrtf(std::max(0.0f, b));
686  } else {
687  float b = balance / 100.0f;
688  l1 = volume * sqrtf(std::max(0.0f, 1.0f - b));
689  r1 = volume * sqrtf(std::max(0.0f, b));
690  l2 = 0.0f;
691  r2 = volume;
692  }
693  } else {
694  // make sure that in case of rounding errors
695  // we don't take sqrt() of negative numbers
696  float b = (balance + 100.0f) / 200.0f;
697  l1 = volume * sqrtf(std::max(0.0f, 1.0f - b));
698  r1 = volume * sqrtf(std::max(0.0f, b));
699  l2 = r2 = 0.0f; // dummy
700  }
701  float ampL, ampR;
702  std::tie(ampL, ampR) = info.device->getAmplificationFactor();
703  info.left1 = l1 * ampL;
704  info.right1 = r1 * ampR;
705  info.left2 = l2 * ampL;
706  info.right2 = r2 * ampR;
707 }
708 
709 void MSXMixer::updateMasterVolume()
710 {
711  for (auto& p : infos) {
712  updateVolumeParams(p);
713  }
714 }
715 
717 {
718  auto it = find_if_unguarded(infos,
719  [&](auto& i) { return i.device == &device; });
720  updateVolumeParams(*it);
721 }
722 
723 void MSXMixer::executeUntil(EmuTime::param time)
724 {
725  updateStream(time);
726  reschedule2();
727 
728  // This method gets called very regularly, typically 44100/512 = 86x
729  // per second (even if sound is muted and even with sound_driver=null).
730  // This rate is constant in real-time (compared to e.g. the VDP sync
731  // points that are constant in emutime). So we can use this to
732  // regularly exit from the main CPU emulation loop. Without this there
733  // were problems like described in 'bug#563 Console very slow when
734  // setting speed to low values like 1'.
735  motherBoard.exitCPULoopSync();
736 }
737 
738 
739 // Sound device info
740 
742 {
743  auto it = ranges::find_if(infos, [&](auto& i) {
744  return i.device->getName() == name;
745  });
746  return (it != end(infos)) ? it->device : nullptr;
747 }
748 
749 MSXMixer::SoundDeviceInfoTopic::SoundDeviceInfoTopic(
750  InfoCommand& machineInfoCommand)
751  : InfoTopic(machineInfoCommand, "sounddevice")
752 {
753 }
754 
755 void MSXMixer::SoundDeviceInfoTopic::execute(
756  span<const TclObject> tokens, TclObject& result) const
757 {
758  auto& msxMixer = OUTER(MSXMixer, soundDeviceInfo);
759  switch (tokens.size()) {
760  case 2:
762  msxMixer.infos,
763  [](auto& info) { return info.device->getName(); }));
764  break;
765  case 3: {
766  SoundDevice* device = msxMixer.findDevice(tokens[2].getString());
767  if (!device) {
768  throw CommandException("Unknown sound device");
769  }
770  result = device->getDescription();
771  break;
772  }
773  default:
774  throw CommandException("Too many parameters");
775  }
776 }
777 
778 string MSXMixer::SoundDeviceInfoTopic::help(const vector<string>& /*tokens*/) const
779 {
780  return "Shows a list of available sound devices.\n";
781 }
782 
783 void MSXMixer::SoundDeviceInfoTopic::tabCompletion(vector<string>& tokens) const
784 {
785  if (tokens.size() == 3) {
786  auto devices = to_vector(view::transform(
787  OUTER(MSXMixer, soundDeviceInfo).infos,
788  [](auto& info) { return info.device->getName(); }));
789  completeString(tokens, devices);
790  }
791 }
792 
793 } // namespace openmsx
auto transform(Range &&range, UnaryOp op)
Definition: view.hh:312
void mute()
TODO This methods (un)mute the sound.
Definition: MSXMixer.cc:547
void addWave(unsigned num, float *data)
Definition: AviRecorder.cc:126
const std::string & getName() const
Get the unique name that identifies this sound device.
Definition: SoundDevice.hh:24
void unregisterSound(SoundDevice &device)
Every sounddevice must unregister before it is destructed.
Definition: MSXMixer.cc:123
SoundDevice * findDevice(string_view name) const
Definition: MSXMixer.cc:741
void updateSoftwareVolume(SoundDevice &device)
Used by SoundDevice::setSoftwareVolume()
Definition: MSXMixer.cc:716
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
Definition: Schedulable.cc:49
Definition: span.hh:34
void setSynchronousMode(bool synchronous)
If we&#39;re recording, we want to emulate sound at 100% emutime speed.
Definition: MSXMixer.cc:137
void updateStream(EmuTime::param time)
Use this method to force an &#39;early&#39; call to all updateBuffer() methods.
Definition: MSXMixer.cc:161
void uploadBuffer(MSXMixer &msxMixer, float *buffer, unsigned len)
Upload new sample data.
Definition: Mixer.cc:149
void setMixerParams(unsigned fragmentSize, unsigned sampleRate)
Set new fragment size and sample frequency.
Definition: MSXMixer.cc:581
vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
Definition: gl_vec.hh:287
bool isStereo() const
Is this a stereo device? This is set in the constructor and cannot be changed anymore.
Definition: SoundDevice.cc:79
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
Definition: stl.hh:191
SSE_ALIGNED(static signed char noiseBuf[NOISE_BUF_SIZE])
auto find_if(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:113
std::unique_ptr< StringSetting > recordSetting
Definition: MSXMixer.hh:123
std::unique_ptr< BooleanSetting > muteSetting
Definition: MSXMixer.hh:124
void attach(Observer< T > &observer)
Definition: Subject.hh:45
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:33
void registerSound(SoundDevice &device, float volume, int balance, unsigned numChannels)
Use this method to register a given sounddevice.
Definition: MSXMixer.cc:79
const std::string & getDescription() const
Gets a description of this sound device, to be presented to the user.
Definition: SoundDevice.hh:29
MSXMixer(Mixer &mixer, MSXMotherBoard &motherBoard, GlobalSettings &globalSettings)
Definition: MSXMixer.cc:38
void setFreq(unsigned freq)
Change the frequency at which this clock ticks.
Definition: DynamicClock.hh:86
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
void unregisterMixer(MSXMixer &mixer)
Unregister per-machine mixer.
Definition: Mixer.cc:111
bool any_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:125
void setRecorder(AviRecorder *recorder)
Definition: MSXMixer.cc:595
EmuTime getFastAdd(unsigned n) const
auto count(InputRange &&range, const T &value)
Definition: ranges.hh:197
virtual void setOutputRate(unsigned sampleRate)=0
When a SoundDevice registers itself with the Mixer, the Mixer sets the required sampleRate through th...
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:16
bool needStereoRecording() const
Definition: MSXMixer.cc:539
unsigned getSampleRate() const
Definition: MSXMixer.hh:110
Manages the throttle state of openMSX.
void detach(Observer< T > &observer)
Definition: Subject.hh:51
double getEffectiveSpeed() const
Returns the ratio of emutime-speed per realtime-speed.
Definition: MSXMixer.cc:154
virtual void update(UpdateType type, string_view name, string_view value)=0
void registerMixer(MSXMixer &mixer)
Register per-machine mixer.
Definition: Mixer.cc:104
virtual bool updateBuffer(unsigned length, float *buffer, EmuTime::param time)=0
Generate sample data.
ITER find_if_unguarded(ITER first, ITER last, PRED pred)
Faster alternative to &#39;find_if&#39; when it&#39;s guaranteed that the predicate will be true for at least one...
Definition: stl.hh:145
std::string strCat(Ts &&...ts)
Definition: strCat.hh:577
#define OUTER(type, member)
Definition: outer.hh:38
This class contains settings that are used by several other class (including some singletons)...
void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
void setSyncPoint(EmuTime::param timestamp)
Definition: Schedulable.cc:23
unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
Definition: DynamicClock.hh:51
void addListElements(ITER first, ITER last)
Definition: TclObject.hh:122
constexpr auto size(const C &c) -> decltype(c.size())
Definition: span.hh:62
auto rfind_if_unguarded(RANGE &range, PRED pred)
Definition: stl.hh:174
TclObject t
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))>>
Definition: stl.hh:325
auto end(const string_view &x)
Definition: string_view.hh:152
#define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH)
Definition: vla.hh:44
#define UNREACHABLE
Definition: unreachable.hh:38