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