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())
58 masterVolume.
attach(*
this);
59 speedManager.
attach(*
this);
60 throttleManager.
attach(*
this);
68 assert(infos.empty());
70 throttleManager.
detach(*
this);
71 speedManager.
detach(*
this);
72 masterVolume.
detach(*
this);
78 : channelSettings(numChannels)
83 int balance,
unsigned numChannels)
86 const std::string& name = device.
getName();
91 commandController,
tmpStrCat(name,
"_volume"),
92 "the volume of this sound chip", 75, 0, 100);
94 commandController,
tmpStrCat(name,
"_balance"),
95 "the balance of this sound chip", balance, -100, 100);
101 auto ch_name =
tmpStrCat(name,
"_ch", i + 1);
103 channelSettings.record = std::make_unique<StringSetting>(
104 commandController,
tmpStrCat(ch_name,
"_record"),
105 "filename to record this channel to",
107 channelSettings.record->attach(*
this);
109 channelSettings.mute = std::make_unique<BooleanSetting>(
110 commandController,
tmpStrCat(ch_name,
"_mute"),
111 "sets mute-status of individual sound channels",
113 channelSettings.mute->attach(*
this);
117 auto& i = infos.emplace_back(std::move(info));
118 updateVolumeParams(i);
126 it->volumeSetting->detach(*
this);
127 it->balanceSetting->detach(*
this);
128 for (
auto& s : it->channelSettings) {
129 s.record->detach(*
this);
130 s.mute->detach(*
this);
140 ++synchronousCounter;
141 if (synchronousCounter == 1) {
145 assert(synchronousCounter > 0);
146 --synchronousCounter;
147 if (synchronousCounter == 0) {
155 return synchronousCounter ? 1.0 : speedManager.
getSpeed();
161 assert(count <= 8192);
162 ALIGNAS_SSE std::array<StereoFloat, 8192> mixBuffer_;
163 auto mixBuffer =
subspan(mixBuffer_, 0, count);
166 generate(mixBuffer, time);
168 if (!muteCount && fragmentSize) {
187static inline void mul(
float* buf,
size_t n,
float f)
192 assume_SSE_aligned(buf);
202static inline void mul(std::span<float> buf,
float f)
204 assert(!buf.empty());
205 mul(buf.data(), buf.size(), f);
207static inline void mul(std::span<StereoFloat> buf,
float f)
209 assert(!buf.empty());
210 mul(&buf.data()->left, 2 * buf.size(), f);
214static inline void mulAcc(
215 float* __restrict acc,
const float* __restrict mul,
size_t n,
float f)
218 assume_SSE_aligned(acc);
219 assume_SSE_aligned(mul);
222 acc[i + 0] += mul[i + 0] * f;
223 acc[i + 1] += mul[i + 1] * f;
224 acc[i + 2] += mul[i + 2] * f;
225 acc[i + 3] += mul[i + 3] * f;
229static inline void mulAcc(std::span<float> acc, std::span<const float> mul,
float f)
231 assert(!acc.empty());
232 assert(acc.size() == mul.size());
233 mulAcc(acc.data(), mul.data(), acc.size(), f);
235static inline void mulAcc(std::span<StereoFloat> acc, std::span<const StereoFloat> mul,
float f)
237 assert(!acc.empty());
238 assert(acc.size() == mul.size());
239 mulAcc(&acc.data()->left, &mul.data()->left, 2 * acc.size(), f);
244static inline void mulExpand(
float* buf,
size_t n,
float l,
float r)
250 buf[2 * i + 0] = l *
t;
251 buf[2 * i + 1] = r *
t;
254static inline void mulExpand(std::span<StereoFloat> buf,
float l,
float r)
256 mulExpand(&buf.data()->left, buf.size(), l, r);
261static inline void mulExpandAcc(
262 float* __restrict acc,
const float* __restrict mul,
size_t n,
268 acc[2 * i + 0] += l *
t;
269 acc[2 * i + 1] += r *
t;
272static inline void mulExpandAcc(
273 std::span<StereoFloat> acc, std::span<const float> mul,
float l,
float r)
275 assert(!acc.empty());
276 assert(acc.size() == mul.size());
277 mulExpandAcc(&acc.data()->left, mul.data(), acc.size(), l, r);
282static inline void mulMix2(std::span<StereoFloat> buf,
float l1,
float l2,
float r1,
float r2)
284 assert(!buf.empty());
285 for (
auto& s : buf) {
288 s.left = l1 * t1 + l2 * t2;
289 s.right = r1 * t1 + r2 * t2;
295static inline void mulMix2Acc(
296 std::span<StereoFloat> acc, std::span<const StereoFloat> mul,
297 float l1,
float l2,
float r1,
float r2)
299 assert(!acc.empty());
300 assert(acc.size() == mul.size());
304 auto t1 = mul[i].left;
305 auto t2 = mul[i].right;
306 acc[i].left += l1 * t1 + l2 * t2;
307 acc[i].right += r1 * t1 + r2 * t2;
326static constexpr auto R = 511.0f / 512.0f;
329static inline float filterMonoNull(
float t0, std::span<StereoFloat> out)
331 assert(!out.empty());
332 for (
auto& o : out) {
343static inline std::tuple<float, float> filterStereoNull(
344 float tl0,
float tr0, std::span<StereoFloat> out)
346 assert(!out.empty());
347 for (
auto& o : out) {
359static inline float filterMonoMono(
360 float t0, std::span<const float> in, std::span<StereoFloat> out)
362 assert(in.size() == out.size());
363 assert(!out.empty());
365 auto t1 =
R * t0 + i;
375static inline std::tuple<float, float>
376filterStereoMono(
float tl0,
float tr0,
377 std::span<const float> in,
378 std::span<StereoFloat> out)
380 assert(in.size() == out.size());
381 assert(!out.empty());
383 auto tl1 =
R * tl0 + i;
384 auto tr1 =
R * tr0 + i;
394static inline std::tuple<float, float>
395filterStereoStereo(
float tl0,
float tr0,
396 std::span<const StereoFloat> in,
397 std::span<StereoFloat> out)
399 assert(in.size() == out.size());
400 assert(!out.empty());
402 auto tl1 =
R * tl0 + i.left;
403 auto tr1 =
R * tr0 + i.right;
413static inline std::tuple<float, float>
414filterBothStereo(
float tl0,
float tr0,
415 std::span<const float> inM,
416 std::span<const StereoFloat> inS,
417 std::span<StereoFloat> out)
419 assert(inM.size() == out.size());
420 assert(inS.size() == out.size());
421 assert(!out.empty());
423 auto tl1 =
R * tl0 + is.left +
im;
424 auto tr1 =
R * tr0 + is.right +
im;
433static bool approxEqual(
float x,
float y)
435 constexpr float threshold = 1.0f / 32768;
436 return std::abs(x - y) < threshold;
439void MSXMixer::generate(std::span<StereoFloat> output, EmuTime::param time)
450 auto samples = output.size();
453 for (
auto& info : infos) {
454 bool ignore = info.device->updateBuffer(0, dummyBuf.data(), time);
465 auto* monoBufPtr = monoBufExtra.data();
466 auto* stereoBufPtr = &stereoBufExtra.data()->left;
467 auto* tmpBufPtr = &tmpBufExtra.data()->left;
468 auto monoBuf = monoBufExtra .subspan(0, samples);
469 auto stereoBuf = stereoBufExtra.subspan(0, samples);
470 auto tmpBufStereo = tmpBufExtra .subspan(0, samples);
471 auto tmpBufMono = std::span{tmpBufPtr, samples};
473 constexpr unsigned HAS_MONO_FLAG = 1;
474 constexpr unsigned HAS_STEREO_FLAG = 2;
475 unsigned usedBuffers = 0;
479 for (
auto& info : infos) {
480 SoundDevice& device = *info.device;
481 auto l1 = info.left1;
482 auto r1 = info.right1;
483 if (!device.isStereo()) {
487 if (!(usedBuffers & HAS_MONO_FLAG)) {
490 if (device.updateBuffer(samples, monoBufPtr, time)) {
491 usedBuffers |= HAS_MONO_FLAG;
497 if (device.updateBuffer(samples, tmpBufPtr, time)) {
498 mulAcc(monoBuf, tmpBufMono, l1);
503 if (!(usedBuffers & HAS_STEREO_FLAG)) {
506 if (device.updateBuffer(samples, stereoBufPtr, time)) {
507 usedBuffers |= HAS_STEREO_FLAG;
508 mulExpand(stereoBuf, l1, r1);
513 if (device.updateBuffer(samples, tmpBufPtr, time)) {
514 mulExpandAcc(stereoBuf, tmpBufMono, l1, r1);
520 auto l2 = info.left2;
521 auto r2 = info.right2;
526 if (!(usedBuffers & HAS_STEREO_FLAG)) {
529 if (device.updateBuffer(samples, stereoBufPtr, time)) {
530 usedBuffers |= HAS_STEREO_FLAG;
536 if (device.updateBuffer(samples, tmpBufPtr, time)) {
537 mulAcc(stereoBuf, tmpBufStereo, l1);
543 if (!(usedBuffers & HAS_STEREO_FLAG)) {
546 if (device.updateBuffer(samples, stereoBufPtr, time)) {
547 usedBuffers |= HAS_STEREO_FLAG;
548 mulMix2(stereoBuf, l1, l2, r1, r2);
553 if (device.updateBuffer(samples, tmpBufPtr, time)) {
554 mulMix2Acc(stereoBuf, tmpBufStereo, l1, l2, r1, r2);
562 switch (usedBuffers) {
564 if (approxEqual(tl0, tr0)) {
565 if (approxEqual(tl0, 0.0f)) {
572 tl0 = filterMonoNull(tl0, output);
576 std::tie(tl0, tr0) = filterStereoNull(tl0, tr0, output);
581 if (approxEqual(tl0, tr0)) {
583 tl0 = filterMonoMono(tl0, monoBuf, output);
587 std::tie(tl0, tr0) = filterStereoMono(tl0, tr0, monoBuf, output);
591 case HAS_STEREO_FLAG:
592 std::tie(tl0, tr0) = filterStereoStereo(tl0, tr0, stereoBuf, output);
596 std::tie(tl0, tr0) = filterBothStereo(tl0, tr0, monoBuf, stereoBuf, output);
603 return info.device->isStereo() ||
604 info.balanceSetting->getInt() != 0;
610 if (muteCount == 0) {
619 if (muteCount == 0) {
631void MSXMixer::reschedule()
636void MSXMixer::reschedule2()
638 unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512;
646 hostSampleRate = newSampleRate;
647 fragmentSize = newFragmentSize;
651 for (
auto& info : infos) {
652 info.device->setOutputRate(newSampleRate, speedManager.
getSpeed());
658 if ((recorder !=
nullptr) != (newRecorder !=
nullptr)) {
661 recorder = newRecorder;
666 if (&
setting == &masterVolume) {
667 updateMasterVolume();
668 }
else if (
dynamic_cast<const IntegerSetting*
>(&
setting)) {
670 [&](
const SoundDeviceInfo& i) {
672 i.balanceSetting.get()); });
673 updateVolumeParams(*it);
674 }
else if (
dynamic_cast<const StringSetting*
>(&
setting)) {
676 }
else if (
dynamic_cast<const BooleanSetting*
>(&
setting)) {
683void MSXMixer::changeRecordSetting(
const Setting&
setting)
685 for (
auto& info : infos) {
686 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
687 if (settings.record.get() == &
setting) {
688 info.device->recordChannel(
691 settings.record->getString()))));
699void MSXMixer::changeMuteSetting(
const Setting&
setting)
701 for (
auto& info : infos) {
702 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
703 if (settings.mute.get() == &
setting) {
704 info.device->muteChannel(
705 unsigned(channel), settings.mute->getBoolean());
713void MSXMixer::update(
const SpeedManager& )
noexcept
715 if (synchronousCounter == 0) {
727void MSXMixer::update(
const ThrottleManager& )
noexcept
733void MSXMixer::updateVolumeParams(SoundDeviceInfo& info)
const
735 int mVolume = masterVolume.
getInt();
736 int dVolume = info.volumeSetting->getInt();
737 float volume = info.defaultVolume * narrow<float>(mVolume) * narrow<float>(dVolume) * (1.0f / (100.0f * 100.0f));
738 int balance = info.balanceSetting->getInt();
739 auto [l1, r1, l2, r2] = [&] {
740 if (info.device->isStereo()) {
742 float b = (narrow<float>(balance) + 100.0f) * (1.0f / 100.0f);
746 volume * sqrtf(std::max(0.0f, 1.0f - b)),
747 volume * sqrtf(std::max(0.0f, b))
750 float b = narrow<float>(balance) * (1.0f / 100.0f);
752 volume * sqrtf(std::max(0.0f, 1.0f - b)),
753 volume * sqrtf(std::max(0.0f, b)),
761 float b = (narrow<float>(balance) + 100.0f) * (1.0f / 200.0f);
763 volume * sqrtf(std::max(0.0f, 1.0f - b)),
764 volume * sqrtf(std::max(0.0f, b)),
770 auto [ampL, ampR] = info.device->getAmplificationFactor();
771 info.left1 = l1 * ampL;
772 info.right1 = r1 * ampR;
773 info.left2 = l2 * ampL;
774 info.right2 = r2 * ampR;
777void MSXMixer::updateMasterVolume()
779 for (
auto& p : infos) {
780 updateVolumeParams(p);
787 updateVolumeParams(*it);
790void MSXMixer::executeUntil(EmuTime::param time)
811 [](
auto& i) {
return i.device->getName(); });
812 return (it !=
end(infos)) ? std::to_address(it) :
nullptr;
818 return info ? info->device :
nullptr;
821MSXMixer::SoundDeviceInfoTopic::SoundDeviceInfoTopic(
823 :
InfoTopic(machineInfoCommand,
"sounddevice")
827void MSXMixer::SoundDeviceInfoTopic::execute(
828 std::span<const TclObject> tokens, TclObject& result)
const
830 auto& msxMixer =
OUTER(MSXMixer, soundDeviceInfo);
831 switch (tokens.size()) {
835 [](
auto& info) { return info.device->getName(); }));
838 SoundDevice* device = msxMixer.findDevice(tokens[2].getString());
840 throw CommandException(
"Unknown sound device");
842 result = device->getDescription();
846 throw CommandException(
"Too many parameters");
850std::string MSXMixer::SoundDeviceInfoTopic::help(std::span<const TclObject> )
const
852 return "Shows a list of available sound devices.\n";
855void MSXMixer::SoundDeviceInfoTopic::tabCompletion(std::vector<std::string>& tokens)
const
857 if (tokens.size() == 3) {
859 OUTER(MSXMixer, soundDeviceInfo).infos,
860 [](
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:
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)