34 #include "emmintrin.h"
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 , synchronousCounter(0)
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);
80 MSXMixer::SoundDeviceInfo::SoundDeviceInfo(
unsigned numChannels)
81 : channelSettings(numChannels)
86 int balance,
unsigned numChannels)
89 const std::string& name = device.
getName();
90 SoundDeviceInfo info(numChannels);
91 info.device = &device;
92 info.defaultVolume = volume;
93 info.volumeSetting = std::make_unique<IntegerSetting>(
94 commandController,
tmpStrCat(name,
"_volume"),
95 "the volume of this sound chip", 75, 0, 100);
96 info.balanceSetting = std::make_unique<IntegerSetting>(
97 commandController,
tmpStrCat(name,
"_balance"),
98 "the balance of this sound chip", balance, -100, 100);
100 info.volumeSetting->attach(*
this);
101 info.balanceSetting->attach(*
this);
103 for (
auto&& [i, channelSettings] :
enumerate(info.channelSettings)) {
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 float mixBuffer[8192 * 2];
171 assert(
count <= 8192);
174 generate(mixBuffer, time,
count);
176 if (!muteCount && fragmentSize) {
195 static inline void mul(
float* buf,
int n,
float f)
200 assume_SSE_aligned(buf);
212 static inline void mulAcc(
213 float* __restrict acc,
const float* __restrict mul,
int n,
float f)
216 assume_SSE_aligned(acc);
217 assume_SSE_aligned(mul);
220 acc[i + 0] += mul[i + 0] * f;
221 acc[i + 1] += mul[i + 1] * f;
222 acc[i + 2] += mul[i + 2] * f;
223 acc[i + 3] += mul[i + 3] * f;
230 static inline void mulExpand(
float* buf,
int n,
float l,
float r)
236 buf[2 * i + 0] = l *
t;
237 buf[2 * i + 1] = r *
t;
243 static inline void mulExpandAcc(
244 float* __restrict acc,
const float* __restrict mul,
int n,
250 acc[2 * i + 0] += l *
t;
251 acc[2 * i + 1] += r *
t;
257 static inline void mulMix2(
float* buf,
int n,
float l1,
float l2,
float r1,
float r2)
261 auto t1 = buf[2 * i + 0];
262 auto t2 = buf[2 * i + 1];
263 buf[2 * i + 0] = l1 * t1 + l2 * t2;
264 buf[2 * i + 1] = r1 * t1 + r2 * t2;
270 static inline void mulMix2Acc(
271 float* __restrict acc,
const float* __restrict mul,
int n,
272 float l1,
float l2,
float r1,
float r2)
276 auto t1 = mul[2 * i + 0];
277 auto t2 = mul[2 * i + 1];
278 acc[2 * i + 0] += l1 * t1 + l2 * t2;
279 acc[2 * i + 1] += r1 * t1 + r2 * t2;
298 constexpr
auto R = 511.0f / 512.0f;
301 static inline float filterMonoNull(
float t0,
float* __restrict out,
int n)
316 static inline std::tuple<float, float> filterStereoNull(
317 float tl0,
float tr0,
float* __restrict out,
int n)
324 out[2 * i + 0] = tl1 - tl0;
325 out[2 * i + 1] = tr1 - tr0;
333 static inline float filterMonoMono(
float t0,
const float* __restrict in,
334 float* __restrict out,
int n)
339 auto t1 =
R * t0 + in[i];
349 static inline std::tuple<float, float>
350 filterStereoMono(
float tl0,
float tr0,
const float* __restrict in,
351 float* __restrict out,
int n)
357 auto tl1 =
R * tl0 +
x;
358 auto tr1 =
R * tr0 +
x;
359 out[2 * i + 0] = tl1 - tl0;
360 out[2 * i + 1] = tr1 - tr0;
368 static inline std::tuple<float, float>
369 filterStereoStereo(
float tl0,
float tr0,
const float* __restrict in,
370 float* __restrict out,
int n)
375 auto tl1 =
R * tl0 + in[2 * i + 0];
376 auto tr1 =
R * tr0 + in[2 * i + 1];
377 out[2 * i + 0] = tl1 - tl0;
378 out[2 * i + 1] = tr1 - tr0;
386 static inline std::tuple<float, float>
387 filterBothStereo(
float tl0,
float tr0,
const float* __restrict inM,
388 const float* __restrict inS,
float* __restrict out,
int n)
394 auto tl1 =
R * tl0 + inS[2 * i + 0] + m;
395 auto tr1 =
R * tr0 + inS[2 * i + 1] + m;
396 out[2 * i + 0] = tl1 - tl0;
397 out[2 * i + 1] = tr1 - tr0;
404 static bool approxEqual(
float x,
float y)
406 constexpr
float threshold = 1.0f / 32768;
407 return std::abs(
x - y) < threshold;
410 void MSXMixer::generate(
float* output, EmuTime::param time,
unsigned samples)
423 for (
auto& info : infos) {
424 bool ignore = info.device->updateBuffer(0, dummyBuf, time);
436 constexpr
unsigned HAS_MONO_FLAG = 1;
437 constexpr
unsigned HAS_STEREO_FLAG = 2;
438 unsigned usedBuffers = 0;
442 for (
auto& info : infos) {
443 SoundDevice& device = *info.device;
444 auto l1 = info.left1;
445 auto r1 = info.right1;
446 if (!device.isStereo()) {
448 if (!(usedBuffers & HAS_MONO_FLAG)) {
449 if (device.updateBuffer(samples, monoBuf, time)) {
450 usedBuffers |= HAS_MONO_FLAG;
451 mul(monoBuf, samples, l1);
454 if (device.updateBuffer(samples, tmpBuf, time)) {
455 mulAcc(monoBuf, tmpBuf, samples, l1);
459 if (!(usedBuffers & HAS_STEREO_FLAG)) {
460 if (device.updateBuffer(samples, stereoBuf, time)) {
461 usedBuffers |= HAS_STEREO_FLAG;
462 mulExpand(stereoBuf, samples, l1, r1);
465 if (device.updateBuffer(samples, tmpBuf, time)) {
466 mulExpandAcc(stereoBuf, tmpBuf, samples, l1, r1);
471 auto l2 = info.left2;
472 auto r2 = info.right2;
476 if (!(usedBuffers & HAS_STEREO_FLAG)) {
477 if (device.updateBuffer(samples, stereoBuf, time)) {
478 usedBuffers |= HAS_STEREO_FLAG;
479 mul(stereoBuf, 2 * samples, l1);
482 if (device.updateBuffer(samples, tmpBuf, time)) {
483 mulAcc(stereoBuf, tmpBuf, 2 * samples, l1);
487 if (!(usedBuffers & HAS_STEREO_FLAG)) {
488 if (device.updateBuffer(samples, stereoBuf, time)) {
489 usedBuffers |= HAS_STEREO_FLAG;
490 mulMix2(stereoBuf, samples, l1, l2, r1, r2);
493 if (device.updateBuffer(samples, tmpBuf, time)) {
494 mulMix2Acc(stereoBuf, tmpBuf, samples, l1, l2, r1, r2);
502 switch (usedBuffers) {
504 if (approxEqual(tl0, tr0)) {
505 if (approxEqual(tl0, 0.0f)) {
508 memset(output, 0, 2 * samples *
sizeof(
float));
512 tl0 = filterMonoNull(tl0, output, samples);
516 std::tie(tl0, tr0) = filterStereoNull(tl0, tr0, output, samples);
521 if (approxEqual(tl0, tr0)) {
523 tl0 = filterMonoMono(tl0, monoBuf, output, samples);
527 std::tie(tl0, tr0) = filterStereoMono(tl0, tr0, monoBuf, output, samples);
531 case HAS_STEREO_FLAG:
532 std::tie(tl0, tr0) = filterStereoStereo(tl0, tr0, stereoBuf, output, samples);
536 std::tie(tl0, tr0) = filterBothStereo(tl0, tr0, monoBuf, stereoBuf, output, samples);
543 return info.device->isStereo() ||
544 info.balanceSetting->getInt() != 0;
550 if (muteCount == 0) {
559 if (muteCount == 0) {
571 void MSXMixer::reschedule()
576 void MSXMixer::reschedule2()
578 unsigned size = (!muteCount && fragmentSize) ? fragmentSize : 512;
586 hostSampleRate = newSampleRate;
587 fragmentSize = newFragmentSize;
591 for (
auto& info : infos) {
592 info.device->setOutputRate(newSampleRate, speedManager.
getSpeed());
598 if ((recorder !=
nullptr) != (newRecorder !=
nullptr)) {
601 recorder = newRecorder;
606 if (&
setting == &masterVolume) {
607 updateMasterVolume();
608 }
else if (
dynamic_cast<const IntegerSetting*
>(&
setting)) {
610 [&](
const SoundDeviceInfo& i) {
612 i.balanceSetting.get()); });
613 updateVolumeParams(*it);
614 }
else if (
dynamic_cast<const StringSetting*
>(&
setting)) {
616 }
else if (
dynamic_cast<const BooleanSetting*
>(&
setting)) {
623 void MSXMixer::changeRecordSetting(
const Setting&
setting)
625 for (
auto& info : infos) {
626 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
627 if (settings.record.get() == &
setting) {
628 info.device->recordChannel(
631 settings.record->getString()))));
639 void MSXMixer::changeMuteSetting(
const Setting&
setting)
641 for (
auto& info : infos) {
642 for (
auto&& [channel, settings] :
enumerate(info.channelSettings)) {
643 if (settings.mute.get() == &
setting) {
644 info.device->muteChannel(
645 unsigned(channel), settings.mute->getBoolean());
653 void MSXMixer::update(
const SpeedManager& ) noexcept
655 if (synchronousCounter == 0) {
656 setMixerParams(fragmentSize, hostSampleRate);
667 void MSXMixer::update(
const ThrottleManager& ) noexcept
673 void MSXMixer::updateVolumeParams(SoundDeviceInfo& info)
675 int mVolume = masterVolume.
getInt();
676 int dVolume = info.volumeSetting->getInt();
677 float volume = info.defaultVolume * mVolume * dVolume / (100.0f * 100.0f);
678 int balance = info.balanceSetting->getInt();
679 auto [l1, r1, l2, r2] = [&] {
680 if (info.device->isStereo()) {
682 float b = (balance + 100.0f) / 100.0f;
686 volume * sqrtf(
std::max(0.0f, 1.0f - b)),
690 float b = balance / 100.0f;
692 volume * sqrtf(
std::max(0.0f, 1.0f - b)),
701 float b = (balance + 100.0f) / 200.0f;
703 volume * sqrtf(
std::max(0.0f, 1.0f - b)),
710 auto [ampL, ampR] = info.device->getAmplificationFactor();
711 info.left1 = l1 * ampL;
712 info.right1 = r1 * ampR;
713 info.left2 = l2 * ampL;
714 info.right2 = r2 * ampR;
717 void MSXMixer::updateMasterVolume()
719 for (
auto& p : infos) {
720 updateVolumeParams(p);
726 auto it =
find_unguarded(infos, &device, &SoundDeviceInfo::device);
727 updateVolumeParams(*it);
730 void MSXMixer::executeUntil(EmuTime::param time)
751 [](
auto& i) {
return i.device->getName(); });
752 return (it !=
end(infos)) ? it->device :
nullptr;
755 MSXMixer::SoundDeviceInfoTopic::SoundDeviceInfoTopic(
757 :
InfoTopic(machineInfoCommand,
"sounddevice")
761 void MSXMixer::SoundDeviceInfoTopic::execute(
764 auto& msxMixer =
OUTER(MSXMixer, soundDeviceInfo);
765 switch (tokens.
size()) {
769 [](
auto& info) { return info.device->getName(); }));
772 SoundDevice* device = msxMixer.findDevice(tokens[2].getString());
774 throw CommandException(
"Unknown sound device");
776 result = device->getDescription();
780 throw CommandException(
"Too many parameters");
786 return "Shows a list of available sound devices.\n";
789 void MSXMixer::SoundDeviceInfoTopic::tabCompletion(std::vector<std::string>& tokens)
const
791 if (tokens.size() == 3) {
793 OUTER(MSXMixer, soundDeviceInfo).infos,
794 [](
auto& info) -> std::string_view {
return info.device->getName(); }));
void addWave(unsigned num, float *data)
virtual void update(UpdateType type, std::string_view name, std::string_view value)=0
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
CliComm & getCliComm() override
void mute()
TODO This methods (un)mute the sound.
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 uploadBuffer(MSXMixer &msxMixer, float *buffer, unsigned len)
Upload new sample data.
void unregisterMixer(MSXMixer &mixer)
Unregister per-machine mixer.
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().
virtual void setOutputRate(unsigned hostSampleRate, double speed)=0
When a SoundDevice registers itself with the Mixer, the Mixer sets the required sampleRate through th...
const std::string & getName() const
Get the unique name that identifies this sound device.
double getSpeed() const
Return the desired ratio between emutime and real time.
void detach(Observer< T > &observer)
void attach(Observer< T > &observer)
constexpr index_type size() const noexcept
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
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:
constexpr KeyMatrixPosition x
Keyboard bindings.
bool any_of(InputRange &&range, UnaryPredicate pred)
auto find(InputRange &&range, const T &value)
size_t size(std::string_view utf8)
constexpr auto transform(Range &&range, UnaryOp op)
#define OUTER(type, member)
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)
#define VLA_SSE_ALIGNED(TYPE, NAME, LENGTH)
constexpr auto end(const zstring_view &x)