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