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) * 0.5f;
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())
471 config.getCommandController(),
tmpStrCat(getName(),
"_vibrato_percent"),
472 "controls strength of vibrato effect", 0.0, 0.0, 10.0)
474 config.getCommandController(),
tmpStrCat(getName(),
"_vibrato_frequency"),
475 "frequency of vibrato effect in Hertz", 5, 1.0, 10.0)
477 config.getCommandController(),
tmpStrCat(getName(),
"_detune_percent"),
478 "controls strength of detune effect", 0.0, 0.0, 10.0)
480 config.getCommandController(),
tmpStrCat(getName(),
"_detune_frequency"),
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)
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)