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