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