openMSX
SoundDevice.cc
Go to the documentation of this file.
1 #include "SoundDevice.hh"
2 #include "MSXMixer.hh"
3 #include "DeviceConfig.hh"
4 #include "XMLElement.hh"
5 #include "WavWriter.hh"
6 #include "Filename.hh"
7 #include "StringOp.hh"
8 #include "MemoryOps.hh"
9 #include "MemBuffer.hh"
10 #include "MSXException.hh"
11 #include "likely.hh"
12 #include "vla.hh"
13 #include "memory.hh"
14 #include <cassert>
15 
16 using std::string;
17 
18 namespace openmsx {
19 
20 static MemBuffer<int, SSE2_ALIGNMENT> mixBuffer;
21 static unsigned mixBufferSize = 0;
22 
23 static void allocateMixBuffer(unsigned size)
24 {
25  if (unlikely(mixBufferSize < size)) {
26  mixBufferSize = size;
27  mixBuffer.resize(mixBufferSize);
28  }
29 }
30 
31 static string makeUnique(MSXMixer& mixer, string_view name)
32 {
33  string result = name.str();
34  if (mixer.findDevice(result)) {
35  unsigned n = 0;
36  do {
37  result = strCat(name, " (", ++n, ')');
38  } while (mixer.findDevice(result));
39  }
40  return result;
41 }
42 
43 void SoundDevice::addFill(int*& buf, int val, unsigned num)
44 {
45  // Note: in the past we tried to optimize this by always producing
46  // a multiple of 4 output values. In the general case a sounddevice is
47  // allowed to do this, but only at the end of the soundbuffer. This
48  // method can also be called in the middle of a buffer (so multiple
49  // times per buffer), in such case it does go wrong.
50  assert(num > 0);
51 #ifdef __arm__
52  asm volatile (
53  "subs %[num],%[num],#4\n\t"
54  "bmi 1f\n"
55  "0:\n\t"
56  "ldmia %[buf],{r3-r6}\n\t"
57  "add r3,r3,%[val]\n\t"
58  "add r4,r4,%[val]\n\t"
59  "add r5,r5,%[val]\n\t"
60  "add r6,r6,%[val]\n\t"
61  "stmia %[buf]!,{r3-r6}\n\t"
62  "subs %[num],%[num],#4\n\t"
63  "bpl 0b\n"
64  "1:\n\t"
65  "tst %[num],#2\n\t"
66  "beq 2f\n\t"
67  "ldmia %[buf],{r3-r4}\n\t"
68  "add r3,r3,%[val]\n\t"
69  "add r4,r4,%[val]\n\t"
70  "stmia %[buf]!,{r3-r4}\n"
71  "2:\n\t"
72  "tst %[num],#1\n\t"
73  "beq 3f\n\t"
74  "ldr r3,[%[buf]]\n\t"
75  "add r3,r3,%[val]\n\t"
76  "str r3,[%[buf]],#4\n"
77  "3:\n\t"
78  : [buf] "=r" (buf)
79  , [num] "=r" (num)
80  : "[buf]" (buf)
81  , [val] "r" (val)
82  , "[num]" (num)
83  : "memory", "r3","r4","r5","r6"
84  );
85  return;
86 #endif
87  do {
88  *buf++ += val;
89  } while (--num);
90 }
91 
93  string_view description_,
94  unsigned numChannels_, bool stereo_)
95  : mixer(mixer_)
96  , name(makeUnique(mixer, name_))
97  , description(description_.str())
98  , numChannels(numChannels_)
99  , stereo(stereo_ ? 2 : 1)
100  , numRecordChannels(0)
101  , balanceCenter(true)
102 {
103  assert(numChannels <= MAX_CHANNELS);
104  assert(stereo == 1 || stereo == 2);
105 
106  // initially no channels are muted
107  for (unsigned i = 0; i < numChannels; ++i) {
108  channelMuted[i] = false;
109  channelBalance[i] = 0;
110  }
111 }
112 
113 SoundDevice::~SoundDevice() = default;
114 
116 {
117  return stereo == 2 || !balanceCenter;
118 }
119 
121 {
122  return 1;
123 }
124 
126 {
127  const XMLElement& soundConfig = config.getChild("sound");
128  float volume = soundConfig.getChildDataAsInt("volume") / 32767.0f;
129  int devBalance = 0;
130  string_view mode = soundConfig.getChildData("mode", "mono");
131  if (mode == "mono") {
132  devBalance = 0;
133  } else if (mode == "left") {
134  devBalance = -100;
135  } else if (mode == "right") {
136  devBalance = 100;
137  } else {
138  throw MSXException("balance \"", mode, "\" illegal");
139  }
140 
141  for (auto& b : soundConfig.getChildren("balance")) {
142  int balance = StringOp::stringToInt(b->getData());
143 
144  if (!b->hasAttribute("channel")) {
145  devBalance = balance;
146  continue;
147  }
148 
149  // TODO Support other balances
150  if (balance != 0 && balance != -100 && balance != 100) {
151  throw MSXException("balance ", balance, " illegal");
152  }
153  if (balance != 0) {
154  balanceCenter = false;
155  }
156 
157  const string& range = b->getAttribute("channel");
158  for (unsigned c : StringOp::parseRange(range, 1, numChannels)) {
159  channelBalance[c - 1] = balance;
160  }
161  }
162 
163  mixer.registerSound(*this, volume, devBalance, numChannels);
164 }
165 
167 {
168  mixer.unregisterSound(*this);
169 }
170 
171 void SoundDevice::updateStream(EmuTime::param time)
172 {
173  mixer.updateStream(time);
174 }
175 
176 void SoundDevice::setSoftwareVolume(VolumeType volume, EmuTime::param time)
177 {
178  setSoftwareVolume(volume, volume, time);
179 }
180 
181 void SoundDevice::setSoftwareVolume(VolumeType left, VolumeType right, EmuTime::param time)
182 {
183  updateStream(time);
184  softwareVolumeLeft = left;
185  softwareVolumeRight = right;
186  mixer.updateSoftwareVolume(*this);
187 }
188 
189 void SoundDevice::recordChannel(unsigned channel, const Filename& filename)
190 {
191  assert(channel < numChannels);
192  bool wasRecording = writer[channel] != nullptr;
193  if (!filename.empty()) {
194  writer[channel] = make_unique<Wav16Writer>(
195  filename, stereo, inputSampleRate);
196  } else {
197  writer[channel].reset();
198  }
199  bool recording = writer[channel] != nullptr;
200  if (recording != wasRecording) {
201  if (recording) {
202  if (numRecordChannels == 0) {
203  mixer.setSynchronousMode(true);
204  }
205  ++numRecordChannels;
206  assert(numRecordChannels <= numChannels);
207  } else {
208  assert(numRecordChannels > 0);
209  --numRecordChannels;
210  if (numRecordChannels == 0) {
211  mixer.setSynchronousMode(false);
212  }
213  }
214  }
215 }
216 
217 void SoundDevice::muteChannel(unsigned channel, bool muted)
218 {
219  assert(channel < numChannels);
220  channelMuted[channel] = muted;
221 }
222 
223 bool SoundDevice::mixChannels(int* dataOut, unsigned samples)
224 {
225 #ifdef __SSE2__
226  assert((uintptr_t(dataOut) & 15) == 0); // must be 16-byte aligned
227 #endif
228  if (samples == 0) return true;
229  unsigned outputStereo = isStereo() ? 2 : 1;
230 
232  if (numChannels != 1) {
233  // The generateChannels() method of SoundDevices with more than
234  // one channel will _add_ the generated channel data in the
235  // provided buffers. Those with only one channel will directly
236  // replace the content of the buffer. For the former we must
237  // start from a buffer containing all zeros.
238  mset(reinterpret_cast<unsigned*>(dataOut), outputStereo * samples, 0);
239  }
240 
241  VLA(int*, bufs, numChannels);
242  unsigned separateChannels = 0;
243  unsigned pitch = (samples * stereo + 3) & ~3; // align for SSE access
244  // TODO optimization: All channels with the same balance (according to
245  // channelBalance[]) could use the same buffer when balanceCenter is
246  // false
247  for (unsigned i = 0; i < numChannels; ++i) {
248  if (!channelMuted[i] && !writer[i] && balanceCenter) {
249  // no need to keep this channel separate
250  bufs[i] = dataOut;
251  } else {
252  // muted or recorded channels must go separate
253  // cannot yet fill in bufs[i] here
254  ++separateChannels;
255  }
256  }
257  if (separateChannels) {
258  allocateMixBuffer(pitch * separateChannels);
259  mset(reinterpret_cast<unsigned*>(mixBuffer.data()),
260  pitch * separateChannels, 0);
261  // still need to fill in (some) bufs[i] pointers
262  unsigned count = 0;
263  for (unsigned i = 0; i < numChannels; ++i) {
264  if (!(!channelMuted[i] && !writer[i] && balanceCenter)) {
265  bufs[i] = &mixBuffer[pitch * count++];
266  }
267  }
268  assert(count == separateChannels);
269  }
270 
271  generateChannels(bufs, samples);
272 
273  if (separateChannels == 0) {
274  for (unsigned i = 0; i < numChannels; ++i) {
275  if (bufs[i]) {
276  return true;
277  }
278  }
279  return false;
280  }
281 
282  // record channels
283  for (unsigned i = 0; i < numChannels; ++i) {
284  if (writer[i]) {
285  assert(bufs[i] != dataOut);
286  if (bufs[i]) {
287  auto amp = getAmplificationFactor();
288  writer[i]->write(
289  bufs[i], stereo, samples,
290  amp.first.toFloat(),
291  amp.second.toFloat());
292  } else {
293  writer[i]->writeSilence(stereo, samples);
294  }
295  }
296  }
297 
298  // remove muted channels (explictly by user or by device itself)
299  bool anyUnmuted = false;
300  unsigned numMix = 0;
301  VLA(int, mixBalance, numChannels);
302  for (unsigned i = 0; i < numChannels; ++i) {
303  if (bufs[i] && !channelMuted[i]) {
304  anyUnmuted = true;
305  if (bufs[i] != dataOut) {
306  bufs[numMix] = bufs[i];
307  mixBalance[numMix] = channelBalance[i];
308  ++numMix;
309  }
310  }
311  }
312 
313  if (numMix == 0) {
314  // all extra channels muted
315  return anyUnmuted;
316  }
317 
318  // actually mix channels
319  if (!balanceCenter) {
320  unsigned i = 0;
321  do {
322  int left0 = 0;
323  int right0 = 0;
324  int left1 = 0;
325  int right1 = 0;
326  unsigned j = 0;
327  do {
328  if (mixBalance[j] <= 0) {
329  left0 += bufs[j][i + 0];
330  left1 += bufs[j][i + 1];
331  }
332  if (mixBalance[j] >= 0) {
333  right0 += bufs[j][i + 0];
334  right1 += bufs[j][i + 1];
335  }
336  j++;
337  } while (j < numMix);
338  dataOut[i * 2 + 0] = left0;
339  dataOut[i * 2 + 1] = right0;
340  dataOut[i * 2 + 2] = left1;
341  dataOut[i * 2 + 3] = right1;
342  i += 2;
343  } while (i < samples);
344 
345  return true;
346  }
347 
348  // In the past we had ARM and x86-SSE2 optimized assembly routines for
349  // the stuff below. Currently this code is only rarely used anymore
350  // (only when recording or muting individual soundchip channels), so
351  // it's not worth the extra complexity anymore.
352  unsigned num = samples * stereo;
353  unsigned i = 0;
354  do {
355  int out0 = dataOut[i + 0];
356  int out1 = dataOut[i + 1];
357  int out2 = dataOut[i + 2];
358  int out3 = dataOut[i + 3];
359  unsigned j = 0;
360  do {
361  out0 += bufs[j][i + 0];
362  out1 += bufs[j][i + 1];
363  out2 += bufs[j][i + 2];
364  out3 += bufs[j][i + 3];
365  ++j;
366  } while (j < numMix);
367  dataOut[i + 0] = out0;
368  dataOut[i + 1] = out1;
369  dataOut[i + 2] = out2;
370  dataOut[i + 3] = out3;
371  i += 4;
372  } while (i < num);
373 
374  return true;
375 }
376 
378 {
379  return mixer.getHostSampleClock();
380 }
382 {
383  return mixer.getEffectiveSpeed();
384 }
385 
386 } // namespace openmsx
void muteChannel(unsigned channel, bool muted)
Definition: SoundDevice.cc:217
#define unlikely(x)
Definition: likely.hh:15
void unregisterSound()
Unregisters this sound device with the Mixer.
Definition: SoundDevice.cc:166
void setSoftwareVolume(VolumeType volume, EmuTime::param time)
Change the &#39;software volume&#39; of this sound device.
Definition: SoundDevice.cc:176
bool isStereo() const
Is this a stereo device? This is set in the constructor and cannot be changed anymore.
Definition: SoundDevice.cc:115
void updateStream(EmuTime::param time)
Definition: SoundDevice.cc:171
bool mixChannels(int *dataOut, unsigned num)
Calls generateChannels() and combines the output to a single channel.
Definition: SoundDevice.cc:223
Represents a clock with a variable frequency.
Definition: DynamicClock.hh:15
virtual int getAmplificationFactorImpl() const
Get extra amplification factor for this device.
Definition: SoundDevice.cc:120
This class represents a filename.
Definition: Filename.hh:17
Thanks to enen for testing this on a real cartridge:
Definition: Autofire.cc:5
const std::string & getChildData(string_view name) const
Definition: XMLElement.cc:192
size_t size() const
SoundDevice(MSXMixer &mixer, string_view name, string_view description, unsigned numChannels, bool stereo=false)
Constructor.
Definition: SoundDevice.cc:92
std::pair< VolumeType, VolumeType > getAmplificationFactor() const
Gets this device its &#39;amplification factor&#39;.
Definition: SoundDevice.hh:50
int getChildDataAsInt(string_view name, int defaultValue=0) const
Definition: XMLElement.cc:210
virtual void generateChannels(int **buffers, unsigned num)=0
Abstract method to generate the actual sound data.
This class implements a (close approximation) of the std::string_view class.
Definition: string_view.hh:15
int stringToInt(const string &str)
Definition: StringOp.cc:16
std::string str() const
Definition: string_view.cc:12
static void addFill(int *&buffer, int value, unsigned num)
Adds a number of samples that all have the same value.
Definition: SoundDevice.cc:43
double getEffectiveSpeed() const
Definition: SoundDevice.cc:381
const DynamicClock & getHostSampleClock() const
See MSXMixer::getHostSampleClock().
Definition: SoundDevice.cc:377
std::string strCat(Ts &&...ts)
Definition: strCat.hh:577
set< unsigned > parseRange(string_view str, unsigned min, unsigned max)
Definition: StringOp.cc:266
bool empty() const
Convenience method to test for empty filename.
Definition: Filename.cc:38
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:10
const Children & getChildren() const
Definition: XMLElement.hh:47
const XMLElement & getChild(string_view name) const
Definition: DeviceConfig.cc:39
void recordChannel(unsigned channel, const Filename &filename)
Definition: SoundDevice.cc:189
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
Definition: SoundDevice.cc:125
static const unsigned MAX_CHANNELS
Definition: SoundDevice.hh:21