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