45 , motherBoard(motherBoard_)
46 , commandController(motherBoard.getMSXCommandController())
47 , masterVolume(mixer.getMasterVolume())
48 , speedManager(globalSettings.getSpeedManager())
49 , throttleManager(globalSettings.getThrottleManager())
50 , prevTime(getCurrentTime(), 44100)
51 , soundDeviceInfo(commandController.getMachineInfoCommand())
53 hostSampleRate = 44100;
61 masterVolume.
attach(*
this);
62 speedManager.
attach(*
this);
63 throttleManager.
attach(*
this);
71 assert(infos.empty());
73 throttleManager.
detach(*
this);
74 speedManager.
detach(*
this);
75 masterVolume.
detach(*
this);
81 : channelSettings(numChannels)
86 int balance,
unsigned numChannels)
89 const std::string& name = device.
getName();
94 commandController,
tmpStrCat(name,
"_volume"),
95 "the volume of this sound chip", 75, 0, 100);
97 commandController,
tmpStrCat(name,
"_balance"),
98 "the balance of this sound chip", balance, -100, 100);
104 auto ch_name =
tmpStrCat(name,
"_ch", i + 1);
106 channelSettings.record = std::make_unique<StringSetting>(
107 commandController,
tmpStrCat(ch_name,
"_record"),
108 "filename to record this channel to",
110 channelSettings.record->attach(*
this);
112 channelSettings.mute = std::make_unique<BooleanSetting>(
113 commandController,
tmpStrCat(ch_name,
"_mute"),
114 "sets mute-status of individual sound channels",
116 channelSettings.mute->attach(*
this);
120 auto& i = infos.emplace_back(std::move(info));
121 updateVolumeParams(i);
129 it->volumeSetting->detach(*
this);
130 it->balanceSetting->detach(*
this);
131 for (
auto& s : it->channelSettings) {
132 s.record->detach(*
this);
133 s.mute->detach(*
this);
143 ++synchronousCounter;
144 if (synchronousCounter == 1) {
148 assert(synchronousCounter > 0);
149 --synchronousCounter;
150 if (synchronousCounter == 0) {
158 return synchronousCounter ? 1.0 : speedManager.
getSpeed();
164 assert(count <= 8192);
165 ALIGNAS_SSE std::array<StereoFloat, 8192> mixBuffer_;
166 auto mixBuffer =
subspan(mixBuffer_, 0, count);
169 generate(mixBuffer, time);
171 if (!muteCount && fragmentSize) {
190static inline void mul(
float* buf,
size_t n,
float f)
195 assume_SSE_aligned(buf);
205static inline void mul(std::span<float> buf,
float f)
207 assert(!buf.empty());
208 mul(buf.data(), buf.size(), f);
210static inline void mul(std::span<StereoFloat> buf,
float f)
212 assert(!buf.empty());
213 mul(&buf.data()->left, 2 * buf.size(), f);
217static inline void mulAcc(
218 float* __restrict acc,
const float* __restrict mul,
size_t n,
float f)
221 assume_SSE_aligned(acc);
222 assume_SSE_aligned(mul);
225 acc[i + 0] += mul[i + 0] * f;
226 acc[i + 1] += mul[i + 1] * f;
227 acc[i + 2] += mul[i + 2] * f;
228 acc[i + 3] += mul[i + 3] * f;
232static inline void mulAcc(std::span<float> acc, std::span<const float> mul,
float f)
234 assert(!acc.empty());
235 assert(acc.size() == mul.size());
236 mulAcc(acc.data(), mul.data(), acc.size(), f);
238static inline void mulAcc(std::span<StereoFloat> acc, std::span<const StereoFloat> mul,
float f)
240 assert(!acc.empty());
241 assert(acc.size() == mul.size());
242 mulAcc(&acc.data()->left, &mul.data()->left, 2 * acc.size(), f);
247static inline void mulExpand(
float* buf,
size_t n,
float l,
float r)
253 buf[2 * i + 0] = l *
t;
254 buf[2 * i + 1] = r *
t;
257static inline void mulExpand(std::span<StereoFloat> buf,
float l,
float r)
259 mulExpand(&buf.data()->left, buf.size(), l, r);
264static inline void mulExpandAcc(
265 float* __restrict acc,
const float* __restrict mul,
size_t n,
271 acc[2 * i + 0] += l *
t;
272 acc[2 * i + 1] += r *
t;
275static inline void mulExpandAcc(
276 std::span<StereoFloat> acc, std::span<const float> mul,
float l,
float r)
278 assert(!acc.empty());
279 assert(acc.size() == mul.size());
280 mulExpandAcc(&acc.data()->left, mul.data(), acc.size(), l, r);
285static inline void mulMix2(std::span<StereoFloat> buf,
float l1,
float l2,
float r1,
float r2)
287 assert(!buf.empty());
288 for (
auto& s : buf) {
291 s.left = l1 * t1 + l2 * t2;
292 s.right = r1 * t1 + r2 * t2;
298static inline void mulMix2Acc(
299 std::span<StereoFloat> acc, std::span<const StereoFloat> mul,
300 float l1,
float l2,
float r1,
float r2)
302 assert(!acc.empty());
303 assert(acc.size() == mul.size());
307 auto t1 = mul[i].left;
308 auto t2 = mul[i].right;
309 acc[i].left += l1 * t1 + l2 * t2;
310 acc[i].right += r1 * t1 + r2 * t2;
329static constexpr auto R = 511.0f / 512.0f;
332static inline float filterMonoNull(
float t0, std::span<StereoFloat> out)
334 assert(!out.empty());
335 for (
auto& o : out) {
346static inline std::tuple<float, float> filterStereoNull(
347 float tl0,
float tr0, std::span<StereoFloat> out)
349 assert(!out.empty());
350 for (
auto& o : out) {
362static inline float filterMonoMono(
363 float t0, std::span<const float> in, std::span<StereoFloat> out)
365 assert(in.size() == out.size());
366 assert(!out.empty());
368 auto t1 =
R * t0 + i;
378static inline std::tuple<float, float>
379filterStereoMono(
float tl0,
float tr0,
380 std::span<const float> in,
381 std::span<StereoFloat> out)
383 assert(in.size() == out.size());
384 assert(!out.empty());
386 auto tl1 =
R * tl0 + i;
387 auto tr1 =
R * tr0 + i;
397static inline std::tuple<float, float>
398filterStereoStereo(
float tl0,
float tr0,
399 std::span<const StereoFloat> in,
400 std::span<StereoFloat> out)
402 assert(in.size() == out.size());
403 assert(!out.empty());
405 auto tl1 =
R * tl0 + i.left;
406 auto tr1 =
R * tr0 + i.right;
416static inline std::tuple<float, float>
417filterBothStereo(
float tl0,
float tr0,
418 std::span<const float> inM,
419 std::span<const StereoFloat> inS,
420 std::span<StereoFloat> out)
422 assert(inM.size() == out.size());
423 assert(inS.size() == out.size());
424 assert(!out.empty());
426 auto tl1 =
R * tl0 + is.left +
im;
427 auto tr1 =
R * tr0 + is.right +
im;
436static bool approxEqual(
float x,
float y)
438 constexpr float threshold = 1.0f / 32768;
439 return std::abs(x - y) < threshold;
442void MSXMixer::generate(std::span<StereoFloat> output, EmuTime::param time)
453 auto samples = output.size();
456 for (
auto& info : infos) {
457 bool ignore = info.device->updateBuffer(0, dummyBuf.data(), time);
468 auto* monoBufPtr = monoBufExtra.data();
469 auto* stereoBufPtr = &stereoBufExtra.data()->left;
470 auto* tmpBufPtr = &tmpBufExtra.data()->left;
471 auto monoBuf = monoBufExtra .subspan(0, samples);
472 auto stereoBuf = stereoBufExtra.subspan(0, samples);
473 auto tmpBufStereo = tmpBufExtra .subspan(0, samples);
474 auto tmpBufMono = std::span{tmpBufPtr, samples};
476 constexpr unsigned HAS_MONO_FLAG = 1;
477 constexpr unsigned HAS_STEREO_FLAG = 2;
478 unsigned usedBuffers = 0;
482 for (
auto& info : infos) {
483 SoundDevice& device = *info.device;
484 auto l1 = info.left1;
485 auto r1 = info.right1;
486 if (!device.isStereo()) {
490 if (!(usedBuffers & HAS_MONO_FLAG)) {
493 if (device.updateBuffer(samples, monoBufPtr, time)) {
494 usedBuffers |= HAS_MONO_FLAG;
500 if (device.updateBuffer(samples, tmpBufPtr, time)) {
501 mulAcc(monoBuf, tmpBufMono, l1);
506 if (!(usedBuffers & HAS_STEREO_FLAG)) {
509 if (device.updateBuffer(samples, stereoBufPtr, time)) {
510 usedBuffers |= HAS_STEREO_FLAG;
511 mulExpand(stereoBuf, l1, r1);
516 if (device.updateBuffer(samples, tmpBufPtr, time)) {
517 mulExpandAcc(stereoBuf, tmpBufMono, l1, r1);
523 auto l2 = info.left2;
524 auto r2 = info.right2;
529 if (!(usedBuffers & HAS_STEREO_FLAG)) {
532 if (device.updateBuffer(samples, stereoBufPtr, time)) {
533 usedBuffers |= HAS_STEREO_FLAG;
539 if (device.updateBuffer(samples, tmpBufPtr, time)) {
540 mulAcc(stereoBuf, tmpBufStereo, l1);
546 if (!(usedBuffers & HAS_STEREO_FLAG)) {
549 if (device.updateBuffer(samples, stereoBufPtr, time)) {
550 usedBuffers |= HAS_STEREO_FLAG;
551 mulMix2(stereoBuf, l1, l2, r1, r2);
556 if (device.updateBuffer(samples, tmpBufPtr, time)) {
557 mulMix2Acc(stereoBuf, tmpBufStereo, l1, l2, r1, r2);
565 switch (usedBuffers) {
567 if (approxEqual(tl0, tr0)) {
568 if (approxEqual(tl0, 0.0f)) {
575 tl0 = filterMonoNull(tl0, output);
579 std::tie(tl0, tr0) = filterStereoNull(tl0, tr0, output);
584 if (approxEqual(tl0, tr0)) {
586 tl0 = filterMonoMono(tl0, monoBuf, output);
590 std::tie(tl0, tr0) = filterStereoMono(tl0, tr0, monoBuf, output);
594 case HAS_STEREO_FLAG:
595 std::tie(tl0, tr0) = filterStereoStereo(tl0, tr0, stereoBuf, output);
599 std::tie(tl0, tr0) = filterBothStereo(tl0, tr0, monoBuf, stereoBuf, output);
606 return info.device->isStereo() ||
607 info.balanceSetting->getInt() != 0;
613 if (muteCount == 0) {
622 if (muteCount == 0) {
634void MSXMixer::reschedule()
639void MSXMixer::reschedule2()
641 unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512;
649 hostSampleRate = newSampleRate;
650 fragmentSize = newFragmentSize;
654 for (
auto& info : infos) {
655 info.device->setOutputRate(newSampleRate, speedManager.
getSpeed());
661 if ((recorder !=
nullptr) != (newRecorder !=
nullptr)) {
664 recorder = newRecorder;
669 if (&
setting == &masterVolume) {
670 updateMasterVolume();
671 }
else if (
dynamic_cast<const IntegerSetting*
>(&
setting)) {
673 [&](
const SoundDeviceInfo& i) {
675 i.balanceSetting.get()); });
676 updateVolumeParams(*it);
677 }
else if (
dynamic_cast<const StringSetting*
>(&
setting)) {
679 }
else if (
dynamic_cast<const BooleanSetting*
>(&
setting)) {
686void MSXMixer::changeRecordSetting(
const Setting&
setting)
688 for (
auto& info : infos) {
689 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
690 if (settings.record.get() == &
setting) {
691 info.device->recordChannel(
694 settings.record->getString()))));
702void MSXMixer::changeMuteSetting(
const Setting&
setting)
704 for (
auto& info : infos) {
705 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
706 if (settings.mute.get() == &
setting) {
707 info.device->muteChannel(
708 unsigned(channel), settings.mute->getBoolean());
716void MSXMixer::update(
const SpeedManager& )
noexcept
718 if (synchronousCounter == 0) {
730void MSXMixer::update(
const ThrottleManager& )
noexcept
736void MSXMixer::updateVolumeParams(SoundDeviceInfo& info)
const
738 int mVolume = masterVolume.
getInt();
739 int dVolume = info.volumeSetting->getInt();
740 float volume = info.defaultVolume * narrow<float>(mVolume) * narrow<float>(dVolume) * (1.0f / (100.0f * 100.0f));
741 int balance = info.balanceSetting->getInt();
742 auto [l1, r1, l2, r2] = [&] {
743 if (info.device->isStereo()) {
745 float b = (narrow<float>(balance) + 100.0f) * (1.0f / 100.0f);
749 volume * sqrtf(std::max(0.0f, 1.0f - b)),
750 volume * sqrtf(std::max(0.0f, b))
753 float b = narrow<float>(balance) * (1.0f / 100.0f);
755 volume * sqrtf(std::max(0.0f, 1.0f - b)),
756 volume * sqrtf(std::max(0.0f, b)),
764 float b = (narrow<float>(balance) + 100.0f) * (1.0f / 200.0f);
766 volume * sqrtf(std::max(0.0f, 1.0f - b)),
767 volume * sqrtf(std::max(0.0f, b)),
773 auto [ampL, ampR] = info.device->getAmplificationFactor();
774 info.left1 = l1 * ampL;
775 info.right1 = r1 * ampR;
776 info.left2 = l2 * ampL;
777 info.right2 = r2 * ampR;
780void MSXMixer::updateMasterVolume()
782 for (
auto& p : infos) {
783 updateVolumeParams(p);
790 updateVolumeParams(*it);
793void MSXMixer::executeUntil(EmuTime::param time)
814 [](
auto& i) {
return i.device->getName(); });
815 return (it !=
end(infos)) ? std::to_address(it) :
nullptr;
821 return info ? info->device :
nullptr;
824MSXMixer::SoundDeviceInfoTopic::SoundDeviceInfoTopic(
826 :
InfoTopic(machineInfoCommand,
"sounddevice")
830void MSXMixer::SoundDeviceInfoTopic::execute(
831 std::span<const TclObject> tokens, TclObject& result)
const
833 auto& msxMixer =
OUTER(MSXMixer, soundDeviceInfo);
834 switch (tokens.size()) {
838 [](
auto& info) { return info.device->getName(); }));
841 SoundDevice* device = msxMixer.findDevice(tokens[2].getString());
843 throw CommandException(
"Unknown sound device");
845 result = device->getDescription();
849 throw CommandException(
"Too many parameters");
853std::string MSXMixer::SoundDeviceInfoTopic::help(std::span<const TclObject> )
const
855 return "Shows a list of available sound devices.\n";
858void MSXMixer::SoundDeviceInfoTopic::tabCompletion(std::vector<std::string>& tokens)
const
860 if (tokens.size() == 3) {
862 OUTER(MSXMixer, soundDeviceInfo).infos,
863 [](
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 reset(EmuTime::param e)
Reset the clock to start ticking at the given time.
void setFreq(unsigned freq)
Change the frequency at which this clock ticks.
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.
MSXMixer(const MSXMixer &)=delete
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
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:
bool any_of(InputRange &&range, UnaryPredicate pred)
constexpr void fill(ForwardRange &&range, const T &value)
auto find(InputRange &&range, const T &value)
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
#define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH)
constexpr auto end(const zstring_view &x)