openMSX
MSXMixer.cc
Go to the documentation of this file.
1 #include "MSXMixer.hh"
2 #include "Mixer.hh"
3 #include "SoundDevice.hh"
5 #include "InfoTopic.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 "unreachable.hh"
21 #include "vla.hh"
22 #include <algorithm>
23 #include <cmath>
24 #include <cstring>
25 #include <cassert>
26 
27 using std::remove;
28 using std::string;
29 using std::vector;
30 
31 namespace openmsx {
32 
34 {
35 public:
36  SoundDeviceInfoTopic(InfoCommand& machineInfoCommand, MSXMixer& mixer);
37  virtual void execute(array_ref<TclObject> tokens,
38  TclObject& result) const;
39  virtual string help(const vector<string>& tokens) const;
40  virtual void tabCompletion(vector<string>& tokens) const;
41 private:
42  MSXMixer& mixer;
43 };
44 
45 
46 MSXMixer::SoundDeviceInfo::SoundDeviceInfo()
47 {
48 }
49 
50 MSXMixer::SoundDeviceInfo::SoundDeviceInfo(SoundDeviceInfo&& rhs)
51  : device (std::move(rhs.device))
52  , defaultVolume (std::move(rhs.defaultVolume))
53  , volumeSetting (std::move(rhs.volumeSetting))
54  , balanceSetting (std::move(rhs.balanceSetting))
55  , channelSettings(std::move(rhs.channelSettings))
56  , left1 (std::move(rhs.left1))
57  , right1 (std::move(rhs.right1))
58  , left2 (std::move(rhs.left2))
59  , right2 (std::move(rhs.right2))
60 {
61 }
62 
63 MSXMixer::SoundDeviceInfo& MSXMixer::SoundDeviceInfo::operator=(SoundDeviceInfo&& rhs)
64 {
65  device = std::move(rhs.device);
66  defaultVolume = std::move(rhs.defaultVolume);
67  volumeSetting = std::move(rhs.volumeSetting);
68  balanceSetting = std::move(rhs.balanceSetting);
69  channelSettings = std::move(rhs.channelSettings);
70  left1 = std::move(rhs.left1);
71  right1 = std::move(rhs.right1);
72  left2 = std::move(rhs.left2);
73  right2 = std::move(rhs.right2);
74  return *this;
75 }
76 
78 {
79 }
80 
82  : recordSetting(std::move(rhs.recordSetting))
83  , muteSetting (std::move(rhs.muteSetting))
84 {
85 }
86 
89 {
90  recordSetting = std::move(rhs.recordSetting);
91  muteSetting = std::move(rhs.muteSetting);
92  return *this;
93 }
94 
95 
96 MSXMixer::MSXMixer(Mixer& mixer_, Scheduler& scheduler,
97  MSXCommandController& msxCommandController_,
98  GlobalSettings& globalSettings)
99  : Schedulable(scheduler)
100  , mixer(mixer_)
101  , commandController(msxCommandController_)
102  , masterVolume(mixer.getMasterVolume())
103  , speedSetting(globalSettings.getSpeedSetting())
104  , throttleManager(globalSettings.getThrottleManager())
105  , prevTime(getCurrentTime(), 44100)
106  , soundDeviceInfo(make_unique<SoundDeviceInfoTopic>(
107  msxCommandController_.getMachineInfoCommand(), *this))
108  , recorder(nullptr)
109  , synchronousCounter(0)
110 {
111  hostSampleRate = 44100;
112  fragmentSize = 0;
113 
114  muteCount = 1;
115  unmute(); // calls Mixer::registerMixer()
116 
117  reschedule2();
118 
119  masterVolume.attach(*this);
120  speedSetting.attach(*this);
121  throttleManager.attach(*this);
122 }
123 
125 {
126  if (recorder) {
127  recorder->stop();
128  }
129  assert(infos.empty());
130 
131  throttleManager.detach(*this);
132  speedSetting.detach(*this);
133  masterVolume.detach(*this);
134 
135  mute(); // calls Mixer::unregisterMixer()
136 }
137 
138 void MSXMixer::registerSound(SoundDevice& device, double volume,
139  int balance, unsigned numChannels)
140 {
141  // TODO read volume/balance(mode) from config file
142  const string& name = device.getName();
143  SoundDeviceInfo info;
144  info.device = &device;
145  info.defaultVolume = volume;
146  info.volumeSetting = make_unique<IntegerSetting>(
147  commandController, name + "_volume",
148  "the volume of this sound chip", 75, 0, 100);
149  info.balanceSetting = make_unique<IntegerSetting>(
150  commandController, name + "_balance",
151  "the balance of this sound chip", balance, -100, 100);
152 
153  info.volumeSetting->attach(*this);
154  info.balanceSetting->attach(*this);
155 
156  for (unsigned i = 0; i < numChannels; ++i) {
157  SoundDeviceInfo::ChannelSettings channelSettings;
158  string ch_name = StringOp::Builder() << name << "_ch" << i + 1;
159 
160  channelSettings.recordSetting = make_unique<StringSetting>(
161  commandController, ch_name + "_record",
162  "filename to record this channel to",
163  "", Setting::DONT_SAVE);
164  channelSettings.recordSetting->attach(*this);
165 
166  channelSettings.muteSetting = make_unique<BooleanSetting>(
167  commandController, ch_name + "_mute",
168  "sets mute-status of individual sound channels",
169  false, Setting::DONT_SAVE);
170  channelSettings.muteSetting->attach(*this);
171 
172  info.channelSettings.push_back(std::move(channelSettings));
173  }
174 
175  device.setOutputRate(getSampleRate());
176  infos.push_back(std::move(info));
177  updateVolumeParams(infos.back());
178 
179  commandController.getCliComm().update(CliComm::SOUNDDEVICE, device.getName(), "add");
180 }
181 
183 {
184  auto it = find_if_unguarded(infos,
185  [&](const SoundDeviceInfo& i) { return i.device == &device; });
186  it->volumeSetting->detach(*this);
187  it->balanceSetting->detach(*this);
188  for (auto& s : it->channelSettings) {
189  s.recordSetting->detach(*this);
190  s.muteSetting->detach(*this);
191  }
192  if (it != (end(infos) - 1)) std::swap(*it, *(end(infos) - 1));
193  infos.pop_back();
194  commandController.getCliComm().update(CliComm::SOUNDDEVICE, device.getName(), "remove");
195 }
196 
197 void MSXMixer::setSynchronousMode(bool synchronous)
198 {
199  // TODO ATM synchronous is not used anymore
200  if (synchronous) {
201  ++synchronousCounter;
202  if (synchronousCounter == 1) {
203  setMixerParams(fragmentSize, hostSampleRate);
204  }
205  } else {
206  assert(synchronousCounter > 0);
207  --synchronousCounter;
208  if (synchronousCounter == 0) {
209  setMixerParams(fragmentSize, hostSampleRate);
210  }
211  }
212 }
213 
215 {
216  return synchronousCounter
217  ? 1.0
218  : speedSetting.getInt() / 100.0;
219 }
220 
222 {
223  unsigned count = prevTime.getTicksTill(time);
224 
225  // call generate() even if count==0 and even if muted
226  short mixBuffer[8192 * 2];
227  assert(count <= 8192);
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 void MSXMixer::generate(short* output, EmuTime::param time, unsigned samples)
242 {
243  // The code below is specialized for a lot of cases (before this
244  // routine was _much_ shorter). This is done because this routine
245  // ends up relatively high (top 5) in a profile run.
246  // After these specialization this routine runs about two times
247  // faster for the common cases (mono output or no sound at all).
248  // In total emulation time this gave a speedup of about 2%.
249 
250  VLA(int, stereoBuf, 2 * samples + 3);
251  VLA(int, monoBuf, samples + 3);
252  VLA_SSE_ALIGNED(int, tmpBuf, 2 * samples + 3);
253 
254  static const unsigned HAS_MONO_FLAG = 1;
255  static const unsigned HAS_STEREO_FLAG = 2;
256  unsigned usedBuffers = 0;
257 
258  // FIXME: The Infos should be ordered such that all the mono
259  // devices are handled first
260  for (auto& info : infos) {
261  // When samples==0, call updateBuffer() but skip mixing
262  SoundDevice& device = *info.device;
263  if (device.updateBuffer(samples, tmpBuf, time) &&
264  (samples > 0)) {
265  if (!device.isStereo()) {
266  int l1 = info.left1;
267  int r1 = info.right1;
268  if (l1 == r1) {
269  if (!(usedBuffers & HAS_MONO_FLAG)) {
270  usedBuffers |= HAS_MONO_FLAG;
271 #ifdef __arm__
272  unsigned dummy1, dummy2, dummy3;
273  asm volatile (
274  "0:\n\t"
275  "ldmia %[in]!,{r3-r6}\n\t"
276  "mul r3,%[f],r3\n\t"
277  "mul r4,%[f],r4\n\t"
278  "mul r5,%[f],r5\n\t"
279  "mul r6,%[f],r6\n\t"
280  "stmia %[out]!,{r3-r6}\n\t"
281  "subs %[n],%[n],#4\n\t"
282  "bgt 0b\n\t"
283  : [in] "=r" (dummy1)
284  , [out] "=r" (dummy2)
285  , [n] "=r" (dummy3)
286  : "[in]" (tmpBuf)
287  , "[out]" (monoBuf)
288  , "[n]" (samples)
289  , [f] "r" (l1)
290  : "memory", "r3","r4","r5","r6"
291  );
292 #else
293  for (unsigned i = 0; i < samples; ++i) {
294  int tmp = l1 * tmpBuf[i];
295  monoBuf[i] = tmp;
296  }
297 #endif
298  } else {
299 #ifdef __arm__
300  unsigned dummy1, dummy2, dummy3;
301  asm volatile (
302  "0:\n\t"
303  "ldmia %[in]!,{r3,r4,r5,r6}\n\t"
304  "ldmia %[out],{r8,r9,r10,r12}\n\t"
305  "mla r3,%[f],r3,r8\n\t"
306  "mla r4,%[f],r4,r9\n\t"
307  "mla r5,%[f],r5,r10\n\t"
308  "mla r6,%[f],r6,r12\n\t"
309  "stmia %[out]!,{r3,r4,r5,r6}\n\t"
310  "subs %[n],%[n],#4\n\t"
311  "bgt 0b\n\t"
312  : [in] "=r" (dummy1)
313  , [out] "=r" (dummy2)
314  , [n] "=r" (dummy3)
315  : "[in]" (tmpBuf)
316  , "[out]" (monoBuf)
317  , "[n]" (samples)
318  , [f] "r" (l1)
319  : "memory"
320  , "r3","r4","r5","r6"
321  , "r8","r9","r10","r12"
322  );
323 #else
324  for (unsigned i = 0; i < samples; ++i) {
325  int tmp = l1 * tmpBuf[i];
326  monoBuf[i] += tmp;
327  }
328 #endif
329  }
330  } else {
331  if (!(usedBuffers & HAS_STEREO_FLAG)) {
332  usedBuffers |= HAS_STEREO_FLAG;
333  for (unsigned i = 0; i < samples; ++i) {
334  int l = l1 * tmpBuf[i];
335  int r = r1 * tmpBuf[i];
336  stereoBuf[2 * i + 0] = l;
337  stereoBuf[2 * i + 1] = r;
338  }
339  } else {
340  for (unsigned i = 0; i < samples; ++i) {
341  int l = l1 * tmpBuf[i];
342  int r = r1 * tmpBuf[i];
343  stereoBuf[2 * i + 0] += l;
344  stereoBuf[2 * i + 1] += r;
345  }
346  }
347  }
348  } else {
349  int l1 = info.left1;
350  int r1 = info.right1;
351  int l2 = info.left2;
352  int r2 = info.right2;
353  if (!(usedBuffers & HAS_STEREO_FLAG)) {
354  usedBuffers |= HAS_STEREO_FLAG;
355  for (unsigned i = 0; i < samples; ++i) {
356  int in1 = tmpBuf[2 * i + 0];
357  int in2 = tmpBuf[2 * i + 1];
358  int l = l1 * in1 + l2 * in2;
359  int r = r1 * in1 + r2 * in2;
360  stereoBuf[2 * i + 0] = l;
361  stereoBuf[2 * i + 1] = r;
362  }
363  } else {
364  for (unsigned i = 0; i < samples; ++i) {
365  int in1 = tmpBuf[2 * i + 0];
366  int in2 = tmpBuf[2 * i + 1];
367  int l = l1 * in1 + l2 * in2;
368  int r = r1 * in1 + r2 * in2;
369  stereoBuf[2 * i + 0] += l;
370  stereoBuf[2 * i + 1] += r;
371  }
372  }
373  }
374  }
375  }
376 
377  // DC removal filter
378  // y(n) = x(n) - x(n-1) + R * y(n-1)
379  // R = 1 - (pi*2 * cut-off-frequency / samplerate)
380  // take R = 511/512
381  // 44100Hz --> cutt-off freq = 14Hz
382  // 22050Hz 7Hz
383  // Note: we divide by 512 iso shift-right by 9 because we want
384  // to round towards zero.
385  switch (usedBuffers) {
386  case 0:
387  // no new input
388  if (samples == 0) break;
389  if ((outLeft == outRight) && (prevLeft == prevRight)) {
390  if ((outLeft == 0) && (prevLeft == 0)) {
391  // output was already zero, after DC-filter
392  // it will still be zero
393  memset(output, 0, 2 * samples * sizeof(short));
394  } else {
395  // output was not zero, but it was the same
396  // left and right
397  assert(samples > 0);
398  outLeft = -prevLeft + ((511 * outLeft) / 512);
399  prevLeft = 0;
400  short out = Math::clipIntToShort(outLeft);
401  output[0] = out;
402  output[1] = out;
403  for (unsigned j = 1; j < samples; ++j) {
404  outLeft = ((511 * outLeft) / 512);
405  out = Math::clipIntToShort(outLeft);
406  output[2 * j + 0] = out;
407  output[2 * j + 1] = out;
408  }
409  }
410  outRight = outLeft;
411  prevRight = prevLeft;
412  } else {
413  assert(samples > 0);
414  outLeft = -prevLeft + ((511 * outLeft ) / 512);
415  outRight = -prevRight + ((511 * outRight) / 512);
416  prevLeft = 0;
417  prevRight = 0;
418  output[0] = Math::clipIntToShort(outLeft);
419  output[1] = Math::clipIntToShort(outRight);
420  for (unsigned j = 1; j < samples; ++j) {
421  outLeft = ((511 * outLeft) / 512);
422  outRight = ((511 * outRight) / 512);
423  output[2 * j + 0] = Math::clipIntToShort(outLeft);
424  output[2 * j + 1] = Math::clipIntToShort(outRight);
425  }
426  }
427  break;
428 
429  case HAS_MONO_FLAG:
430  // only mono
431  if ((outLeft == outRight) && (prevLeft == prevRight)) {
432  // previous output was also mono
433 #ifdef __arm__
434  // Note: there are two functional differences in the
435  // asm and c++ code below:
436  // - divide by 512 is replaced by ASR #9
437  // (different for negative numbers)
438  // - the outLeft variable is set to the clipped value
439  // Though this difference is very small, and we need
440  // the extra speed.
441  unsigned dummy1, dummy2, dummy3, dummy4;
442  asm volatile (
443  "0:\n\t"
444  "rsb %[o],%[o],%[o],LSL #9\n\t"
445  "rsb %[o],%[p],%[o],ASR #9\n\t"
446  "ldr %[p],[%[in]],#4\n\t"
447  "asrs %[p],%[p],#8\n\t"
448  "add %[o],%[o],%[p]\n\t"
449  "lsls %[t],%[o],#16\n\t"
450  "cmp %[o],%[t],ASR #16\n\t"
451  "it ne\n\t"
452  "subne %[o],%[m],%[o],ASR #31\n\t"
453  "strh %[o],[%[out]],#2\n\t"
454  "strh %[o],[%[out]],#2\n\t"
455  "subs %[n],%[n],#1\n\t"
456  "bne 0b\n\t"
457  : [o] "=r" (outLeft)
458  , [p] "=r" (prevLeft)
459  , [in] "=r" (dummy1)
460  , [out] "=r" (dummy2)
461  , [n] "=r" (dummy3)
462  , [t] "=&r" (dummy4)
463  : "[o]" (outLeft)
464  , "[p]" (prevLeft)
465  , "[in]" (monoBuf)
466  , "[out]" (output)
467  , "[n]" (samples)
468  , [m] "r" (0x7FFF)
469  : "memory"
470  );
471 #else
472  for (unsigned j = 0; j < samples; ++j) {
473  int mono = monoBuf[j] >> 8;
474  outLeft = mono - prevLeft + ((511 * outLeft) / 512);
475  prevLeft = mono;
476  short out = Math::clipIntToShort(outLeft);
477  output[2 * j + 0] = out;
478  output[2 * j + 1] = out;
479  }
480 #endif
481  outRight = outLeft;
482  prevRight = prevLeft;
483  } else {
484  for (unsigned j = 0; j < samples; ++j) {
485  int mono = monoBuf[j] >> 8;
486  outLeft = mono - prevLeft + ((511 * outLeft) / 512);
487  prevLeft = mono;
488  outRight = mono - prevRight + ((511 * outRight) / 512);
489  prevRight = mono;
490  output[2 * j + 0] = Math::clipIntToShort(outLeft);
491  output[2 * j + 1] = Math::clipIntToShort(outRight);
492  }
493  }
494  break;
495 
496  case HAS_STEREO_FLAG:
497  // only stereo
498  for (unsigned j = 0; j < samples; ++j) {
499  int left = stereoBuf[2 * j + 0] >> 8;
500  int right = stereoBuf[2 * j + 1] >> 8;
501  outLeft = left - prevLeft + ((511 * outLeft) / 512);
502  prevLeft = left;
503  outRight = right - prevRight + ((511 * outRight) / 512);
504  prevRight = right;
505  output[2 * j + 0] = Math::clipIntToShort(outLeft);
506  output[2 * j + 1] = Math::clipIntToShort(outRight);
507  }
508  break;
509 
510  default:
511  // mono + stereo
512  for (unsigned j = 0; j < samples; ++j) {
513  int mono = monoBuf[j] >> 8;
514  int left = (stereoBuf[2 * j + 0] >> 8) + mono;
515  int right = (stereoBuf[2 * j + 1] >> 8) + mono;
516  outLeft = left - prevLeft + ((511 * outLeft) / 512);
517  prevLeft = left;
518  outRight = right - prevRight + ((511 * outRight) / 512);
519  prevRight = right;
520  output[2 * j + 0] = Math::clipIntToShort(outLeft);
521  output[2 * j + 1] = Math::clipIntToShort(outRight);
522  }
523  }
524 }
525 
527 {
528  return any_of(begin(infos), end(infos),
529  [](const SoundDeviceInfo& info) {
530  return info.device->isStereo() ||
531  info.balanceSetting->getInt() != 0; });
532 }
533 
535 {
536  if (muteCount == 0) {
537  mixer.unregisterMixer(*this);
538  }
539  ++muteCount;
540 }
541 
543 {
544  --muteCount;
545  if (muteCount == 0) {
546  prevLeft = outLeft = 0;
547  prevRight = outRight = 0;
548  mixer.registerMixer(*this);
549  }
550 }
551 
553 {
554  prevTime.reset(getCurrentTime());
555  prevTime.setFreq(hostSampleRate / getEffectiveSpeed());
556  reschedule();
557 }
558 void MSXMixer::reschedule()
559 {
561  reschedule2();
562 }
563 void MSXMixer::reschedule2()
564 {
565  unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512;
566  setSyncPoint(prevTime.getFastAdd(size));
567 }
568 
569 void MSXMixer::setMixerParams(unsigned newFragmentSize, unsigned newSampleRate)
570 {
571  // TODO old code checked that values did actually change,
572  // investigate if this optimization is worth it
573  hostSampleRate = newSampleRate;
574  fragmentSize = newFragmentSize;
575 
576  reInit(); // must come before call to setOutputRate()
577 
578  for (auto& info : infos) {
579  info.device->setOutputRate(newSampleRate);
580  }
581 }
582 
584 {
585  return prevTime;
586 }
587 
589 {
590  if ((recorder != nullptr) != (newRecorder != nullptr)) {
591  setSynchronousMode(newRecorder != nullptr);
592  }
593  recorder = newRecorder;
594 }
595 
596 unsigned MSXMixer::getSampleRate() const
597 {
598  return hostSampleRate;
599 }
600 
601 void MSXMixer::update(const Setting& setting)
602 {
603  if (&setting == &masterVolume) {
604  updateMasterVolume();
605  } else if (&setting == &speedSetting) {
606  if (synchronousCounter == 0) {
607  setMixerParams(fragmentSize, hostSampleRate);
608  } else {
609  // Avoid calling reInit() while recording because
610  // each call causes a small hiccup in the sound (and
611  // while recording this call anyway has no effect).
612  // This was noticable while sliding the speed slider
613  // in catapult (becuase this causes many changes in
614  // the speed setting).
615  }
616  } else if (dynamic_cast<const IntegerSetting*>(&setting)) {
617  auto it = find_if_unguarded(infos,
618  [&](const SoundDeviceInfo& i) {
619  return (i.volumeSetting .get() == &setting) ||
620  (i.balanceSetting.get() == &setting); });
621  updateVolumeParams(*it);
622  } else if (dynamic_cast<const StringSetting*>(&setting)) {
623  changeRecordSetting(setting);
624  } else if (dynamic_cast<const BooleanSetting*>(&setting)) {
625  changeMuteSetting(setting);
626  } else {
627  UNREACHABLE;
628  }
629 }
630 
631 void MSXMixer::changeRecordSetting(const Setting& setting)
632 {
633  for (auto& info : infos) {
634  unsigned channel = 0;
635  for (auto& s : info.channelSettings) {
636  if (s.recordSetting.get() == &setting) {
637  info.device->recordChannel(
638  channel,
639  Filename(s.recordSetting->getString()));
640  return;
641  }
642  ++channel;
643  }
644  }
645  UNREACHABLE;
646 }
647 
648 void MSXMixer::changeMuteSetting(const Setting& setting)
649 {
650  for (auto& info : infos) {
651  unsigned channel = 0;
652  for (auto& s : info.channelSettings) {
653  if (s.muteSetting.get() == &setting) {
654  info.device->muteChannel(
655  channel, s.muteSetting->getBoolean());
656  return;
657  }
658  ++channel;
659  }
660  }
661  UNREACHABLE;
662 }
663 
664 void MSXMixer::update(const ThrottleManager& /*throttleManager*/)
665 {
666  //reInit();
667  // TODO Should this be removed?
668 }
669 
670 void MSXMixer::updateVolumeParams(SoundDeviceInfo& info)
671 {
672  int mVolume = masterVolume.getInt();
673  int dVolume = info.volumeSetting->getInt();
674  double volume = info.defaultVolume * mVolume * dVolume / (100.0 * 100.0);
675  int balance = info.balanceSetting->getInt();
676  double l1, r1, l2, r2;
677  if (info.device->isStereo()) {
678  if (balance < 0) {
679  double b = (balance + 100.0) / 100.0;
680  l1 = volume;
681  r1 = 0.0;
682  l2 = volume * sqrt(std::max(0.0, 1.0 - b));
683  r2 = volume * sqrt(std::max(0.0, b));
684  } else {
685  double b = balance / 100.0;
686  l1 = volume * sqrt(std::max(0.0, 1.0 - b));
687  r1 = volume * sqrt(std::max(0.0, b));
688  l2 = 0.0;
689  r2 = volume;
690  }
691  } else {
692  // make sure that in case of rounding errors
693  // we don't take sqrt() of negative numbers
694  double b = (balance + 100.0) / 200.0;
695  l1 = volume * sqrt(std::max(0.0, 1.0 - b));
696  r1 = volume * sqrt(std::max(0.0, b));
697  l2 = r2 = 0.0; // dummy
698  }
699  int amp = 256 * info.device->getAmplificationFactor();
700  info.left1 = int(l1 * amp);
701  info.right1 = int(r1 * amp);
702  info.left2 = int(l2 * amp);
703  info.right2 = int(r2 * amp);
704 }
705 
706 void MSXMixer::updateMasterVolume()
707 {
708  for (auto& p : infos) {
709  updateVolumeParams(p);
710  }
711 }
712 
713 void MSXMixer::executeUntil(EmuTime::param time, int /*userData*/)
714 {
715  updateStream(time);
716  reschedule2();
717 }
718 
719 
720 // Sound device info
721 
723 {
724  auto it = find_if(begin(infos), end(infos),
725  [&](const SoundDeviceInfo& i) {
726  return i.device->getName() == name; });
727  return (it != end(infos)) ? it->device : nullptr;
728 }
729 
731  InfoCommand& machineInfoCommand, MSXMixer& mixer_)
732  : InfoTopic(machineInfoCommand, "sounddevice")
733  , mixer(mixer_)
734 {
735 }
736 
738  TclObject& result) const
739 {
740  switch (tokens.size()) {
741  case 2:
742  for (auto& info : mixer.infos) {
743  result.addListElement(info.device->getName());
744  }
745  break;
746  case 3: {
747  SoundDevice* device = mixer.findDevice(tokens[2].getString());
748  if (!device) {
749  throw CommandException("Unknown sound device");
750  }
751  result.setString(device->getDescription());
752  break;
753  }
754  default:
755  throw CommandException("Too many parameters");
756  }
757 }
758 
759 string SoundDeviceInfoTopic::help(const vector<string>& /*tokens*/) const
760 {
761  return "Shows a list of available sound devices.\n";
762 }
763 
764 void SoundDeviceInfoTopic::tabCompletion(vector<string>& tokens) const
765 {
766  if (tokens.size() == 3) {
767  vector<string_ref> devices;
768  for (auto& info : mixer.infos) {
769  devices.push_back(info.device->getName());
770  }
771  completeString(tokens, devices);
772  }
773 }
774 
775 } // namespace openmsx
virtual ~MSXMixer()
Definition: MSXMixer.cc:124
void addWave(unsigned num, short *data)
Definition: AviRecorder.cc:131
void mute()
TODO This methods (un)mute the sound.
Definition: MSXMixer.cc:534
string_ref::const_iterator end(const string_ref &x)
Definition: string_ref.hh:135
size_type size() const
Definition: array_ref.hh:61
void unregisterSound(SoundDevice &device)
Every sounddevice must unregister before it is destructed.
Definition: MSXMixer.cc:182
virtual bool updateBuffer(unsigned length, int *buffer, EmuTime::param time)=0
Generate sample data.
unsigned getSampleRate() const
Definition: MSXMixer.cc:596
void uploadBuffer(MSXMixer &msxMixer, short *buffer, unsigned len)
Upload new sample data.
Definition: Mixer.cc:163
bool needStereoRecording() const
Definition: MSXMixer.cc:526
void setSynchronousMode(bool synchronous)
If we're recording, we want to emulate sound at 100% emutime speed.
Definition: MSXMixer.cc:197
const std::string & getDescription() const
Gets a description of this sound device, to be presented to the user.
Definition: SoundDevice.cc:76
void setSyncPoint(EmuTime::param timestamp, int userData=0)
Definition: Schedulable.cc:25
void updateStream(EmuTime::param time)
Use this method to force an 'early' call to all updateBuffer() methods.
Definition: MSXMixer.cc:221
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:569
virtual void execute(array_ref< TclObject > tokens, TclObject &result) const
Show info on this topic.
Definition: MSXMixer.cc:737
double getEffectiveSpeed() const
Returns the ratio of emutime-speed per realtime-speed.
Definition: MSXMixer.cc:214
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:124
int16_t clipIntToShort(int x)
Clip x to range [-32768,32767].
Definition: Math.hh:37
std::unique_ptr< BooleanSetting > muteSetting
Definition: MSXMixer.hh:125
void attach(Observer< T > &observer)
Definition: Subject.hh:52
const DynamicClock & getHostSampleClock() const
Clock that ticks at the exact moment(s) in time that a host sample should be generated.
Definition: MSXMixer.cc:583
Every class that wants to get scheduled at some point must inherit from this class.
Definition: Schedulable.hh:16
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
Represents a clock with a variable frequency.
Definition: DynamicClock.hh:15
virtual string help(const vector< string > &tokens) const
Print help for this topic.
Definition: MSXMixer.cc:759
SoundDeviceInfoTopic(InfoCommand &machineInfoCommand, MSXMixer &mixer)
Definition: MSXMixer.cc:730
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:50
const EmuTime & param
Definition: EmuTime.hh:20
void unregisterMixer(MSXMixer &mixer)
Unregister per-machine mixer.
Definition: Mixer.cc:120
MSXMixer(Mixer &mixer, Scheduler &scheduler, MSXCommandController &msxCommandController, GlobalSettings &globalSettings)
Definition: MSXMixer.cc:96
void setRecorder(AviRecorder *recorder)
Definition: MSXMixer.cc:588
SoundDevice * findDevice(string_ref name) const
Definition: MSXMixer.cc:722
virtual void setOutputRate(unsigned sampleRate)=0
When a SoundDevice registers itself with the Mixer, the Mixer sets the required sampleRate through th...
void addListElement(string_ref element)
Definition: TclObject.cc:110
static void completeString(std::vector< std::string > &tokens, const RANGE &possibleValues, bool caseSensitive=true)
Definition: Completer.hh:88
void setString(string_ref value)
Definition: TclObject.cc:55
virtual CliComm & getCliComm()=0
bool isStereo() const
Is this a stereo device? This is set in the constructor and cannot be changed anymore.
Definition: SoundDevice.cc:81
size_t size() const
void detach(Observer< T > &observer)
Definition: Subject.hh:58
void registerMixer(MSXMixer &mixer)
Register per-machine mixer.
Definition: Mixer.cc:113
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
const std::string & getName() const
Get the unique name that identifies this sound device.
Definition: SoundDevice.cc:71
ChannelSettings & operator=(ChannelSettings &&rhs)
Definition: MSXMixer.cc:88
virtual void tabCompletion(vector< string > &tokens) const
Attempt tab completion for this topic.
Definition: MSXMixer.cc:764
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:134
void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:10
void registerSound(SoundDevice &device, double volume, int balance, unsigned numChannels)
Use this method to register a given sounddevice.
Definition: MSXMixer.cc:138
EmuTime getFastAdd(unsigned n) const
std::unique_ptr< T > make_unique()
Definition: memory.hh:27
#define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH)
Definition: vla.hh:44
#define UNREACHABLE
Definition: unreachable.hh:56