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 <cassert>
14 #include <memory>
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  do {
52  *buf++ += val;
53  } while (--num);
54 }
55 
57  string_view description_,
58  unsigned numChannels_, bool stereo_)
59  : mixer(mixer_)
60  , name(makeUnique(mixer, name_))
61  , description(description_.str())
62  , numChannels(numChannels_)
63  , stereo(stereo_ ? 2 : 1)
64  , numRecordChannels(0)
65  , balanceCenter(true)
66 {
67  assert(numChannels <= MAX_CHANNELS);
68  assert(stereo == 1 || stereo == 2);
69 
70  // initially no channels are muted
71  for (unsigned i = 0; i < numChannels; ++i) {
72  channelMuted[i] = false;
73  channelBalance[i] = 0;
74  }
75 }
76 
77 SoundDevice::~SoundDevice() = default;
78 
80 {
81  return stereo == 2 || !balanceCenter;
82 }
83 
85 {
86  return 1;
87 }
88 
90 {
91  const XMLElement& soundConfig = config.getChild("sound");
92  float volume = soundConfig.getChildDataAsInt("volume") / 32767.0f;
93  int devBalance = 0;
94  string_view mode = soundConfig.getChildData("mode", "mono");
95  if (mode == "mono") {
96  devBalance = 0;
97  } else if (mode == "left") {
98  devBalance = -100;
99  } else if (mode == "right") {
100  devBalance = 100;
101  } else {
102  throw MSXException("balance \"", mode, "\" illegal");
103  }
104 
105  for (auto& b : soundConfig.getChildren("balance")) {
106  int balance = StringOp::stringToInt(b->getData());
107 
108  if (!b->hasAttribute("channel")) {
109  devBalance = balance;
110  continue;
111  }
112 
113  // TODO Support other balances
114  if (balance != 0 && balance != -100 && balance != 100) {
115  throw MSXException("balance ", balance, " illegal");
116  }
117  if (balance != 0) {
118  balanceCenter = false;
119  }
120 
121  const string& range = b->getAttribute("channel");
122  for (unsigned c : StringOp::parseRange(range, 1, numChannels)) {
123  channelBalance[c - 1] = balance;
124  }
125  }
126 
127  mixer.registerSound(*this, volume, devBalance, numChannels);
128 }
129 
131 {
132  mixer.unregisterSound(*this);
133 }
134 
135 void SoundDevice::updateStream(EmuTime::param time)
136 {
137  mixer.updateStream(time);
138 }
139 
140 void SoundDevice::setSoftwareVolume(VolumeType volume, EmuTime::param time)
141 {
142  setSoftwareVolume(volume, volume, time);
143 }
144 
145 void SoundDevice::setSoftwareVolume(VolumeType left, VolumeType right, EmuTime::param time)
146 {
147  updateStream(time);
148  softwareVolumeLeft = left;
149  softwareVolumeRight = right;
150  mixer.updateSoftwareVolume(*this);
151 }
152 
153 void SoundDevice::recordChannel(unsigned channel, const Filename& filename)
154 {
155  assert(channel < numChannels);
156  bool wasRecording = writer[channel] != nullptr;
157  if (!filename.empty()) {
158  writer[channel] = std::make_unique<Wav16Writer>(
159  filename, stereo, inputSampleRate);
160  } else {
161  writer[channel].reset();
162  }
163  bool recording = writer[channel] != nullptr;
164  if (recording != wasRecording) {
165  if (recording) {
166  if (numRecordChannels == 0) {
167  mixer.setSynchronousMode(true);
168  }
169  ++numRecordChannels;
170  assert(numRecordChannels <= numChannels);
171  } else {
172  assert(numRecordChannels > 0);
173  --numRecordChannels;
174  if (numRecordChannels == 0) {
175  mixer.setSynchronousMode(false);
176  }
177  }
178  }
179 }
180 
181 void SoundDevice::muteChannel(unsigned channel, bool muted)
182 {
183  assert(channel < numChannels);
184  channelMuted[channel] = muted;
185 }
186 
187 bool SoundDevice::mixChannels(int* dataOut, unsigned samples)
188 {
189 #ifdef __SSE2__
190  assert((uintptr_t(dataOut) & 15) == 0); // must be 16-byte aligned
191 #endif
192  if (samples == 0) return true;
193  unsigned outputStereo = isStereo() ? 2 : 1;
194 
196  if (numChannels != 1) {
197  // The generateChannels() method of SoundDevices with more than
198  // one channel will _add_ the generated channel data in the
199  // provided buffers. Those with only one channel will directly
200  // replace the content of the buffer. For the former we must
201  // start from a buffer containing all zeros.
202  mset(reinterpret_cast<unsigned*>(dataOut), outputStereo * samples, 0);
203  }
204 
205  VLA(int*, bufs, numChannels);
206  unsigned separateChannels = 0;
207  unsigned pitch = (samples * stereo + 3) & ~3; // align for SSE access
208  // TODO optimization: All channels with the same balance (according to
209  // channelBalance[]) could use the same buffer when balanceCenter is
210  // false
211  for (unsigned i = 0; i < numChannels; ++i) {
212  if (!channelMuted[i] && !writer[i] && balanceCenter) {
213  // no need to keep this channel separate
214  bufs[i] = dataOut;
215  } else {
216  // muted or recorded channels must go separate
217  // cannot yet fill in bufs[i] here
218  ++separateChannels;
219  }
220  }
221  if (separateChannels) {
222  allocateMixBuffer(pitch * separateChannels);
223  mset(reinterpret_cast<unsigned*>(mixBuffer.data()),
224  pitch * separateChannels, 0);
225  // still need to fill in (some) bufs[i] pointers
226  unsigned count = 0;
227  for (unsigned i = 0; i < numChannels; ++i) {
228  if (!(!channelMuted[i] && !writer[i] && balanceCenter)) {
229  bufs[i] = &mixBuffer[pitch * count++];
230  }
231  }
232  assert(count == separateChannels);
233  }
234 
235  generateChannels(bufs, samples);
236 
237  if (separateChannels == 0) {
238  for (unsigned i = 0; i < numChannels; ++i) {
239  if (bufs[i]) {
240  return true;
241  }
242  }
243  return false;
244  }
245 
246  // record channels
247  for (unsigned i = 0; i < numChannels; ++i) {
248  if (writer[i]) {
249  assert(bufs[i] != dataOut);
250  if (bufs[i]) {
251  auto amp = getAmplificationFactor();
252  writer[i]->write(
253  bufs[i], stereo, samples,
254  amp.first.toFloat(),
255  amp.second.toFloat());
256  } else {
257  writer[i]->writeSilence(stereo, samples);
258  }
259  }
260  }
261 
262  // remove muted channels (explictly by user or by device itself)
263  bool anyUnmuted = false;
264  unsigned numMix = 0;
265  VLA(int, mixBalance, numChannels);
266  for (unsigned i = 0; i < numChannels; ++i) {
267  if (bufs[i] && !channelMuted[i]) {
268  anyUnmuted = true;
269  if (bufs[i] != dataOut) {
270  bufs[numMix] = bufs[i];
271  mixBalance[numMix] = channelBalance[i];
272  ++numMix;
273  }
274  }
275  }
276 
277  if (numMix == 0) {
278  // all extra channels muted
279  return anyUnmuted;
280  }
281 
282  // actually mix channels
283  if (!balanceCenter) {
284  unsigned i = 0;
285  do {
286  int left0 = 0;
287  int right0 = 0;
288  int left1 = 0;
289  int right1 = 0;
290  unsigned j = 0;
291  do {
292  if (mixBalance[j] <= 0) {
293  left0 += bufs[j][i + 0];
294  left1 += bufs[j][i + 1];
295  }
296  if (mixBalance[j] >= 0) {
297  right0 += bufs[j][i + 0];
298  right1 += bufs[j][i + 1];
299  }
300  j++;
301  } while (j < numMix);
302  dataOut[i * 2 + 0] = left0;
303  dataOut[i * 2 + 1] = right0;
304  dataOut[i * 2 + 2] = left1;
305  dataOut[i * 2 + 3] = right1;
306  i += 2;
307  } while (i < samples);
308 
309  return true;
310  }
311 
312  // In the past we had ARM and x86-SSE2 optimized assembly routines for
313  // the stuff below. Currently this code is only rarely used anymore
314  // (only when recording or muting individual soundchip channels), so
315  // it's not worth the extra complexity anymore.
316  unsigned num = samples * stereo;
317  unsigned i = 0;
318  do {
319  int out0 = dataOut[i + 0];
320  int out1 = dataOut[i + 1];
321  int out2 = dataOut[i + 2];
322  int out3 = dataOut[i + 3];
323  unsigned j = 0;
324  do {
325  out0 += bufs[j][i + 0];
326  out1 += bufs[j][i + 1];
327  out2 += bufs[j][i + 2];
328  out3 += bufs[j][i + 3];
329  ++j;
330  } while (j < numMix);
331  dataOut[i + 0] = out0;
332  dataOut[i + 1] = out1;
333  dataOut[i + 2] = out2;
334  dataOut[i + 3] = out3;
335  i += 4;
336  } while (i < num);
337 
338  return true;
339 }
340 
342 {
343  return mixer.getHostSampleClock();
344 }
346 {
347  return mixer.getEffectiveSpeed();
348 }
349 
350 } // namespace openmsx
void muteChannel(unsigned channel, bool muted)
Definition: SoundDevice.cc:181
#define unlikely(x)
Definition: likely.hh:15
void unregisterSound()
Unregisters this sound device with the Mixer.
Definition: SoundDevice.cc:130
void setSoftwareVolume(VolumeType volume, EmuTime::param time)
Change the &#39;software volume&#39; of this sound device.
Definition: SoundDevice.cc:140
bool isStereo() const
Is this a stereo device? This is set in the constructor and cannot be changed anymore.
Definition: SoundDevice.cc:79
void updateStream(EmuTime::param time)
Definition: SoundDevice.cc:135
size_t size(string_view utf8)
bool mixChannels(int *dataOut, unsigned samples)
Calls generateChannels() and combines the output to a single channel.
Definition: SoundDevice.cc:187
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:84
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
SoundDevice(MSXMixer &mixer, string_view name, string_view description, unsigned numChannels, bool stereo=false)
Constructor.
Definition: SoundDevice.cc:56
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:345
const DynamicClock & getHostSampleClock() const
See MSXMixer::getHostSampleClock().
Definition: SoundDevice.cc:341
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:153
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
Definition: SoundDevice.cc:89
static const unsigned MAX_CHANNELS
Definition: SoundDevice.hh:21