34static constexpr float NATIVE_FREQ_FLOAT = (3579545.0f / 2) / 8;
35static constexpr int NATIVE_FREQ_INT = int(
cstd::round(NATIVE_FREQ_FLOAT));
37static constexpr int PORT_A_DIRECTION = 0x40;
38static constexpr int PORT_B_DIRECTION = 0x80;
50static constexpr auto YM2149EnvelopeTab = [] {
51 std::array<float, 32> result = {};
53 double factor = cstd::pow<5, 3>(0.5, 0.25);
54 for (
int i = 31; i > 0; --i) {
55 result[i] = float(out);
61static constexpr auto AY8910EnvelopeTab = [] {
63 std::array<float, 32> result = {};
66 for (
int i = 2; i < 32; i += 2) {
67 result[i + 0] = YM2149EnvelopeTab[i + 1];
68 result[i + 1] = YM2149EnvelopeTab[i + 1];
72static constexpr auto volumeTab = [] {
73 std::array<float, 16> result = {};
75 for (
auto i :
xrange(1, 16)) {
76 result[i] = YM2149EnvelopeTab[2 * i + 1];
83static std::array<float, 256 + 3> noiseTab;
85static void initDetune()
88 std::uniform_real_distribution<float> distribution(-1.0f, 1.0f);
90 for (
auto i :
xrange(256)) {
91 noiseTab[i] = distribution(generator);
93 noiseTab[256] = noiseTab[0];
94 noiseTab[257] = noiseTab[1];
95 noiseTab[258] = noiseTab[2];
97static float noiseValue(
float x)
102 float xf = x - narrow_cast<float>(xi);
110inline void AY8910::Generator::reset()
115inline void AY8910::Generator::setPeriod(
int value)
128inline unsigned AY8910::Generator::getNextEventTime()
const
130 assert(
count < period);
131 return period -
count;
134inline void AY8910::Generator::advanceFast(
unsigned duration)
136 count += narrow<int>(duration);
137 assert(
count < period);
143AY8910::ToneGenerator::ToneGenerator()
148inline void AY8910::ToneGenerator::reset()
154int AY8910::ToneGenerator::getDetune(
AY8910& ay8910)
157 float vibPerc = ay8910.vibratoPercent.getFloat();
158 if (vibPerc != 0.0f) {
159 int vibratoPeriod = int(
161 ay8910.vibratoFrequency.getFloat());
162 vibratoCount += period;
163 vibratoCount %= vibratoPeriod;
164 result += narrow_cast<int>(
165 sinf((
float(2 *
Math::pi) * narrow_cast<float>(vibratoCount)) / narrow_cast<float>(vibratoPeriod))
166 * vibPerc * 0.01f * narrow_cast<float>(period));
168 float detunePerc = ay8910.detunePercent.getFloat();
169 if (detunePerc != 0.0f) {
170 float detunePeriod = NATIVE_FREQ_FLOAT /
171 ay8910.detuneFrequency.getFloat();
172 detuneCount += period;
173 float noiseIdx = narrow_cast<float>(detuneCount) / detunePeriod;
174 float detuneNoise = noiseValue( noiseIdx)
175 + noiseValue(2.0f * noiseIdx) / 2.0f;
176 result += narrow_cast<int>(detuneNoise * detunePerc * 0.01f * narrow_cast<float>(period));
178 return std::min(result, period - 1);
183 assert(
count < period);
184 count += narrow<int>(duration);
185 if (
count >= period) {
187 int cycles =
count / period;
188 count -= period * cycles;
189 output ^= cycles & 1;
193inline void AY8910::ToneGenerator::doNextEvent(
AY8910& ay8910)
195 if (ay8910.doDetune) [[unlikely]] {
196 count = getDetune(ay8910);
206AY8910::NoiseGenerator::NoiseGenerator()
211inline void AY8910::NoiseGenerator::reset()
217inline void AY8910::NoiseGenerator::doNextEvent()
232 random = (random >> 1) ^ ((random & 1) << 13) ^ ((random & 1) << 16);
237 assert(
count < period);
238 count += narrow<int>(duration);
239 int cycles =
count / period;
240 count -= cycles * period;
246 for (; cycles >= 4585; cycles -= 4585) {
247 random = ((random & 0x1f) << 12)
248 ^ ((random & 0x1f) << 9)
252 for (; cycles >= 275; cycles -= 275) {
253 random = ((random & 0x03f) << 11)
254 ^ ((random & 0x1c0) << 8)
255 ^ ((random & 0x1ff) << 5)
260 for (; cycles >= 68; cycles -= 68) {
261 random = ((random & 0xfff) << 5)
262 ^ ((random & 0xfff) << 2)
266 for (; cycles >= 8; cycles -= 8) {
267 random = ((random & 0xff) << 9)
268 ^ ((random & 0xff) << 6)
271 for (; cycles >= 1; cycles -= 1) {
272 random = ((random & 1) << 16)
273 ^ ((random & 1) << 13)
281static bool checkAY8910(
const DeviceConfig& config)
283 auto type = config.getChildData(
"type",
"ay8910");
285 if (cmp(type,
"ay8910")) {
287 }
else if (cmp(type,
"ym2149")) {
290 throw FatalError(
"Unknown PSG type: ", type);
293AY8910::Amplitude::Amplitude(
const DeviceConfig& config)
294 : isAY8910(checkAY8910(config))
295 , envVolTable(isAY8910 ? AY8910EnvelopeTab : YM2149EnvelopeTab)
297 vol[0] = vol[1] = vol[2] = 0.0f;
303 std::cout <<
"YM2149Envelope:";
304 for (
const auto&
e : YM2149EnvelopeTab) {
305 std::cout <<
' ' << std::hexfloat <<
e;
307 std::cout <<
"\nAY8910Envelope:";
308 for (
const auto&
e : AY8910EnvelopeTab) {
309 std::cout <<
' ' << std::hexfloat <<
e;
311 std::cout <<
"\nvolume:";
312 for (
const auto&
e : volumeTab) {
313 std::cout <<
' ' << std::hexfloat <<
e;
319inline float AY8910::Amplitude::getVolume(
unsigned chan)
const
321 assert(!followsEnvelope(chan));
325inline void AY8910::Amplitude::setChannelVolume(
unsigned chan,
unsigned value)
327 envChan[chan] = (value & 0x10) != 0;
328 vol[chan] = volumeTab[value & 0x0F];
331inline bool AY8910::Amplitude::followsEnvelope(
unsigned chan)
const
333 return envChan[chan];
344inline AY8910::Envelope::Envelope(std::span<const float, 32> envVolTable_)
345 : envVolTable(envVolTable_)
349inline void AY8910::Envelope::reset()
354inline void AY8910::Envelope::setPeriod(
int value)
362inline float AY8910::Envelope::getVolume()
const
364 return envVolTable[step ^ attack];
367inline void AY8910::Envelope::setShape(
unsigned shape)
384 attack = (shape & 0x04) ? 0x1F : 0x00;
385 if ((shape & 0x08) == 0) {
389 alternate = attack != 0;
391 hold = (shape & 0x01) != 0;
392 alternate = (shape & 0x02) != 0;
399inline bool AY8910::Envelope::isChanging()
const
404inline void AY8910::Envelope::doSteps(
int steps)
418 if (alternate) attack ^= 0x1F;
424 if (alternate && (step & 0x10)) {
434 assert(
count < period);
435 count += narrow<int>(duration * 2);
436 if (
count >= period) {
437 int steps =
count / period;
438 count -= steps * period;
443inline void AY8910::Envelope::doNextEvent()
446 doSteps(period == 1 ? 2 : 1);
449inline unsigned AY8910::Envelope::getNextEventTime()
const
451 assert(
count < period);
452 return (period -
count + 1) / 2;
455inline void AY8910::Envelope::advanceFast(
unsigned duration)
457 count += narrow<int>(2 * duration);
458 assert(
count < period);
468 , periphery(periphery_)
469 , debuggable(config.getMotherBoard(),
getName())
472 "controls strength of vibrato effect", 0.0, 0.0, 10.0)
475 "frequency of vibrato effect in Hertz", 5, 1.0, 10.0)
478 "controls strength of detune effect", 0.0, 0.0, 10.0)
481 "frequency of detune effect in Hertz", 5.0, 1.0, 100.0)
482 , directionsCallback(
483 config.getGlobalSettings().getInvalidPsgDirectionsSetting())
485 , envelope(amplitude.getEnvVolTable())
486 , isAY8910(checkAY8910(config))
487 , ignorePortDirections(config.getChildDataAsBool(
"ignorePortDirections", true))
489 update(vibratoPercent);
498 vibratoPercent.
attach(*
this);
499 detunePercent .
attach(*
this);
504 vibratoPercent.
detach(*
this);
505 detunePercent .
detach(*
this);
513 for (
auto&
t : tone)
t.reset();
517 for (
auto reg :
xrange(16)) {
518 wrtReg(reg, 0, time);
525 if (reg >= 16)
return 255;
528 if (!(regs[
AY_ENABLE] & PORT_A_DIRECTION)) {
529 regs[reg] = periphery.
readA(time);
533 if (!(regs[
AY_ENABLE] & PORT_B_DIRECTION)) {
534 regs[reg] = periphery.
readB(time);
540 static constexpr std::array<uint8_t, 16> regMask = {
541 0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0xff,
542 0x1f, 0x1f ,0x1f, 0xff, 0xff, 0x0f, 0xff, 0xff
544 return isAY8910 ? regs[reg] & regMask[reg]
550 if (reg >= 16)
return 255;
553 if (!(regs[
AY_ENABLE] & PORT_A_DIRECTION)) {
554 return periphery.
readA(time);
558 if (!(regs[
AY_ENABLE] & PORT_B_DIRECTION)) {
559 return periphery.
readB(time);
569 if (reg >= 16)
return;
574 wrtReg(reg, value, time);
576void AY8910::wrtReg(
unsigned reg, uint8_t value, EmuTime::param time)
580 if (value & PORT_A_DIRECTION) {
583 if (ignorePortDirections)
587 value = (value & ~PORT_A_DIRECTION) | PORT_B_DIRECTION;
592 uint8_t diff = regs[reg] ^ value;
602 tone[reg / 2].setPeriod(regs[reg & ~1] + 256 * (regs[reg | 1] & 0x0F));
613 noise.setPeriod(2 *
std::max(1, value & 0x1F));
618 amplitude.setChannelVolume(reg -
AY_AVOL, value);
627 envelope.setShape(value);
630 if (diff & PORT_A_DIRECTION) {
632 if (value & PORT_A_DIRECTION) {
637 periphery.
writeA(0xff, time);
640 if (diff & PORT_B_DIRECTION) {
642 if (value & PORT_B_DIRECTION) {
647 periphery.
writeB(0xff, time);
652 if (regs[
AY_ENABLE] & PORT_A_DIRECTION) {
653 periphery.
writeA(value, time);
657 if (regs[
AY_ENABLE] & PORT_B_DIRECTION) {
658 periphery.
writeB(value, time);
664[[nodiscard]]
static inline float calc(
bool b,
float f)
669 return narrow<float>(b) * f;
671[[nodiscard]]
static inline float calc(
bool b1,
bool b2,
float f)
673 return narrow<float>(b1 * b2) * f;
676void AY8910::generateChannels(std::span<float*> bufs,
unsigned num)
681 for (
auto chan :
xrange(3)) {
682 if ((!amplitude.followsEnvelope(chan) &&
683 (amplitude.getVolume(chan) == 0.0f)) ||
684 (amplitude.followsEnvelope(chan) &&
685 !envelope.isChanging() &&
686 (envelope.getVolume() == 0.0f))) {
687 bufs[chan] =
nullptr;
688 tone[chan].advance(num);
689 chanEnable |= 0x09 << chan;
693 if ((chanEnable & 0x38) == 0x38) {
707 bool envelopeUpdated =
false;
708 Envelope initialEnvelope = envelope;
709 NoiseGenerator initialNoise = noise;
710 for (
unsigned chan = 0; chan < 3; ++chan, chanEnable >>= 1) {
711 auto* buf = bufs[chan];
713 ToneGenerator&
t = tone[chan];
714 if (envelope.isChanging() && amplitude.followsEnvelope(chan)) {
715 envelopeUpdated =
true;
716 envelope = initialEnvelope;
717 if ((chanEnable & 0x09) == 0x08) {
719 auto val = calc(
t.getOutput(), envelope.getVolume());
720 unsigned remaining = num;
721 unsigned nextE = envelope.getNextEventTime();
722 unsigned nextT =
t.getNextEventTime();
723 while ((nextT <= remaining) || (nextE <= remaining)) {
728 envelope.advanceFast(nextT);
729 t.doNextEvent(*
this);
730 nextT =
t.getNextEventTime();
731 }
else if (nextE < nextT) {
735 t.advanceFast(nextE);
736 envelope.doNextEvent();
737 nextE = envelope.getNextEventTime();
739 assert(nextT == nextE);
742 t.doNextEvent(*
this);
743 nextT =
t.getNextEventTime();
744 envelope.doNextEvent();
745 nextE = envelope.getNextEventTime();
747 val = calc(
t.getOutput(), envelope.getVolume());
752 t.advanceFast(remaining);
753 envelope.advanceFast(remaining);
756 }
else if ((chanEnable & 0x09) == 0x09) {
758 auto val = envelope.getVolume();
759 unsigned remaining = num;
760 unsigned next = envelope.getNextEventTime();
761 while (next <= remaining) {
764 envelope.doNextEvent();
765 val = envelope.getVolume();
766 next = envelope.getNextEventTime();
771 envelope.advanceFast(remaining);
775 }
else if ((chanEnable & 0x09) == 0x00) {
777 noise = initialNoise;
778 auto val = calc(noise.getOutput(),
t.getOutput(), envelope.getVolume());
779 unsigned remaining = num;
780 unsigned nextT =
t.getNextEventTime();
781 unsigned nextN = noise.getNextEventTime();
782 unsigned nextE = envelope.getNextEventTime();
784 while (next <= remaining) {
793 t.doNextEvent(*
this);
794 nextT =
t.getNextEventTime();
797 noise.advanceFast(next);
800 nextN = noise.getNextEventTime();
803 envelope.advanceFast(next);
805 envelope.doNextEvent();
806 nextE = envelope.getNextEventTime();
809 val = calc(noise.getOutput(),
t.getOutput(), envelope.getVolume());
814 t.advanceFast(remaining);
815 noise.advanceFast(remaining);
816 envelope.advanceFast(remaining);
821 noise = initialNoise;
822 auto val = calc(noise.getOutput(), envelope.getVolume());
823 unsigned remaining = num;
824 unsigned nextE = envelope.getNextEventTime();
825 unsigned nextN = noise.getNextEventTime();
826 while ((nextN <= remaining) || (nextE <= remaining)) {
831 envelope.advanceFast(nextN);
833 nextN = noise.getNextEventTime();
834 }
else if (nextE < nextN) {
838 noise.advanceFast(nextE);
839 envelope.doNextEvent();
840 nextE = envelope.getNextEventTime();
842 assert(nextN == nextE);
846 nextN = noise.getNextEventTime();
847 envelope.doNextEvent();
848 nextE = envelope.getNextEventTime();
850 val = calc(noise.getOutput(), envelope.getVolume());
855 noise.advanceFast(remaining);
856 envelope.advanceFast(remaining);
862 auto volume = amplitude.followsEnvelope(chan)
863 ? envelope.getVolume()
864 : amplitude.getVolume(chan);
865 if ((chanEnable & 0x09) == 0x08) {
867 auto val = calc(
t.getOutput(), volume);
868 unsigned remaining = num;
869 unsigned next =
t.getNextEventTime();
870 while (next <= remaining) {
874 t.doNextEvent(*
this);
875 next =
t.getNextEventTime();
880 t.advanceFast(remaining);
883 }
else if ((chanEnable & 0x09) == 0x09) {
888 }
else if ((chanEnable & 0x09) == 0x00) {
890 noise = initialNoise;
891 auto val1 = calc(
t.getOutput(), volume);
892 auto val2 = calc(noise.getOutput(), val1);
893 unsigned remaining = num;
894 unsigned nextN = noise.getNextEventTime();
895 unsigned nextT =
t.getNextEventTime();
896 while ((nextN <= remaining) || (nextT <= remaining)) {
901 noise.advanceFast(nextT);
902 t.doNextEvent(*
this);
903 nextT =
t.getNextEventTime();
904 val1 = volume - val1;
905 val2 = calc(noise.getOutput(), val1);
906 }
else if (nextN < nextT) {
910 t.advanceFast(nextN);
912 nextN = noise.getNextEventTime();
913 val2 = calc(noise.getOutput(), val1);
915 assert(nextT == nextN);
918 t.doNextEvent(*
this);
919 nextT =
t.getNextEventTime();
921 nextN = noise.getNextEventTime();
922 val1 = volume - val1;
923 val2 = calc(noise.getOutput(), val1);
929 t.advanceFast(remaining);
930 noise.advanceFast(remaining);
935 noise = initialNoise;
936 unsigned remaining = num;
937 auto val = calc(noise.getOutput(), volume);
938 unsigned next = noise.getNextEventTime();
939 while (next <= remaining) {
943 val = calc(noise.getOutput(), volume);
944 next = noise.getNextEventTime();
949 noise.advanceFast(remaining);
957 if (envelope.isChanging() && !envelopeUpdated) {
958 envelope.advance(num);
962float AY8910::getAmplificationFactorImpl()
const
967void AY8910::update(
const Setting&
setting)
noexcept
970 doDetune = (vibratoPercent.getFloat() != 0.0f) ||
971 (detunePercent .getFloat() != 0.0f);
972 if (doDetune && !detuneInitialized) {
973 detuneInitialized =
true;
984AY8910::Debuggable::Debuggable(MSXMotherBoard& motherBoard_,
const std::string& name_)
985 : SimpleDebuggable(motherBoard_, name_ +
" regs",
"PSG", 0x10)
989uint8_t AY8910::Debuggable::read(
unsigned address, EmuTime::param time)
992 return ay8910.readRegister(address, time);
995void AY8910::Debuggable::write(
unsigned address, uint8_t value, EmuTime::param time)
998 return ay8910.writeRegister(address, value, time);
1002template<
typename Archive>
1005 ar.serialize(
"toneGenerators", tone,
1006 "noiseGenerator", noise,
1007 "envelope", envelope,
1011 if constexpr (Archive::IS_LOADER) {
1012 for (
auto i :
xrange(3)) {
1013 amplitude.setChannelVolume(i, regs[i +
AY_AVOL]);
1021template<
typename Archive>
1024 ar.serialize(
"period", period,
1031template<
typename Archive>
1034 ar.template serializeInlinedBase<Generator>(*
this, version);
1035 ar.serialize(
"vibratoCount", vibratoCount,
1036 "detuneCount", detuneCount);
1037 if (ar.versionAtLeast(version, 2)) {
1038 ar.serialize(
"output", output);
1050template<
typename Archive>
1053 ar.template serializeInlinedBase<Generator>(*
this, version);
1054 ar.serialize(
"random", random);
1058template<
typename Archive>
1061 ar.serialize(
"period", period,
1066 "alternate", alternate,
1067 "holding", holding);
Models the general purpose I/O ports of the AY8910.
virtual void writeA(byte value, EmuTime::param time)
Writes to the peripheral on port A.
virtual byte readA(EmuTime::param time)
Reads the state of the peripheral on port A.
virtual byte readB(EmuTime::param time)
Similar to readA, but reads port B.
virtual void writeB(byte value, EmuTime::param time)
Similar to writeA, but writes port B.
This class implements the AY-3-8910 sound chip.
uint8_t readRegister(unsigned reg, EmuTime::param time)
void reset(EmuTime::param time)
void writeRegister(unsigned reg, uint8_t value, EmuTime::param time)
uint8_t peekRegister(unsigned reg, EmuTime::param time) const
void serialize(Archive &ar, unsigned version)
void update(const Setting &setting) noexcept override
void updateStream(EmuTime::param time)
static void addFill(float *&buffer, float value, unsigned num)
Adds a number of samples that all have the same value.
void unregisterSound()
Unregisters this sound device with the Mixer.
void registerSound(const DeviceConfig &config)
Registers this sound device with the Mixer.
void detach(Observer< T > &observer)
void attach(Observer< T > &observer)
TclObject execute() const
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
constexpr float cubicHermite(std::span< const float, 4 > y, float x)
constexpr double round(double x)
constexpr vecN< N, T > min(const vecN< N, T > &x, const vecN< N, T > &y)
constexpr vecN< N, T > max(const vecN< N, T > &x, const vecN< N, T > &y)
std::string getName(KeyCode keyCode)
Translate key code to key name.
This file implemented 3 utility functions:
void serialize(Archive &ar, T &t, unsigned version)
constexpr void fill(ForwardRange &&range, const T &value)
void advance(octet_iterator &it, distance_type n, octet_iterator end)
uint32_t next(octet_iterator &it, octet_iterator end)
#define OUTER(type, member)
auto & global_urng()
Return reference to a (shared) global random number generator.
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
TemporaryString tmpStrCat(Ts &&... ts)
constexpr auto xrange(T e)