41 , motherBoard(motherBoard_)
42 , commandController(motherBoard.getMSXCommandController())
43 , masterVolume(mixer.getMasterVolume())
44 , speedManager(globalSettings.getSpeedManager())
45 , throttleManager(globalSettings.getThrottleManager())
46 , prevTime(getCurrentTime(), 44100)
47 , soundDeviceInfo(commandController.getMachineInfoCommand())
51 masterVolume.
attach(*
this);
52 speedManager.
attach(*
this);
53 throttleManager.
attach(*
this);
61 assert(infos.empty());
63 throttleManager.
detach(*
this);
64 speedManager.
detach(*
this);
65 masterVolume.
detach(*
this);
71 : channelSettings(numChannels)
76 int balance,
unsigned numChannels)
79 const std::string& name = device.
getName();
84 commandController,
tmpStrCat(name,
"_volume"),
85 "the volume of this sound chip", 75, 0, 100);
87 commandController,
tmpStrCat(name,
"_balance"),
88 "the balance of this sound chip", balance, -100, 100);
94 auto ch_name =
tmpStrCat(name,
"_ch", i + 1);
96 channelSettings.record = std::make_unique<StringSetting>(
97 commandController,
tmpStrCat(ch_name,
"_record"),
98 "filename to record this channel to",
100 channelSettings.record->attach(*
this);
102 channelSettings.mute = std::make_unique<BooleanSetting>(
103 commandController,
tmpStrCat(ch_name,
"_mute"),
104 "sets mute-status of individual sound channels",
106 channelSettings.mute->attach(*
this);
110 auto& i = infos.emplace_back(std::move(info));
111 updateVolumeParams(i);
119 it->volumeSetting->detach(*
this);
120 it->balanceSetting->detach(*
this);
121 for (
auto& s : it->channelSettings) {
122 s.record->detach(*
this);
123 s.mute->detach(*
this);
133 ++synchronousCounter;
134 if (synchronousCounter == 1) {
138 assert(synchronousCounter > 0);
139 --synchronousCounter;
140 if (synchronousCounter == 0) {
148 return synchronousCounter ? 1.0 : speedManager.
getSpeed();
154 assert(count <= 8192);
158 generate(mixBuffer, time);
160 if (!muteCount && fragmentSize) {
179static inline void mul(
float* buf,
size_t n,
float f)
193static inline void mul(std::span<float> buf,
float f)
195 assert(!buf.empty());
196 mul(buf.data(), buf.size(), f);
198static inline void mul(std::span<StereoFloat> buf,
float f)
200 assert(!buf.empty());
201 mul(&buf.data()->left, 2 * buf.size(), f);
205static inline void mulAcc(
206 float* __restrict acc,
const float* __restrict mul,
size_t n,
float f)
211 acc[i + 0] += mul[i + 0] * f;
212 acc[i + 1] += mul[i + 1] * f;
213 acc[i + 2] += mul[i + 2] * f;
214 acc[i + 3] += mul[i + 3] * f;
218static inline void mulAcc(std::span<float> acc, std::span<const float> mul,
float f)
220 assert(!acc.empty());
221 assert(acc.size() == mul.size());
222 mulAcc(acc.data(), mul.data(), acc.size(), f);
224static inline void mulAcc(std::span<StereoFloat> acc, std::span<const StereoFloat> mul,
float f)
226 assert(!acc.empty());
227 assert(acc.size() == mul.size());
228 mulAcc(&acc.data()->left, &mul.data()->left, 2 * acc.size(), f);
233static inline void mulExpand(
float* buf,
size_t n,
float l,
float r)
239 buf[2 * i + 0] = l *
t;
240 buf[2 * i + 1] = r *
t;
243static inline void mulExpand(std::span<StereoFloat> buf,
float l,
float r)
245 mulExpand(&buf.data()->left, buf.size(), l, r);
250static inline void mulExpandAcc(
251 float* __restrict acc,
const float* __restrict mul,
size_t n,
257 acc[2 * i + 0] += l *
t;
258 acc[2 * i + 1] += r *
t;
261static inline void mulExpandAcc(
262 std::span<StereoFloat> acc, std::span<const float> mul,
float l,
float r)
264 assert(!acc.empty());
265 assert(acc.size() == mul.size());
266 mulExpandAcc(&acc.data()->left, mul.data(), acc.size(), l, r);
271static inline void mulMix2(std::span<StereoFloat> buf,
float l1,
float l2,
float r1,
float r2)
273 assert(!buf.empty());
274 for (
auto& s : buf) {
277 s.left = l1 * t1 + l2 * t2;
278 s.right = r1 * t1 + r2 * t2;
284static inline void mulMix2Acc(
285 std::span<StereoFloat> acc, std::span<const StereoFloat> mul,
286 float l1,
float l2,
float r1,
float r2)
288 assert(!acc.empty());
289 assert(acc.size() == mul.size());
293 auto t1 = mul[i].left;
294 auto t2 = mul[i].right;
295 acc[i].left += l1 * t1 + l2 * t2;
296 acc[i].right += r1 * t1 + r2 * t2;
315static constexpr auto R = 511.0f / 512.0f;
318static inline float filterMonoNull(
float t0, std::span<StereoFloat> out)
320 assert(!out.empty());
321 for (
auto& o : out) {
332static inline std::tuple<float, float> filterStereoNull(
333 float tl0,
float tr0, std::span<StereoFloat> out)
335 assert(!out.empty());
336 for (
auto& o : out) {
348static inline float filterMonoMono(
349 float t0, std::span<const float> in, std::span<StereoFloat> out)
351 assert(in.size() == out.size());
352 assert(!out.empty());
354 auto t1 =
R * t0 + i;
364static inline std::tuple<float, float>
365filterStereoMono(
float tl0,
float tr0,
366 std::span<const float> in,
367 std::span<StereoFloat> out)
369 assert(in.size() == out.size());
370 assert(!out.empty());
372 auto tl1 =
R * tl0 + i;
373 auto tr1 =
R * tr0 + i;
383static inline std::tuple<float, float>
384filterStereoStereo(
float tl0,
float tr0,
385 std::span<const StereoFloat> in,
386 std::span<StereoFloat> out)
388 assert(in.size() == out.size());
389 assert(!out.empty());
391 auto tl1 =
R * tl0 + i.left;
392 auto tr1 =
R * tr0 + i.right;
402static inline std::tuple<float, float>
403filterBothStereo(
float tl0,
float tr0,
404 std::span<const float> inM,
405 std::span<const StereoFloat> inS,
406 std::span<StereoFloat> out)
408 assert(inM.size() == out.size());
409 assert(inS.size() == out.size());
410 assert(!out.empty());
412 auto tl1 =
R * tl0 + is.left +
im;
413 auto tr1 =
R * tr0 + is.right +
im;
422static bool approxEqual(
float x,
float y)
424 constexpr float threshold = 1.0f / 32768;
425 return std::abs(x - y) < threshold;
428void MSXMixer::generate(std::span<StereoFloat> output, EmuTime::param time)
439 auto samples = output.size();
440 assert(samples <= 8192);
443 for (
auto& info : infos) {
444 bool ignore = info.device->updateBuffer(0, dummyBuf.data(), time);
455 auto* monoBufPtr = monoBufExtra.data();
456 auto* stereoBufPtr = &stereoBufExtra.data()->left;
457 auto* tmpBufPtr = &tmpBufExtra.data()->left;
458 auto monoBuf =
subspan(monoBufExtra, 0, samples);
459 auto stereoBuf =
subspan(stereoBufExtra, 0, samples);
460 auto tmpBufStereo =
subspan(tmpBufExtra, 0, samples);
461 auto tmpBufMono = std::span{tmpBufPtr, samples};
463 constexpr unsigned HAS_MONO_FLAG = 1;
464 constexpr unsigned HAS_STEREO_FLAG = 2;
465 unsigned usedBuffers = 0;
469 for (
auto& info : infos) {
470 SoundDevice& device = *info.device;
471 auto l1 = info.left1;
472 auto r1 = info.right1;
473 if (!device.isStereo()) {
477 if (!(usedBuffers & HAS_MONO_FLAG)) {
480 if (device.updateBuffer(samples, monoBufPtr, time)) {
481 usedBuffers |= HAS_MONO_FLAG;
487 if (device.updateBuffer(samples, tmpBufPtr, time)) {
488 mulAcc(monoBuf, tmpBufMono, l1);
493 if (!(usedBuffers & HAS_STEREO_FLAG)) {
496 if (device.updateBuffer(samples, stereoBufPtr, time)) {
497 usedBuffers |= HAS_STEREO_FLAG;
498 mulExpand(stereoBuf, l1, r1);
503 if (device.updateBuffer(samples, tmpBufPtr, time)) {
504 mulExpandAcc(stereoBuf, tmpBufMono, l1, r1);
510 auto l2 = info.left2;
511 auto r2 = info.right2;
516 if (!(usedBuffers & HAS_STEREO_FLAG)) {
519 if (device.updateBuffer(samples, stereoBufPtr, time)) {
520 usedBuffers |= HAS_STEREO_FLAG;
526 if (device.updateBuffer(samples, tmpBufPtr, time)) {
527 mulAcc(stereoBuf, tmpBufStereo, l1);
533 if (!(usedBuffers & HAS_STEREO_FLAG)) {
536 if (device.updateBuffer(samples, stereoBufPtr, time)) {
537 usedBuffers |= HAS_STEREO_FLAG;
538 mulMix2(stereoBuf, l1, l2, r1, r2);
543 if (device.updateBuffer(samples, tmpBufPtr, time)) {
544 mulMix2Acc(stereoBuf, tmpBufStereo, l1, l2, r1, r2);
552 switch (usedBuffers) {
554 if (approxEqual(tl0, tr0)) {
555 if (approxEqual(tl0, 0.0f)) {
562 tl0 = filterMonoNull(tl0, output);
566 std::tie(tl0, tr0) = filterStereoNull(tl0, tr0, output);
571 if (approxEqual(tl0, tr0)) {
573 tl0 = filterMonoMono(tl0, monoBuf, output);
577 std::tie(tl0, tr0) = filterStereoMono(tl0, tr0, monoBuf, output);
581 case HAS_STEREO_FLAG:
582 std::tie(tl0, tr0) = filterStereoStereo(tl0, tr0, stereoBuf, output);
586 std::tie(tl0, tr0) = filterBothStereo(tl0, tr0, monoBuf, stereoBuf, output);
593 return info.device->isStereo() ||
594 info.balanceSetting->getInt() != 0;
600 if (muteCount == 0) {
609 if (muteCount == 0) {
621void MSXMixer::reschedule()
626void MSXMixer::reschedule2()
628 unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512;
636 hostSampleRate = newSampleRate;
637 fragmentSize = newFragmentSize;
641 for (
auto& info : infos) {
642 info.device->setOutputRate(newSampleRate, speedManager.
getSpeed());
648 if ((recorder !=
nullptr) != (newRecorder !=
nullptr)) {
651 recorder = newRecorder;
656 if (&
setting == &masterVolume) {
657 updateMasterVolume();
658 }
else if (
dynamic_cast<const IntegerSetting*
>(&
setting)) {
660 [&](
const SoundDeviceInfo& i) {
662 i.balanceSetting.get()); });
663 updateVolumeParams(*it);
664 }
else if (
dynamic_cast<const StringSetting*
>(&
setting)) {
666 }
else if (
dynamic_cast<const BooleanSetting*
>(&
setting)) {
673void MSXMixer::changeRecordSetting(
const Setting&
setting)
675 for (
auto& info : infos) {
676 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
677 if (settings.record.get() == &
setting) {
678 info.device->recordChannel(
681 settings.record->getString()))));
689void MSXMixer::changeMuteSetting(
const Setting&
setting)
691 for (
auto& info : infos) {
692 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
693 if (settings.mute.get() == &
setting) {
694 info.device->muteChannel(
695 unsigned(channel), settings.mute->getBoolean());
703void MSXMixer::update(
const SpeedManager& )
noexcept
705 if (synchronousCounter == 0) {
717void MSXMixer::update(
const ThrottleManager& )
noexcept
723void MSXMixer::updateVolumeParams(SoundDeviceInfo& info)
const
725 int mVolume = masterVolume.
getInt();
726 int dVolume = info.volumeSetting->getInt();
727 float volume = info.defaultVolume * narrow<float>(mVolume) * narrow<float>(dVolume) * (1.0f / (100.0f * 100.0f));
728 int balance = info.balanceSetting->getInt();
729 auto [l1, r1, l2, r2] = [&] {
730 if (info.device->isStereo()) {
732 float b = (narrow<float>(balance) + 100.0f) * (1.0f / 100.0f);
736 volume * sqrtf(std::max(0.0f, 1.0f - b)),
737 volume * sqrtf(std::max(0.0f, b))
740 float b = narrow<float>(balance) * (1.0f / 100.0f);
742 volume * sqrtf(std::max(0.0f, 1.0f - b)),
743 volume * sqrtf(std::max(0.0f, b)),
751 float b = (narrow<float>(balance) + 100.0f) * (1.0f / 200.0f);
753 volume * sqrtf(std::max(0.0f, 1.0f - b)),
754 volume * sqrtf(std::max(0.0f, b)),
760 auto [ampL, ampR] = info.device->getAmplificationFactor();
761 info.left1 = l1 * ampL;
762 info.right1 = r1 * ampR;
763 info.left2 = l2 * ampL;
764 info.right2 = r2 * ampR;
767void MSXMixer::updateMasterVolume()
769 for (
auto& p : infos) {
770 updateVolumeParams(p);
777 updateVolumeParams(*it);
780void MSXMixer::executeUntil(EmuTime::param time)
801 [](
auto& i) {
return i.device->getName(); });
802 return (it !=
end(infos)) ? std::to_address(it) :
nullptr;
808 return info ? info->device :
nullptr;
811MSXMixer::SoundDeviceInfoTopic::SoundDeviceInfoTopic(
813 :
InfoTopic(machineInfoCommand,
"sounddevice")
817void MSXMixer::SoundDeviceInfoTopic::execute(
818 std::span<const TclObject> tokens, TclObject& result)
const
820 auto& msxMixer =
OUTER(MSXMixer, soundDeviceInfo);
821 switch (tokens.size()) {
825 [](
const auto& info) { return info.device->getName(); }));
828 const auto* device = msxMixer.findDevice(tokens[2].getString());
830 throw CommandException(
"Unknown sound device");
832 result = device->getDescription();
836 throw CommandException(
"Too many parameters");
840std::string MSXMixer::SoundDeviceInfoTopic::help(std::span<const TclObject> )
const
842 return "Shows a list of available sound devices.\n";
845void MSXMixer::SoundDeviceInfoTopic::tabCompletion(std::vector<std::string>& tokens)
const
847 if (tokens.size() == 3) {
849 OUTER(MSXMixer, soundDeviceInfo).infos,
850 [](
auto& info) -> std::string_view {
return info.device->getName(); }));
void addWave(std::span< const StereoFloat > data)
EmuTime getFastAdd(unsigned n) const
unsigned getTicksTill(EmuTime::param e) const
Calculate the number of ticks for this clock until the given time.
void setPeriod(EmuDuration period)
Set the duration of a clock tick.
void reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
This class contains settings that are used by several other class (including some singletons).
int getInt() const noexcept
void update(UpdateType type, std::string_view name, std::string_view value) override
MSXCliComm & getCliComm() override
void mute()
TODO This methods (un)mute the sound.
const SoundDeviceInfo * findDeviceInfo(std::string_view name) const
void setRecorder(AviRecorder *recorder)
void registerSound(SoundDevice &device, float volume, int balance, unsigned numChannels)
Use this method to register a given SoundDevice.
void setSynchronousMode(bool synchronous)
If we're recording, we want to emulate sound at 100% EmuTime speed.
void updateSoftwareVolume(SoundDevice &device)
Used by SoundDevice::setSoftwareVolume()
unsigned getSampleRate() const
double getEffectiveSpeed() const
Returns the ratio of EmuTime-speed per realtime-speed.
SoundDevice * findDevice(std::string_view name) const
bool needStereoRecording() const
MSXMixer(Mixer &mixer, MSXMotherBoard &motherBoard, GlobalSettings &globalSettings)
void updateStream(EmuTime::param time)
Use this method to force an 'early' call to all updateBuffer() methods.
void setMixerParams(unsigned fragmentSize, unsigned sampleRate)
Set new fragment size and sample frequency.
void unregisterSound(SoundDevice &device)
Every SoundDevice must unregister before it is destructed.
void registerMixer(MSXMixer &mixer)
Register per-machine mixer.
void unregisterMixer(MSXMixer &mixer)
Unregister per-machine mixer.
void uploadBuffer(MSXMixer &msxMixer, std::span< const StereoFloat > buffer)
Upload new sample data.
Every class that wants to get scheduled at some point must inherit from this class.
void setSyncPoint(EmuTime::param timestamp)
EmuTime::param getCurrentTime() const
Convenience method: This is the same as getScheduler().getCurrentTime().
const std::string & getName() const
Get the unique name that identifies this sound device.
virtual void setOutputRate(unsigned hostSampleRate, double speed)=0
When a SoundDevice registers itself with the Mixer, the Mixer sets the required sampleRate through th...
double getSpeed() const
Return the desired ratio between EmuTime and real time.
void detach(Observer< T > &observer)
void attach(Observer< T > &observer)
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
string expandTilde(string path)
Expand the '~' character to the users home directory.
This file implemented 3 utility functions:
constexpr void fill(ForwardRange &&range, const T &value)
auto find(InputRange &&range, const T &value)
constexpr bool any_of(InputRange &&range, UnaryPredicate pred)
Zip< false, RangesTuple, Is... > zip_equal(RangesTuple &&ranges, std::index_sequence< Is... >)
constexpr auto transform(Range &&range, UnaryOp op)
#define OUTER(type, member)
constexpr auto subspan(Range &&range, size_t offset, size_t count=std::dynamic_extent)
ITER find_unguarded(ITER first, ITER last, const VAL &val, Proj proj={})
Faster alternative to 'find' when it's guaranteed that the value will be found (if not the behavior i...
constexpr ITER find_if_unguarded(ITER first, ITER last, PRED pred)
Faster alternative to 'find_if' when it's guaranteed that the predicate will be true for at least one...
void move_pop_back(VECTOR &v, typename VECTOR::iterator it)
Erase the pointed to element from the given vector.
auto rfind_unguarded(RANGE &range, const VAL &val, Proj proj={})
Similar to the find(_if)_unguarded functions above, but searches from the back to front.
TemporaryString tmpStrCat(Ts &&... ts)
dynarray< ChannelSettings > channelSettings
SoundDeviceInfo(unsigned numChannels)
std::unique_ptr< IntegerSetting > balanceSetting
std::unique_ptr< IntegerSetting > volumeSetting
constexpr auto end(const zstring_view &x)