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
18namespace openmsx {
19
20static MemBuffer<float, SSE_ALIGNMENT> mixBuffer;
21static unsigned mixBufferSize = 0;
22
23static 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
43void 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
56SoundDevice::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
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
138void SoundDevice::updateStream(EmuTime::param time)
139{
140 mixer.updateStream(time);
141}
142
143void SoundDevice::setSoftwareVolume(float volume, EmuTime::param time)
144{
145 setSoftwareVolume(volume, volume, time);
146}
147
148void 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
156void 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
184void SoundDevice::muteChannel(unsigned channel, bool muted)
185{
186 assert(channel < numChannels);
187 channelMuted[channel] = muted;
188}
189
190bool 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
void resize(size_t size)
Grow or shrink the memory block.
Definition: MemBuffer.hh:111
const T * data() const
Returns pointer to the start of the memory buffer.
Definition: MemBuffer.hh:81
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