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