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 "Filename.hh"
6 #include "StringOp.hh"
7 #include "MemoryOps.hh"
8 #include "MemBuffer.hh"
9 #include "MSXException.hh"
10 #include "aligned.hh"
11 #include "ranges.hh"
12 #include "one_of.hh"
13 #include "vla.hh"
14 #include "xrange.hh"
15 #include <cassert>
16 #include <memory>
17 
18 namespace openmsx {
19 
20 static MemBuffer<float, SSE_ALIGNMENT> mixBuffer;
21 static unsigned mixBufferSize = 0;
22 
23 static void allocateMixBuffer(unsigned size)
24 {
25  if (mixBufferSize < size) [[unlikely]] {
26  mixBufferSize = size;
27  mixBuffer.resize(mixBufferSize);
28  }
29 }
30 
31 [[nodiscard]] static std::string makeUnique(MSXMixer& mixer, std::string_view name)
32 {
33  std::string result(name);
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(float*& buf, float 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 
56 SoundDevice::SoundDevice(MSXMixer& mixer_, std::string_view name_, static_string_view description_,
57  unsigned numChannels_, unsigned inputRate, bool stereo_)
58  : mixer(mixer_)
59  , name(makeUnique(mixer, name_))
60  , description(description_)
61  , numChannels(numChannels_)
62  , stereo(stereo_ ? 2 : 1)
63  , numRecordChannels(0)
64  , balanceCenter(true)
65 {
66  assert(numChannels <= MAX_CHANNELS);
67  assert(stereo == one_of(1u, 2u));
68 
69  setInputRate(inputRate);
70 
71  // initially no channels are muted
72  ranges::fill(channelMuted, false);
73  ranges::fill(channelBalance, 0);
74 }
75 
76 SoundDevice::~SoundDevice() = default;
77 
79 {
80  return stereo == 2 || !balanceCenter;
81 }
82 
84 {
85  return 1.0f / 32768.0f;
86 }
87 
89 {
90  const auto& soundConfig = config.getChild("sound");
91  float volume = soundConfig.getChildDataAsInt("volume", 0) / 32767.0f;
92  int devBalance = 0;
93  std::string_view mode = soundConfig.getChildData("mode", "mono");
94  if (mode == "mono") {
95  devBalance = 0;
96  } else if (mode == "left") {
97  devBalance = -100;
98  } else if (mode == "right") {
99  devBalance = 100;
100  } else {
101  throw MSXException("balance \"", mode, "\" illegal");
102  }
103 
104  for (const auto* b : soundConfig.getChildren("balance")) {
105  auto balance = StringOp::stringTo<int>(b->getData());
106  if (!balance) {
107  throw MSXException("balance ", b->getData(), " illegal");
108  }
109 
110  const auto* channel = b->findAttribute("channel");
111  if (!channel) {
112  devBalance = *balance;
113  continue;
114  }
115 
116  // TODO Support other balances
117  if (*balance != one_of(0, -100, 100)) {
118  throw MSXException("balance ", *balance, " illegal");
119  }
120  if (*balance != 0) {
121  balanceCenter = false;
122  }
123 
124  auto channels = StringOp::parseRange(channel->getValue(), 1, numChannels);
125  channels.foreachSetBit([&](size_t c) {
126  channelBalance[c - 1] = *balance;
127  });
128  }
129 
130  mixer.registerSound(*this, volume, devBalance, numChannels);
131 }
132 
134 {
135  mixer.unregisterSound(*this);
136 }
137 
138 void SoundDevice::updateStream(EmuTime::param time)
139 {
140  mixer.updateStream(time);
141 }
142 
143 void SoundDevice::setSoftwareVolume(float volume, EmuTime::param time)
144 {
145  setSoftwareVolume(volume, volume, time);
146 }
147 
148 void SoundDevice::setSoftwareVolume(float left, float right, EmuTime::param time)
149 {
150  updateStream(time);
151  softwareVolumeLeft = left;
152  softwareVolumeRight = right;
153  mixer.updateSoftwareVolume(*this);
154 }
155 
156 void SoundDevice::recordChannel(unsigned channel, const Filename& filename)
157 {
158  assert(channel < numChannels);
159  bool wasRecording = writer[channel].has_value();
160  if (!filename.empty()) {
161  writer[channel].emplace(
162  filename, stereo, inputSampleRate);
163  } else {
164  writer[channel].reset();
165  }
166  bool recording = writer[channel].has_value();
167  if (recording != wasRecording) {
168  if (recording) {
169  if (numRecordChannels == 0) {
170  mixer.setSynchronousMode(true);
171  }
172  ++numRecordChannels;
173  assert(numRecordChannels <= numChannels);
174  } else {
175  assert(numRecordChannels > 0);
176  --numRecordChannels;
177  if (numRecordChannels == 0) {
178  mixer.setSynchronousMode(false);
179  }
180  }
181  }
182 }
183 
184 void SoundDevice::muteChannel(unsigned channel, bool muted)
185 {
186  assert(channel < numChannels);
187  channelMuted[channel] = muted;
188 }
189 
190 bool SoundDevice::mixChannels(float* dataOut, unsigned samples)
191 {
192 #ifdef __SSE2__
193  assert((uintptr_t(dataOut) & 15) == 0); // must be 16-byte aligned
194 #endif
195  if (samples == 0) return true;
196  unsigned outputStereo = isStereo() ? 2 : 1;
197 
198  float* bufs[MAX_CHANNELS];
199  unsigned separateChannels = 0;
200  unsigned pitch = (samples * stereo + 3) & ~3; // align for SSE access
201  // TODO optimization: All channels with the same balance (according to
202  // channelBalance[]) could use the same buffer when balanceCenter is
203  // false
204  for (auto i : xrange(numChannels)) {
205  if (!channelMuted[i] && !writer[i] && balanceCenter) {
206  // no need to keep this channel separate
207  bufs[i] = dataOut;
208  } else {
209  // muted or recorded channels must go separate
210  // cannot yet fill in bufs[i] here
211  ++separateChannels;
212  }
213  }
214 
215  static_assert(sizeof(float) == sizeof(uint32_t));
217  if ((numChannels != 1) || separateChannels) {
218  // The generateChannels() method of SoundDevices with more than
219  // one channel will _add_ the generated channel data in the
220  // provided buffers. Those with only one channel will directly
221  // replace the content of the buffer. For the former we must
222  // start from a buffer containing all zeros.
223  mSet(reinterpret_cast<uint32_t*>(dataOut), outputStereo * samples, 0);
224  }
225 
226  if (separateChannels) {
227  allocateMixBuffer(pitch * separateChannels);
228  mSet(reinterpret_cast<uint32_t*>(mixBuffer.data()),
229  pitch * separateChannels, 0);
230  // still need to fill in (some) bufs[i] pointers
231  unsigned count = 0;
232  for (auto i : xrange(numChannels)) {
233  if (!(!channelMuted[i] && !writer[i] && balanceCenter)) {
234  bufs[i] = &mixBuffer[pitch * count++];
235  }
236  }
237  assert(count == separateChannels);
238  }
239 
240  generateChannels(bufs, samples);
241 
242  if (separateChannels == 0) {
243  return ranges::any_of(xrange(numChannels),
244  [&](auto i) { return bufs[i]; });
245  }
246 
247  // record channels
248  for (auto i : xrange(numChannels)) {
249  if (writer[i]) {
250  assert(bufs[i] != dataOut);
251  if (bufs[i]) {
252  auto amp = getAmplificationFactor();
253  writer[i]->write(
254  bufs[i], stereo, samples,
255  amp.left, amp.right);
256  } else {
257  writer[i]->writeSilence(stereo, samples);
258  }
259  }
260  }
261 
262  // remove muted channels (explicitly by user or by device itself)
263  bool anyUnmuted = false;
264  unsigned numMix = 0;
265  VLA(int, mixBalance, numChannels);
266  for (auto i : xrange(numChannels)) {
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  float left0 = 0.0f;
287  float right0 = 0.0f;
288  float left1 = 0.0f;
289  float right1 = 0.0f;
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  auto out0 = dataOut[i + 0];
320  auto out1 = dataOut[i + 1];
321  auto out2 = dataOut[i + 2];
322  auto 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
Definition: one_of.hh:7
const XMLElement & getChild(std::string_view name) const
Definition: DeviceConfig.cc:44
Represents a clock with a variable frequency.
Definition: DynamicClock.hh:16
This class represents a filename.
Definition: Filename.hh:18
const T * data() const
Returns pointer to the start of the memory buffer.
Definition: MemBuffer.hh:81
void resize(size_t size)
Grow or shrink the memory block.
Definition: MemBuffer.hh:111
double getEffectiveSpeed() const
Definition: SoundDevice.cc:345
void recordChannel(unsigned channel, const Filename &filename)
Definition: SoundDevice.cc:156
virtual void generateChannels(float **buffers, unsigned num)=0
Abstract method to generate the actual sound data.
void updateStream(EmuTime::param time)
Definition: SoundDevice.cc:138
bool mixChannels(float *dataOut, unsigned samples)
Calls generateChannels() and combines the output to a single channel.
Definition: SoundDevice.cc:190
AmplificationFactors getAmplificationFactor() const
Definition: SoundDevice.hh:57
static void addFill(float *&buffer, float value, unsigned num)
Adds a number of samples that all have the same value.
Definition: SoundDevice.cc:43
const DynamicClock & getHostSampleClock() const
See MSXMixer::getHostSampleClock().
Definition: SoundDevice.cc:341
void setInputRate(unsigned sampleRate)
Definition: SoundDevice.hh:118
void setSoftwareVolume(float volume, EmuTime::param time)
Change the 'software volume' of this sound device.
Definition: SoundDevice.cc:143
static constexpr unsigned MAX_CHANNELS
Definition: SoundDevice.hh:21
void unregisterSound()
Unregisters this sound device with the Mixer.
Definition: SoundDevice.cc:133
SoundDevice(const SoundDevice &)=delete
bool isStereo() const
Is this a stereo device? This is set in the constructor and cannot be changed anymore.
Definition: SoundDevice.cc:78
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
Definition: SoundDevice.cc:88
void muteChannel(unsigned channel, bool muted)
Definition: SoundDevice.cc:184
virtual float getAmplificationFactorImpl() const
Get amplification/attenuation factor for this device.
Definition: SoundDevice.cc:83
int getChildDataAsInt(std::string_view childName, int defaultValue) const
Definition: XMLElement.cc:81
static_string_view
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
Definition: lz4.cc:146
IterableBitSet< 64 > parseRange(string_view str, unsigned min, unsigned max)
Definition: StringOp.cc:177
This file implemented 3 utility functions:
Definition: Autofire.cc:9
constexpr const char *const filename
bool any_of(InputRange &&range, UnaryPredicate pred)
Definition: ranges.hh:169
constexpr void fill(ForwardRange &&range, const T &value)
Definition: ranges.hh:256
size_t size(std::string_view utf8)
std::string strCat(Ts &&...ts)
Definition: strCat.hh:549
#define VLA(TYPE, NAME, LENGTH)
Definition: vla.hh:10
constexpr auto xrange(T e)
Definition: xrange.hh:133