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