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 "HostCPU.hh"
9 #include "MemoryOps.hh"
10 #include "MemBuffer.hh"
11 #include "MSXException.hh"
12 #include "aligned.hh"
13 #include "likely.hh"
14 #include "vla.hh"
15 #include "unreachable.hh"
16 #include "memory.hh"
17 #include "build-info.hh"
18 #include <cstring>
19 #include <cassert>
20 
21 using std::string;
22 
23 namespace openmsx {
24 
26 int* mixBuffer; // 16-byte aligned ptr into mixBufferStorage (for SSE access)
27 
28 static void allocateMixBuffer(unsigned size)
29 {
30  size += 3; // to be able to align
33  // align at 16-byte boundary
34  auto tmp = reinterpret_cast<size_t>(mixBufferStorage.data());
35  mixBuffer = reinterpret_cast<int*>((tmp + 15) & ~15);
36  }
37 }
38 
39 static string makeUnique(MSXMixer& mixer, string_ref name)
40 {
41  string result = name.str();
42  if (mixer.findDevice(result)) {
43  unsigned n = 0;
44  do {
45  result = StringOp::Builder() << name << " (" << ++n << ')';
46  } while (mixer.findDevice(result));
47  }
48  return result;
49 }
50 
52  string_ref description_,
53  unsigned numChannels_, bool stereo_)
54  : mixer(mixer_)
55  , name(makeUnique(mixer, name_))
56  , description(description_.str())
57  , numChannels(numChannels_)
58  , stereo(stereo_ ? 2 : 1)
59  , numRecordChannels(0)
60  , balanceCenter(true)
61 {
62  assert(numChannels <= MAX_CHANNELS);
63  assert(stereo == 1 || stereo == 2);
64 
65  // initially no channels are muted
66  for (unsigned i = 0; i < numChannels; ++i) {
67  channelMuted[i] = false;
68  channelBalance[i] = 0;
69  }
70 }
71 
73 {
74 }
75 
76 const std::string& SoundDevice::getName() const
77 {
78  return name;
79 }
80 
81 const std::string& SoundDevice::getDescription() const
82 {
83  return description;
84 }
85 
87 {
88  return stereo == 2 || !balanceCenter;
89 }
90 
92 {
93  return 1;
94 }
95 
97 {
98  const XMLElement& soundConfig = config.getChild("sound");
99  double volume = soundConfig.getChildDataAsInt("volume") / 32767.0;
100  int devBalance = 0;
101  string_ref mode = soundConfig.getChildData("mode", "mono");
102  if (mode == "mono") {
103  devBalance = 0;
104  } else if (mode == "left") {
105  devBalance = -100;
106  } else if (mode == "right") {
107  devBalance = 100;
108  } else {
109  throw MSXException("balance \"" + mode + "\" illegal");
110  }
111 
112  for (auto& b : soundConfig.getChildren("balance")) {
113  int balance = b->getDataAsInt();
114 
115  if (!b->hasAttribute("channel")) {
116  devBalance = balance;
117  continue;
118  }
119 
120  // TODO Support other balances
121  if (balance != 0 && balance != -100 && balance != 100) {
123  "balance " << balance << " illegal");
124  }
125  if (balance != 0) {
126  balanceCenter = false;
127  }
128 
129  const string& range = b->getAttribute("channel");
130  for (unsigned c : StringOp::parseRange(range, 1, numChannels)) {
131  channelBalance[c - 1] = balance;
132  }
133  }
134 
135  mixer.registerSound(*this, volume, devBalance, numChannels);
136 }
137 
139 {
140  mixer.unregisterSound(*this);
141 }
142 
144 {
145  mixer.updateStream(time);
146 }
147 
148 void SoundDevice::setInputRate(unsigned sampleRate)
149 {
150  inputSampleRate = sampleRate;
151 }
152 
153 void SoundDevice::recordChannel(unsigned channel, const Filename& filename)
154 {
155  assert(channel < numChannels);
156  bool wasRecording = writer[channel].get() != nullptr;
157  if (!filename.empty()) {
158  writer[channel] = make_unique<Wav16Writer>(
159  filename, stereo, inputSampleRate);
160  } else {
161  writer[channel].reset();
162  }
163  bool recording = writer[channel].get() != nullptr;
164  if (recording != wasRecording) {
165  if (recording) {
166  if (numRecordChannels == 0) {
167  mixer.setSynchronousMode(true);
168  }
169  ++numRecordChannels;
170  assert(numRecordChannels <= numChannels);
171  } else {
172  assert(numRecordChannels > 0);
173  --numRecordChannels;
174  if (numRecordChannels == 0) {
175  mixer.setSynchronousMode(false);
176  }
177  }
178  }
179 }
180 
181 void SoundDevice::muteChannel(unsigned channel, bool muted)
182 {
183  assert(channel < numChannels);
184  channelMuted[channel] = muted;
185 }
186 
187 bool SoundDevice::mixChannels(int* dataOut, unsigned samples)
188 {
189 #if ASM_X86
190  assert((long(dataOut) & 15) == 0); // must be 16-byte aligned
191 #endif
192  if (samples == 0) return true;
193  unsigned outputStereo = isStereo() ? 2 : 1;
194 
196  mset(reinterpret_cast<unsigned*>(dataOut), outputStereo * samples, 0);
197 
198  VLA(int*, bufs, numChannels);
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 (unsigned i = 0; i < numChannels; ++i) {
205  if (!channelMuted[i] && !writer[i].get() && 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  if (separateChannels) {
215  allocateMixBuffer(pitch * separateChannels);
216  mset(reinterpret_cast<unsigned*>(mixBuffer),
217  pitch * separateChannels, 0);
218  // still need to fill in (some) bufs[i] pointers
219  unsigned count = 0;
220  for (unsigned i = 0; i < numChannels; ++i) {
221  if (!(!channelMuted[i] && !writer[i].get() && balanceCenter)) {
222  bufs[i] = &mixBuffer[pitch * count++];
223  }
224  }
225  assert(count == separateChannels);
226  }
227 
228  // note: some SoundDevices (DACSound16S and CassettePlayer) replace the
229  // (single) channel data instead of adding to the exiting data.
230  // ATM that's ok because the existing data is anyway zero.
231  generateChannels(bufs, samples);
232 
233  if (separateChannels == 0) {
234  for (unsigned i = 0; i < numChannels; ++i) {
235  if (bufs[i]) {
236  return true;
237  }
238  }
239  return false;
240  }
241 
242  // record channels
243  for (unsigned i = 0; i < numChannels; ++i) {
244  if (writer[i].get()) {
245  assert(bufs[i] != dataOut);
246  if (bufs[i]) {
247  writer[i]->write(
248  bufs[i], stereo, samples, getAmplificationFactor());
249  } else {
250  writer[i]->writeSilence(stereo, samples);
251  }
252  }
253  }
254 
255  // remove muted channels (explictly by user or by device itself)
256  bool anyUnmuted = false;
257  unsigned numMix = 0;
258  VLA(int, mixBalance, numChannels);
259  for (unsigned i = 0; i < numChannels; ++i) {
260  if (bufs[i] && !channelMuted[i]) {
261  anyUnmuted = true;
262  if (bufs[i] != dataOut) {
263  bufs[numMix] = bufs[i];
264  mixBalance[numMix] = channelBalance[i];
265  ++numMix;
266  }
267  }
268  }
269 
270  if (numMix == 0) {
271  // all extra channels muted
272  return anyUnmuted;
273  }
274 
275  // actually mix channels
276  if (!balanceCenter) {
277  unsigned i = 0;
278  do {
279  int left0 = 0;
280  int right0 = 0;
281  int left1 = 0;
282  int right1 = 0;
283  unsigned j = 0;
284  do {
285  if (mixBalance[j] <= 0) {
286  left0 += bufs[j][i + 0];
287  left1 += bufs[j][i + 1];
288  }
289  if (mixBalance[j] >= 0) {
290  right0 += bufs[j][i + 0];
291  right1 += bufs[j][i + 1];
292  }
293  j++;
294  } while (j < numMix);
295  dataOut[i * 2 + 0] = left0;
296  dataOut[i * 2 + 1] = right0;
297  dataOut[i * 2 + 2] = left1;
298  dataOut[i * 2 + 3] = right1;
299  i += 2;
300  } while (i < samples);
301 
302  return true;
303  }
304 
305  // In the past we had ARM and x86-SSE2 optimized assembly routines for
306  // the stuff below. Currently this code is only rarely used anymore
307  // (only when recording or muting individual soundchip channels), so
308  // it's not worth the extra complexity anymore.
309  unsigned num = samples * stereo;
310  unsigned i = 0;
311  do {
312  int out0 = dataOut[i + 0];
313  int out1 = dataOut[i + 1];
314  int out2 = dataOut[i + 2];
315  int out3 = dataOut[i + 3];
316  unsigned j = 0;
317  do {
318  out0 += bufs[j][i + 0];
319  out1 += bufs[j][i + 1];
320  out2 += bufs[j][i + 2];
321  out3 += bufs[j][i + 3];
322  ++j;
323  } while (j < numMix);
324  dataOut[i + 0] = out0;
325  dataOut[i + 1] = out1;
326  dataOut[i + 2] = out2;
327  dataOut[i + 3] = out3;
328  i += 4;
329  } while (i < num);
330 
331  return true;
332 }
333 
335 {
336  return mixer.getHostSampleClock();
337 }
339 {
340  return mixer.getEffectiveSpeed();
341 }
342 
343 } // namespace openmsx