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