23[[nodiscard]]
static std::string makeUnique(
const MSXMixer& mixer, std::string_view name)
25 std::string result(name);
26 if (mixer.findDevice(result)) {
29 result =
strCat(name,
" (", ++n,
')');
30 }
while (mixer.findDevice(result));
49 unsigned numChannels_,
unsigned inputRate,
bool stereo_)
51 , name(makeUnique(mixer, name_))
52 , description(description_)
53 , numChannels(numChannels_)
54 , stereo(stereo_ ? 2 : 1)
57 assert(stereo ==
one_of(1u, 2u));
70 return 1.0f / 32768.0f;
75 const auto& soundConfig = config.
getChild(
"sound");
76 float volume = narrow<float>(soundConfig.getChildDataAsInt(
"volume", 0)) * (1.0f / 32767.0f);
78 std::string_view mode = soundConfig.
getChildData(
"mode",
"mono");
81 }
else if (mode ==
"left") {
83 }
else if (mode ==
"right") {
89 for (
const auto* b : soundConfig.getChildren(
"balance")) {
90 auto balance = StringOp::stringTo<int>(b->getData());
92 throw MSXException(
"balance ", b->getData(),
" illegal");
94 if ((*balance < -100) || (balance > 100)) {
95 throw MSXException(
"balance must be between -100...100: ", *balance);
98 const auto* channel = b->findAttribute(
"channel");
100 devBalance = *balance;
105 channels.foreachSetBit([&](
size_t c) {
106 setBalance(
unsigned(c - 1), narrow_cast<float>(*balance) * (1.0f / 100.0f));
110 mixer.registerSound(*
this, volume, devBalance, numChannels);
115 assert(channel < numChannels);
116 assert(-1.0f <= balance);
117 assert(balance <= 1.0f);
119 if (balance != 0.0f) balanceCenter =
false;
120 auto left = std::sqrt(1.0f - balance);
121 auto right = std::sqrt(1.0f + balance);
122 channelBalance[channel] = Balance{left, right};
127 mixer.updateSoftwareVolume(*
this);
132 mixer.unregisterSound(*
this);
137 mixer.updateStream(time);
148 softwareVolumeLeft = left;
149 softwareVolumeRight = right;
150 mixer.updateSoftwareVolume(*
this);
155 assert(channel < numChannels);
156 bool wasRecording = writer[channel].has_value();
157 if (!filename.
empty()) {
158 writer[channel].emplace(
159 filename, stereo, inputSampleRate);
161 writer[channel].reset();
163 bool recording = writer[channel].has_value();
164 if (recording != wasRecording) {
166 if (numRecordChannels == 0) {
167 mixer.setSynchronousMode(
true);
170 assert(numRecordChannels <= numChannels);
172 assert(numRecordChannels > 0);
174 if (numRecordChannels == 0) {
175 mixer.setSynchronousMode(
false);
183 assert(channel < numChannels);
184 channelMuted[channel] = muted;
189 assert(channel < numChannels);
190 auto& buf = channelBuffers[channel];
192 buf.requestCounter = inputSampleRate;
195 if (buf.stopIdx < requestedSize)
return {};
196 if (buf.silent >= requestedSize)
return {};
197 return {&buf.buffer[buf.stopIdx - requestedSize], requestedSize};
202 if (samples == 0)
return true;
203 size_t outputStereo =
isStereo() ? 2 : 1;
210 auto needSeparateBuffer = [&](
unsigned channel) {
211 return channelBuffers[channel].requestCounter != 0
212 || channelMuted[channel]
216 bool anySeparateChannel =
false;
217 auto size = narrow<unsigned>(samples * stereo);
218 auto padded = (size + 3) & ~3;
219 for (
auto i :
xrange(numChannels)) {
220 auto& cb = channelBuffers[i];
221 if (!needSeparateBuffer(i)) {
226 anySeparateChannel =
true;
227 cb.requestCounter = (cb.requestCounter < samples) ? 0 : unsigned(cb.requestCounter - samples);
229 if (
auto remainingSize = narrow<unsigned>(cb.buffer.size() - cb.stopIdx);
230 remainingSize < padded) {
233 if (
auto allocateSize = 2 * std::max(lastBufferSize, padded);
234 cb.buffer.size() < allocateSize) [[unlikely]] {
236 cb.buffer.resize(allocateSize);
238 unsigned reuse = lastBufferSize >= size ? lastBufferSize - size : 0;
239 if (cb.stopIdx > reuse) {
241 memmove(&cb.buffer[0], &cb.buffer[cb.stopIdx - reuse], reuse *
sizeof(
float));
245 auto* ptr = &cb.buffer[cb.stopIdx];
252 static_assert(
sizeof(float) ==
sizeof(uint32_t));
253 if ((numChannels != 1) || anySeparateChannel) {
259 ranges::fill(std::span{dataOut, outputStereo * samples}, 0.0f);
264 if (!anySeparateChannel) {
266 [&](
auto i) {
return bufs[i]; });
269 for (
auto i :
xrange(numChannels)) {
272 assert(bufs[i] != dataOut);
277 std::span{bufs[i], samples},
281 std::span{std::bit_cast<const StereoFloat*>(bufs[i]), samples},
282 amp.left, amp.right);
285 writer[i]->writeSilence(narrow<unsigned>(stereo * samples));
289 auto& cb = channelBuffers[i];
293 cb.silent += narrow<unsigned>(samples);
298 bool anyUnmuted =
false;
301 for (
auto i :
xrange(numChannels)) {
302 if (bufs[i] && !channelMuted[i]) {
304 if (bufs[i] != dataOut) {
305 bufs[numMix] = bufs[i];
306 mixBalance[numMix] = channelBalance[i];
318 if (!balanceCenter) {
327 left0 += bufs[j][i + 0] * mixBalance[j].left;
328 left1 += bufs[j][i + 1] * mixBalance[j].left;
329 right0 += bufs[j][i + 0] * mixBalance[j].right;
330 right1 += bufs[j][i + 1] * mixBalance[j].right;
332 }
while (j < numMix);
333 dataOut[i * 2 + 0] = left0;
334 dataOut[i * 2 + 1] = right0;
335 dataOut[i * 2 + 2] = left1;
336 dataOut[i * 2 + 3] = right1;
338 }
while (i < samples);
347 size_t num = samples * stereo;
350 auto out0 = dataOut[i + 0];
351 auto out1 = dataOut[i + 1];
352 auto out2 = dataOut[i + 2];
353 auto out3 = dataOut[i + 3];
356 out0 += bufs[j][i + 0];
357 out1 += bufs[j][i + 1];
358 out2 += bufs[j][i + 2];
359 out3 += bufs[j][i + 3];
361 }
while (j < numMix);
362 dataOut[i + 0] = out0;
363 dataOut[i + 1] = out1;
364 dataOut[i + 2] = out2;
365 dataOut[i + 3] = out3;
374 return mixer.getHostSampleClock();
378 return mixer.getEffectiveSpeed();
const XMLElement & getChild(std::string_view name) const
Represents a clock with a variable frequency.
This class represents a filename.
bool empty() const
Convenience method to test for empty filename.
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 setBalance(unsigned channel, float balance)
Change the balance of a single channel.
virtual void postSetBalance()
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
IterableBitSet< 64 > parseRange(string_view str, unsigned min, unsigned max)
This file implemented 3 utility functions:
constexpr void fill(ForwardRange &&range, const T &value)
constexpr bool any_of(InputRange &&range, UnaryPredicate pred)
constexpr auto xrange(T e)