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())
55 masterVolume.
attach(*
this);
56 speedManager.
attach(*
this);
57 throttleManager.
attach(*
this);
65 assert(infos.empty());
67 throttleManager.
detach(*
this);
68 speedManager.
detach(*
this);
69 masterVolume.
detach(*
this);
75 : channelSettings(numChannels)
80 int balance,
unsigned numChannels)
83 const std::string& name = device.
getName();
88 commandController,
tmpStrCat(name,
"_volume"),
89 "the volume of this sound chip", 75, 0, 100);
91 commandController,
tmpStrCat(name,
"_balance"),
92 "the balance of this sound chip", balance, -100, 100);
98 auto ch_name =
tmpStrCat(name,
"_ch", i + 1);
100 channelSettings.record = std::make_unique<StringSetting>(
101 commandController,
tmpStrCat(ch_name,
"_record"),
102 "filename to record this channel to",
104 channelSettings.record->attach(*
this);
106 channelSettings.mute = std::make_unique<BooleanSetting>(
107 commandController,
tmpStrCat(ch_name,
"_mute"),
108 "sets mute-status of individual sound channels",
110 channelSettings.mute->attach(*
this);
114 auto& i = infos.emplace_back(std::move(info));
115 updateVolumeParams(i);
123 it->volumeSetting->detach(*
this);
124 it->balanceSetting->detach(*
this);
125 for (
auto& s : it->channelSettings) {
126 s.record->detach(*
this);
127 s.mute->detach(*
this);
137 ++synchronousCounter;
138 if (synchronousCounter == 1) {
142 assert(synchronousCounter > 0);
143 --synchronousCounter;
144 if (synchronousCounter == 0) {
152 return synchronousCounter ? 1.0 : speedManager.
getSpeed();
158 assert(count <= 8192);
159 ALIGNAS_SSE std::array<StereoFloat, 8192> mixBuffer_;
160 auto mixBuffer =
subspan(mixBuffer_, 0, count);
163 generate(mixBuffer, time);
165 if (!muteCount && fragmentSize) {
184static inline void mul(
float* buf,
size_t n,
float f)
189 assume_SSE_aligned(buf);
199static inline void mul(std::span<float> buf,
float f)
201 assert(!buf.empty());
202 mul(buf.data(), buf.size(), f);
204static inline void mul(std::span<StereoFloat> buf,
float f)
206 assert(!buf.empty());
207 mul(&buf.data()->left, 2 * buf.size(), f);
211static inline void mulAcc(
212 float* __restrict acc,
const float* __restrict mul,
size_t n,
float f)
215 assume_SSE_aligned(acc);
216 assume_SSE_aligned(mul);
219 acc[i + 0] += mul[i + 0] * f;
220 acc[i + 1] += mul[i + 1] * f;
221 acc[i + 2] += mul[i + 2] * f;
222 acc[i + 3] += mul[i + 3] * f;
226static inline void mulAcc(std::span<float> acc, std::span<const float> mul,
float f)
228 assert(!acc.empty());
229 assert(acc.size() == mul.size());
230 mulAcc(acc.data(), mul.data(), acc.size(), f);
232static inline void mulAcc(std::span<StereoFloat> acc, std::span<const StereoFloat> mul,
float f)
234 assert(!acc.empty());
235 assert(acc.size() == mul.size());
236 mulAcc(&acc.data()->left, &mul.data()->left, 2 * acc.size(), f);
241static inline void mulExpand(
float* buf,
size_t n,
float l,
float r)
247 buf[2 * i + 0] = l *
t;
248 buf[2 * i + 1] = r *
t;
251static inline void mulExpand(std::span<StereoFloat> buf,
float l,
float r)
253 mulExpand(&buf.data()->left, buf.size(), l, r);
258static inline void mulExpandAcc(
259 float* __restrict acc,
const float* __restrict mul,
size_t n,
265 acc[2 * i + 0] += l *
t;
266 acc[2 * i + 1] += r *
t;
269static inline void mulExpandAcc(
270 std::span<StereoFloat> acc, std::span<const float> mul,
float l,
float r)
272 assert(!acc.empty());
273 assert(acc.size() == mul.size());
274 mulExpandAcc(&acc.data()->left, mul.data(), acc.size(), l, r);
279static inline void mulMix2(std::span<StereoFloat> buf,
float l1,
float l2,
float r1,
float r2)
281 assert(!buf.empty());
282 for (
auto& s : buf) {
285 s.left = l1 * t1 + l2 * t2;
286 s.right = r1 * t1 + r2 * t2;
292static inline void mulMix2Acc(
293 std::span<StereoFloat> acc, std::span<const StereoFloat> mul,
294 float l1,
float l2,
float r1,
float r2)
296 assert(!acc.empty());
297 assert(acc.size() == mul.size());
301 auto t1 = mul[i].left;
302 auto t2 = mul[i].right;
303 acc[i].left += l1 * t1 + l2 * t2;
304 acc[i].right += r1 * t1 + r2 * t2;
323static constexpr auto R = 511.0f / 512.0f;
326static inline float filterMonoNull(
float t0, std::span<StereoFloat> out)
328 assert(!out.empty());
329 for (
auto& o : out) {
340static inline std::tuple<float, float> filterStereoNull(
341 float tl0,
float tr0, std::span<StereoFloat> out)
343 assert(!out.empty());
344 for (
auto& o : out) {
356static inline float filterMonoMono(
357 float t0, std::span<const float> in, std::span<StereoFloat> out)
359 assert(in.size() == out.size());
360 assert(!out.empty());
362 auto t1 =
R * t0 + i;
372static inline std::tuple<float, float>
373filterStereoMono(
float tl0,
float tr0,
374 std::span<const float> in,
375 std::span<StereoFloat> out)
377 assert(in.size() == out.size());
378 assert(!out.empty());
380 auto tl1 =
R * tl0 + i;
381 auto tr1 =
R * tr0 + i;
391static inline std::tuple<float, float>
392filterStereoStereo(
float tl0,
float tr0,
393 std::span<const StereoFloat> in,
394 std::span<StereoFloat> out)
396 assert(in.size() == out.size());
397 assert(!out.empty());
399 auto tl1 =
R * tl0 + i.left;
400 auto tr1 =
R * tr0 + i.right;
410static inline std::tuple<float, float>
411filterBothStereo(
float tl0,
float tr0,
412 std::span<const float> inM,
413 std::span<const StereoFloat> inS,
414 std::span<StereoFloat> out)
416 assert(inM.size() == out.size());
417 assert(inS.size() == out.size());
418 assert(!out.empty());
420 auto tl1 =
R * tl0 + is.left +
im;
421 auto tr1 =
R * tr0 + is.right +
im;
430static bool approxEqual(
float x,
float y)
432 constexpr float threshold = 1.0f / 32768;
433 return std::abs(x - y) < threshold;
436void MSXMixer::generate(std::span<StereoFloat> output, EmuTime::param time)
447 auto samples = output.size();
450 for (
auto& info : infos) {
451 bool ignore = info.device->updateBuffer(0, dummyBuf.data(), time);
462 auto* monoBufPtr = monoBufExtra.data();
463 auto* stereoBufPtr = &stereoBufExtra.data()->left;
464 auto* tmpBufPtr = &tmpBufExtra.data()->left;
465 auto monoBuf = monoBufExtra .subspan(0, samples);
466 auto stereoBuf = stereoBufExtra.subspan(0, samples);
467 auto tmpBufStereo = tmpBufExtra .subspan(0, samples);
468 auto tmpBufMono = std::span{tmpBufPtr, samples};
470 constexpr unsigned HAS_MONO_FLAG = 1;
471 constexpr unsigned HAS_STEREO_FLAG = 2;
472 unsigned usedBuffers = 0;
476 for (
auto& info : infos) {
477 SoundDevice& device = *info.device;
478 auto l1 = info.left1;
479 auto r1 = info.right1;
480 if (!device.isStereo()) {
484 if (!(usedBuffers & HAS_MONO_FLAG)) {
487 if (device.updateBuffer(samples, monoBufPtr, time)) {
488 usedBuffers |= HAS_MONO_FLAG;
494 if (device.updateBuffer(samples, tmpBufPtr, time)) {
495 mulAcc(monoBuf, tmpBufMono, l1);
500 if (!(usedBuffers & HAS_STEREO_FLAG)) {
503 if (device.updateBuffer(samples, stereoBufPtr, time)) {
504 usedBuffers |= HAS_STEREO_FLAG;
505 mulExpand(stereoBuf, l1, r1);
510 if (device.updateBuffer(samples, tmpBufPtr, time)) {
511 mulExpandAcc(stereoBuf, tmpBufMono, l1, r1);
517 auto l2 = info.left2;
518 auto r2 = info.right2;
523 if (!(usedBuffers & HAS_STEREO_FLAG)) {
526 if (device.updateBuffer(samples, stereoBufPtr, time)) {
527 usedBuffers |= HAS_STEREO_FLAG;
533 if (device.updateBuffer(samples, tmpBufPtr, time)) {
534 mulAcc(stereoBuf, tmpBufStereo, l1);
540 if (!(usedBuffers & HAS_STEREO_FLAG)) {
543 if (device.updateBuffer(samples, stereoBufPtr, time)) {
544 usedBuffers |= HAS_STEREO_FLAG;
545 mulMix2(stereoBuf, l1, l2, r1, r2);
550 if (device.updateBuffer(samples, tmpBufPtr, time)) {
551 mulMix2Acc(stereoBuf, tmpBufStereo, l1, l2, r1, r2);
559 switch (usedBuffers) {
561 if (approxEqual(tl0, tr0)) {
562 if (approxEqual(tl0, 0.0f)) {
569 tl0 = filterMonoNull(tl0, output);
573 std::tie(tl0, tr0) = filterStereoNull(tl0, tr0, output);
578 if (approxEqual(tl0, tr0)) {
580 tl0 = filterMonoMono(tl0, monoBuf, output);
584 std::tie(tl0, tr0) = filterStereoMono(tl0, tr0, monoBuf, output);
588 case HAS_STEREO_FLAG:
589 std::tie(tl0, tr0) = filterStereoStereo(tl0, tr0, stereoBuf, output);
593 std::tie(tl0, tr0) = filterBothStereo(tl0, tr0, monoBuf, stereoBuf, output);
600 return info.device->isStereo() ||
601 info.balanceSetting->getInt() != 0;
607 if (muteCount == 0) {
616 if (muteCount == 0) {
628void MSXMixer::reschedule()
633void MSXMixer::reschedule2()
635 unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512;
643 hostSampleRate = newSampleRate;
644 fragmentSize = newFragmentSize;
648 for (
auto& info : infos) {
649 info.device->setOutputRate(newSampleRate, speedManager.
getSpeed());
655 if ((recorder !=
nullptr) != (newRecorder !=
nullptr)) {
658 recorder = newRecorder;
663 if (&
setting == &masterVolume) {
664 updateMasterVolume();
665 }
else if (
dynamic_cast<const IntegerSetting*
>(&
setting)) {
667 [&](
const SoundDeviceInfo& i) {
669 i.balanceSetting.get()); });
670 updateVolumeParams(*it);
671 }
else if (
dynamic_cast<const StringSetting*
>(&
setting)) {
673 }
else if (
dynamic_cast<const BooleanSetting*
>(&
setting)) {
680void MSXMixer::changeRecordSetting(
const Setting&
setting)
682 for (
auto& info : infos) {
683 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
684 if (settings.record.get() == &
setting) {
685 info.device->recordChannel(
688 settings.record->getString()))));
696void MSXMixer::changeMuteSetting(
const Setting&
setting)
698 for (
auto& info : infos) {
699 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
700 if (settings.mute.get() == &
setting) {
701 info.device->muteChannel(
702 unsigned(channel), settings.mute->getBoolean());
710void MSXMixer::update(
const SpeedManager& )
noexcept
712 if (synchronousCounter == 0) {
724void MSXMixer::update(
const ThrottleManager& )
noexcept
730void MSXMixer::updateVolumeParams(SoundDeviceInfo& info)
const
732 int mVolume = masterVolume.
getInt();
733 int dVolume = info.volumeSetting->getInt();
734 float volume = info.defaultVolume * narrow<float>(mVolume) * narrow<float>(dVolume) * (1.0f / (100.0f * 100.0f));
735 int balance = info.balanceSetting->getInt();
736 auto [l1, r1, l2, r2] = [&] {
737 if (info.device->isStereo()) {
739 float b = (narrow<float>(balance) + 100.0f) * (1.0f / 100.0f);
743 volume * sqrtf(std::max(0.0f, 1.0f - b)),
744 volume * sqrtf(std::max(0.0f, b))
747 float b = narrow<float>(balance) * (1.0f / 100.0f);
749 volume * sqrtf(std::max(0.0f, 1.0f - b)),
750 volume * sqrtf(std::max(0.0f, b)),
758 float b = (narrow<float>(balance) + 100.0f) * (1.0f / 200.0f);
760 volume * sqrtf(std::max(0.0f, 1.0f - b)),
761 volume * sqrtf(std::max(0.0f, b)),
767 auto [ampL, ampR] = info.device->getAmplificationFactor();
768 info.left1 = l1 * ampL;
769 info.right1 = r1 * ampR;
770 info.left2 = l2 * ampL;
771 info.right2 = r2 * ampR;
774void MSXMixer::updateMasterVolume()
776 for (
auto& p : infos) {
777 updateVolumeParams(p);
784 updateVolumeParams(*it);
787void MSXMixer::executeUntil(EmuTime::param time)
808 [](
auto& i) {
return i.device->getName(); });
809 return (it !=
end(infos)) ? std::to_address(it) :
nullptr;
815 return info ? info->device :
nullptr;
818MSXMixer::SoundDeviceInfoTopic::SoundDeviceInfoTopic(
820 :
InfoTopic(machineInfoCommand,
"sounddevice")
824void MSXMixer::SoundDeviceInfoTopic::execute(
825 std::span<const TclObject> tokens, TclObject& result)
const
827 auto& msxMixer =
OUTER(MSXMixer, soundDeviceInfo);
828 switch (tokens.size()) {
832 [](
const auto& info) { return info.device->getName(); }));
835 const auto* device = msxMixer.findDevice(tokens[2].getString());
837 throw CommandException(
"Unknown sound device");
839 result = device->getDescription();
843 throw CommandException(
"Too many parameters");
847std::string MSXMixer::SoundDeviceInfoTopic::help(std::span<const TclObject> )
const
849 return "Shows a list of available sound devices.\n";
852void MSXMixer::SoundDeviceInfoTopic::tabCompletion(std::vector<std::string>& tokens)
const
854 if (tokens.size() == 3) {
856 OUTER(MSXMixer, soundDeviceInfo).infos,
857 [](
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.
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
#define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH)
constexpr auto end(const zstring_view &x)