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