43 , motherBoard(motherBoard_)
44 , commandController(motherBoard.getMSXCommandController())
45 , masterVolume(mixer.getMasterVolume())
46 , speedManager(globalSettings.getSpeedManager())
47 , throttleManager(globalSettings.getThrottleManager())
48 , prevTime(getCurrentTime(), 44100)
49 , soundDeviceInfo(commandController.getMachineInfoCommand())
51 hostSampleRate = 44100;
59 masterVolume.
attach(*
this);
60 speedManager.
attach(*
this);
61 throttleManager.
attach(*
this);
69 assert(infos.empty());
71 throttleManager.
detach(*
this);
72 speedManager.
detach(*
this);
73 masterVolume.
detach(*
this);
79 : channelSettings(numChannels)
84 int balance,
unsigned numChannels)
87 const std::string& name = device.
getName();
92 commandController,
tmpStrCat(name,
"_volume"),
93 "the volume of this sound chip", 75, 0, 100);
95 commandController,
tmpStrCat(name,
"_balance"),
96 "the balance of this sound chip", balance, -100, 100);
102 auto ch_name =
tmpStrCat(name,
"_ch", i + 1);
104 channelSettings.record = std::make_unique<StringSetting>(
105 commandController,
tmpStrCat(ch_name,
"_record"),
106 "filename to record this channel to",
108 channelSettings.record->attach(*
this);
110 channelSettings.mute = std::make_unique<BooleanSetting>(
111 commandController,
tmpStrCat(ch_name,
"_mute"),
112 "sets mute-status of individual sound channels",
114 channelSettings.mute->attach(*
this);
118 auto& i = infos.emplace_back(std::move(info));
119 updateVolumeParams(i);
127 it->volumeSetting->detach(*
this);
128 it->balanceSetting->detach(*
this);
129 for (
auto& s : it->channelSettings) {
130 s.record->detach(*
this);
131 s.mute->detach(*
this);
141 ++synchronousCounter;
142 if (synchronousCounter == 1) {
146 assert(synchronousCounter > 0);
147 --synchronousCounter;
148 if (synchronousCounter == 0) {
156 return synchronousCounter ? 1.0 : speedManager.
getSpeed();
162 assert(count <= 8192);
163 ALIGNAS_SSE std::array<StereoFloat, 8192> mixBuffer_;
164 auto mixBuffer =
subspan(mixBuffer_, 0, count);
167 generate(mixBuffer, time);
169 if (!muteCount && fragmentSize) {
188static inline void mul(
float* buf,
size_t n,
float f)
193 assume_SSE_aligned(buf);
203static inline void mul(std::span<float> buf,
float f)
205 assert(!buf.empty());
206 mul(buf.data(), buf.size(), f);
208static inline void mul(std::span<StereoFloat> buf,
float f)
210 assert(!buf.empty());
211 mul(&buf.data()->left, 2 * buf.size(), f);
215static inline void mulAcc(
216 float* __restrict acc,
const float* __restrict mul,
size_t n,
float f)
219 assume_SSE_aligned(acc);
220 assume_SSE_aligned(mul);
223 acc[i + 0] += mul[i + 0] * f;
224 acc[i + 1] += mul[i + 1] * f;
225 acc[i + 2] += mul[i + 2] * f;
226 acc[i + 3] += mul[i + 3] * f;
230static inline void mulAcc(std::span<float> acc, std::span<const float> mul,
float f)
232 assert(!acc.empty());
233 assert(acc.size() == mul.size());
234 mulAcc(acc.data(), mul.data(), acc.size(), f);
236static inline void mulAcc(std::span<StereoFloat> acc, std::span<const StereoFloat> mul,
float f)
238 assert(!acc.empty());
239 assert(acc.size() == mul.size());
240 mulAcc(&acc.data()->left, &mul.data()->left, 2 * acc.size(), f);
245static inline void mulExpand(
float* buf,
size_t n,
float l,
float r)
251 buf[2 * i + 0] = l *
t;
252 buf[2 * i + 1] = r *
t;
255static inline void mulExpand(std::span<StereoFloat> buf,
float l,
float r)
257 mulExpand(&buf.data()->left, buf.size(), l, r);
262static inline void mulExpandAcc(
263 float* __restrict acc,
const float* __restrict mul,
size_t n,
269 acc[2 * i + 0] += l *
t;
270 acc[2 * i + 1] += r *
t;
273static inline void mulExpandAcc(
274 std::span<StereoFloat> acc, std::span<const float> mul,
float l,
float r)
276 assert(!acc.empty());
277 assert(acc.size() == mul.size());
278 mulExpandAcc(&acc.data()->left, mul.data(), acc.size(), l, r);
283static inline void mulMix2(std::span<StereoFloat> buf,
float l1,
float l2,
float r1,
float r2)
285 assert(!buf.empty());
286 for (
auto& s : buf) {
289 s.left = l1 * t1 + l2 * t2;
290 s.right = r1 * t1 + r2 * t2;
296static inline void mulMix2Acc(
297 std::span<StereoFloat> acc, std::span<const StereoFloat> mul,
298 float l1,
float l2,
float r1,
float r2)
300 assert(!acc.empty());
301 assert(acc.size() == mul.size());
305 auto t1 = mul[i].left;
306 auto t2 = mul[i].right;
307 acc[i].left += l1 * t1 + l2 * t2;
308 acc[i].right += r1 * t1 + r2 * t2;
327static constexpr auto R = 511.0f / 512.0f;
330static inline float filterMonoNull(
float t0, std::span<StereoFloat> out)
332 assert(!out.empty());
333 for (
auto& o : out) {
344static inline std::tuple<float, float> filterStereoNull(
345 float tl0,
float tr0, std::span<StereoFloat> out)
347 assert(!out.empty());
348 for (
auto& o : out) {
360static inline float filterMonoMono(
361 float t0, std::span<const float> in, std::span<StereoFloat> out)
363 assert(in.size() == out.size());
364 assert(!out.empty());
366 auto t1 =
R * t0 + i;
376static inline std::tuple<float, float>
377filterStereoMono(
float tl0,
float tr0,
378 std::span<const float> in,
379 std::span<StereoFloat> out)
381 assert(in.size() == out.size());
382 assert(!out.empty());
384 auto tl1 =
R * tl0 + i;
385 auto tr1 =
R * tr0 + i;
395static inline std::tuple<float, float>
396filterStereoStereo(
float tl0,
float tr0,
397 std::span<const StereoFloat> in,
398 std::span<StereoFloat> out)
400 assert(in.size() == out.size());
401 assert(!out.empty());
403 auto tl1 =
R * tl0 + i.left;
404 auto tr1 =
R * tr0 + i.right;
414static inline std::tuple<float, float>
415filterBothStereo(
float tl0,
float tr0,
416 std::span<const float> inM,
417 std::span<const StereoFloat> inS,
418 std::span<StereoFloat> out)
420 assert(inM.size() == out.size());
421 assert(inS.size() == out.size());
422 assert(!out.empty());
424 auto tl1 =
R * tl0 + is.left +
im;
425 auto tr1 =
R * tr0 + is.right +
im;
434static bool approxEqual(
float x,
float y)
436 constexpr float threshold = 1.0f / 32768;
437 return std::abs(x - y) < threshold;
440void MSXMixer::generate(std::span<StereoFloat> output, EmuTime::param time)
451 auto samples = output.size();
454 for (
auto& info : infos) {
455 bool ignore = info.device->updateBuffer(0, dummyBuf.data(), time);
466 float* monoBufPtr = monoBufExtra.data();
467 float* stereoBufPtr = &stereoBufExtra.data()->left;
468 float* tmpBufPtr = &tmpBufExtra.data()->left;
469 std::span monoBuf = monoBufExtra .subspan(0, samples);
470 std::span stereoBuf = stereoBufExtra.subspan(0, samples);
471 std::span tmpBufStereo = tmpBufExtra .subspan(0, samples);
472 std::span tmpBufMono = std::span{tmpBufPtr, samples};
474 constexpr unsigned HAS_MONO_FLAG = 1;
475 constexpr unsigned HAS_STEREO_FLAG = 2;
476 unsigned usedBuffers = 0;
480 for (
auto& info : infos) {
481 SoundDevice& device = *info.device;
482 auto l1 = info.left1;
483 auto r1 = info.right1;
484 if (!device.isStereo()) {
488 if (!(usedBuffers & HAS_MONO_FLAG)) {
491 if (device.updateBuffer(samples, monoBufPtr, time)) {
492 usedBuffers |= HAS_MONO_FLAG;
498 if (device.updateBuffer(samples, tmpBufPtr, time)) {
499 mulAcc(monoBuf, tmpBufMono, l1);
504 if (!(usedBuffers & HAS_STEREO_FLAG)) {
507 if (device.updateBuffer(samples, stereoBufPtr, time)) {
508 usedBuffers |= HAS_STEREO_FLAG;
509 mulExpand(stereoBuf, l1, r1);
514 if (device.updateBuffer(samples, tmpBufPtr, time)) {
515 mulExpandAcc(stereoBuf, tmpBufMono, l1, r1);
521 auto l2 = info.left2;
522 auto r2 = info.right2;
527 if (!(usedBuffers & HAS_STEREO_FLAG)) {
530 if (device.updateBuffer(samples, stereoBufPtr, time)) {
531 usedBuffers |= HAS_STEREO_FLAG;
537 if (device.updateBuffer(samples, tmpBufPtr, time)) {
538 mulAcc(stereoBuf, tmpBufStereo, l1);
544 if (!(usedBuffers & HAS_STEREO_FLAG)) {
547 if (device.updateBuffer(samples, stereoBufPtr, time)) {
548 usedBuffers |= HAS_STEREO_FLAG;
549 mulMix2(stereoBuf, l1, l2, r1, r2);
554 if (device.updateBuffer(samples, tmpBufPtr, time)) {
555 mulMix2Acc(stereoBuf, tmpBufStereo, l1, l2, r1, r2);
563 switch (usedBuffers) {
565 if (approxEqual(tl0, tr0)) {
566 if (approxEqual(tl0, 0.0f)) {
573 tl0 = filterMonoNull(tl0, output);
577 std::tie(tl0, tr0) = filterStereoNull(tl0, tr0, output);
582 if (approxEqual(tl0, tr0)) {
584 tl0 = filterMonoMono(tl0, monoBuf, output);
588 std::tie(tl0, tr0) = filterStereoMono(tl0, tr0, monoBuf, output);
592 case HAS_STEREO_FLAG:
593 std::tie(tl0, tr0) = filterStereoStereo(tl0, tr0, stereoBuf, output);
597 std::tie(tl0, tr0) = filterBothStereo(tl0, tr0, monoBuf, stereoBuf, output);
604 return info.device->isStereo() ||
605 info.balanceSetting->getInt() != 0;
611 if (muteCount == 0) {
620 if (muteCount == 0) {
632void MSXMixer::reschedule()
637void MSXMixer::reschedule2()
639 unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512;
647 hostSampleRate = newSampleRate;
648 fragmentSize = newFragmentSize;
652 for (
auto& info : infos) {
653 info.device->setOutputRate(newSampleRate, speedManager.
getSpeed());
659 if ((recorder !=
nullptr) != (newRecorder !=
nullptr)) {
662 recorder = newRecorder;
667 if (&
setting == &masterVolume) {
668 updateMasterVolume();
669 }
else if (
dynamic_cast<const IntegerSetting*
>(&
setting)) {
671 [&](
const SoundDeviceInfo& i) {
673 i.balanceSetting.get()); });
674 updateVolumeParams(*it);
675 }
else if (
dynamic_cast<const StringSetting*
>(&
setting)) {
677 }
else if (
dynamic_cast<const BooleanSetting*
>(&
setting)) {
684void MSXMixer::changeRecordSetting(
const Setting&
setting)
686 for (
auto& info : infos) {
687 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
688 if (settings.record.get() == &
setting) {
689 info.device->recordChannel(
692 settings.record->getString()))));
700void MSXMixer::changeMuteSetting(
const Setting&
setting)
702 for (
auto& info : infos) {
703 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
704 if (settings.mute.get() == &
setting) {
705 info.device->muteChannel(
706 unsigned(channel), settings.mute->getBoolean());
714void MSXMixer::update(
const SpeedManager& )
noexcept
716 if (synchronousCounter == 0) {
728void MSXMixer::update(
const ThrottleManager& )
noexcept
734void MSXMixer::updateVolumeParams(SoundDeviceInfo& info)
736 int mVolume = masterVolume.
getInt();
737 int dVolume = info.volumeSetting->getInt();
738 float volume = info.defaultVolume * narrow<float>(mVolume) * narrow<float>(dVolume) * (1.0f / (100.0f * 100.0f));
739 int balance = info.balanceSetting->getInt();
740 auto [l1, r1, l2, r2] = [&] {
741 if (info.device->isStereo()) {
743 float b = (narrow<float>(balance) + 100.0f) * (1.0f / 100.0f);
747 volume * sqrtf(
std::max(0.0f, 1.0f - b)),
751 float b = narrow<float>(balance) * (1.0f / 100.0f);
753 volume * sqrtf(
std::max(0.0f, 1.0f - b)),
762 float b = (narrow<float>(balance) + 100.0f) * (1.0f / 200.0f);
764 volume * sqrtf(
std::max(0.0f, 1.0f - b)),
771 auto [ampL, ampR] = info.device->getAmplificationFactor();
772 info.left1 = l1 * ampL;
773 info.right1 = r1 * ampR;
774 info.left2 = l2 * ampL;
775 info.right2 = r2 * ampR;
778void MSXMixer::updateMasterVolume()
780 for (
auto& p : infos) {
781 updateVolumeParams(p);
788 updateVolumeParams(*it);
791void MSXMixer::executeUntil(EmuTime::param time)
812 [](
auto& i) {
return i.device->getName(); });
813 return (it !=
end(infos)) ? &*it :
nullptr;
819 return info ? info->device :
nullptr;
822MSXMixer::SoundDeviceInfoTopic::SoundDeviceInfoTopic(
824 :
InfoTopic(machineInfoCommand,
"sounddevice")
828void MSXMixer::SoundDeviceInfoTopic::execute(
829 std::span<const TclObject> tokens, TclObject& result)
const
831 auto& msxMixer =
OUTER(MSXMixer, soundDeviceInfo);
832 switch (tokens.size()) {
836 [](
auto& info) { return info.device->getName(); }));
839 SoundDevice* device = msxMixer.findDevice(tokens[2].getString());
841 throw CommandException(
"Unknown sound device");
843 result = device->getDescription();
847 throw CommandException(
"Too many parameters");
851std::string MSXMixer::SoundDeviceInfoTopic::help(std::span<const TclObject> )
const
853 return "Shows a list of available sound devices.\n";
856void MSXMixer::SoundDeviceInfoTopic::tabCompletion(std::vector<std::string>& tokens)
const
858 if (tokens.size() == 3) {
860 OUTER(MSXMixer, soundDeviceInfo).infos,
861 [](
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....
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
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)
size_t size(std::string_view utf8)
auto zip_equal(Ranges &&... ranges)
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)