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;
40static constexpr uint8_t AY_AFINE = 0;
41static constexpr uint8_t AY_ACOARSE = 1;
42static constexpr uint8_t AY_BFINE = 2;
43static constexpr uint8_t AY_BCOARSE = 3;
44static constexpr uint8_t AY_CFINE = 4;
45static constexpr uint8_t AY_CCOARSE = 5;
46static constexpr uint8_t AY_NOISEPER = 6;
47static constexpr uint8_t AY_ENABLE = 7;
48static constexpr uint8_t AY_AVOL = 8;
49static constexpr uint8_t AY_BVOL = 9;
50static constexpr uint8_t AY_CVOL = 10;
51static constexpr uint8_t AY_EFINE = 11;
52static constexpr uint8_t AY_ECOARSE = 12;
53static constexpr uint8_t AY_ESHAPE = 13;
54static constexpr uint8_t AY_PORTA = 14;
55static constexpr uint8_t AY_PORTB = 15;
60static constexpr auto YM2149EnvelopeTab = [] {
61 std::array<float, 32> result = {};
63 double factor = cstd::pow<5, 3>(0.5, 0.25);
64 for (
int i = 31; i > 0; --i) {
65 result[i] = float(out);
71static constexpr auto AY8910EnvelopeTab = [] {
73 std::array<float, 32> result = {};
76 for (
int i = 2; i < 32; i += 2) {
77 result[i + 0] = YM2149EnvelopeTab[i + 1];
78 result[i + 1] = YM2149EnvelopeTab[i + 1];
82static constexpr auto volumeTab = [] {
83 std::array<float, 16> result = {};
85 for (
auto i :
xrange(1, 16)) {
86 result[i] = YM2149EnvelopeTab[2 * i + 1];
94static constexpr auto noiseTab = []{
95 std::array<float, 256 + 3> result = {};
99 for (
int i = 0; i < 256; ++i) {
102 result[256] = result[0];
103 result[257] = result[1];
104 result[258] = result[2];
108static float noiseValue(
float x)
113 auto xf = x - narrow_cast<float>(xi);
121inline void AY8910::Generator::reset()
126inline void AY8910::Generator::setPeriod(
int value)
135 period = std::max(1, value);
136 count = std::min(count, period - 1);
139inline unsigned AY8910::Generator::getNextEventTime()
const
141 assert(count < period);
142 return period -
count;
145inline void AY8910::Generator::advanceFast(
unsigned duration)
147 count += narrow<int>(duration);
148 assert(count < period);
154AY8910::ToneGenerator::ToneGenerator()
159inline void AY8910::ToneGenerator::reset()
165int AY8910::ToneGenerator::getDetune(
const AY8910& ay8910)
168 if (
float vibPerc = ay8910.vibratoPercent.getFloat();
170 auto vibratoPeriod = int(
172 ay8910.vibratoFrequency.getFloat());
173 vibratoCount += period;
174 vibratoCount %= vibratoPeriod;
175 result += narrow_cast<int>(
176 sinf((
float(2 *
Math::pi) * narrow_cast<float>(vibratoCount)) / narrow_cast<float>(vibratoPeriod))
177 * vibPerc * 0.01f * narrow_cast<float>(period));
179 if (
float detunePerc = ay8910.detunePercent.getFloat();
180 detunePerc != 0.0f) {
181 float detunePeriod = NATIVE_FREQ_FLOAT /
182 ay8910.detuneFrequency.getFloat();
183 detuneCount += period;
184 float noiseIdx = narrow_cast<float>(detuneCount) / detunePeriod;
185 float detuneNoise = noiseValue( noiseIdx)
186 + noiseValue(2.0f * noiseIdx) * 0.5f;
187 result += narrow_cast<int>(detuneNoise * detunePerc * 0.01f * narrow_cast<float>(period));
189 return std::min(result, period - 1);
192inline void AY8910::ToneGenerator::advance(
unsigned duration)
194 assert(count < period);
195 count += narrow<int>(duration);
196 if (count >= period) {
198 int cycles =
count / period;
199 count -= period * cycles;
200 output ^= cycles & 1;
204inline void AY8910::ToneGenerator::doNextEvent(
const AY8910& ay8910)
206 if (ay8910.doDetune) [[unlikely]] {
207 count = getDetune(ay8910);
217AY8910::NoiseGenerator::NoiseGenerator()
222inline void AY8910::NoiseGenerator::reset()
228inline void AY8910::NoiseGenerator::doNextEvent()
243 random = (random >> 1) ^ ((random & 1) << 13) ^ ((random & 1) << 16);
246inline void AY8910::NoiseGenerator::advance(
unsigned duration)
248 assert(count < period);
249 count += narrow<int>(duration);
250 int cycles =
count / period;
251 count -= cycles * period;
257 for (; cycles >= 4585; cycles -= 4585) {
258 random = ((random & 0x1f) << 12)
259 ^ ((random & 0x1f) << 9)
263 for (; cycles >= 275; cycles -= 275) {
264 random = ((random & 0x03f) << 11)
265 ^ ((random & 0x1c0) << 8)
266 ^ ((random & 0x1ff) << 5)
271 for (; cycles >= 68; cycles -= 68) {
272 random = ((random & 0xfff) << 5)
273 ^ ((random & 0xfff) << 2)
277 for (; cycles >= 8; cycles -= 8) {
278 random = ((random & 0xff) << 9)
279 ^ ((random & 0xff) << 6)
282 for (; cycles >= 1; cycles -= 1) {
283 random = ((random & 1) << 16)
284 ^ ((random & 1) << 13)
292static bool checkAY8910(
const DeviceConfig& config)
294 auto type = config.getChildData(
"type",
"ay8910");
296 if (cmp(type,
"ay8910")) {
298 }
else if (cmp(type,
"ym2149")) {
301 throw FatalError(
"Unknown PSG type: ", type);
304AY8910::Amplitude::Amplitude(
const DeviceConfig& config)
305 : isAY8910(checkAY8910(config))
306 , envVolTable(isAY8910 ? AY8910EnvelopeTab : YM2149EnvelopeTab)
308 vol[0] = vol[1] = vol[2] = 0.0f;
314 std::cout <<
"YM2149Envelope:";
315 for (
const auto& e : YM2149EnvelopeTab) {
316 std::cout <<
' ' << std::hexfloat <<
e;
318 std::cout <<
"\nAY8910Envelope:";
319 for (
const auto& e : AY8910EnvelopeTab) {
320 std::cout <<
' ' << std::hexfloat <<
e;
322 std::cout <<
"\nvolume:";
323 for (
const auto& e : volumeTab) {
324 std::cout <<
' ' << std::hexfloat <<
e;
330inline float AY8910::Amplitude::getVolume(
unsigned chan)
const
332 assert(!followsEnvelope(chan));
336inline void AY8910::Amplitude::setChannelVolume(
unsigned chan,
unsigned value)
338 envChan[chan] = (value & 0x10) != 0;
339 vol[chan] = volumeTab[value & 0x0F];
342inline bool AY8910::Amplitude::followsEnvelope(
unsigned chan)
const
344 return envChan[chan];
355inline AY8910::Envelope::Envelope(std::span<const float, 32> envVolTable_)
356 : envVolTable(envVolTable_)
360inline void AY8910::Envelope::reset()
365inline void AY8910::Envelope::setPeriod(
int value)
369 period = std::max(1, 2 * value);
370 count = std::min(count, period - 1);
373inline float AY8910::Envelope::getVolume()
const
375 return envVolTable[step ^ attack];
378inline void AY8910::Envelope::setShape(
unsigned shape)
395 attack = (shape & 0x04) ? 0x1F : 0x00;
396 if ((shape & 0x08) == 0) {
400 alternate = attack != 0;
402 hold = (shape & 0x01) != 0;
403 alternate = (shape & 0x02) != 0;
410inline bool AY8910::Envelope::isChanging()
const
415inline void AY8910::Envelope::doSteps(
int steps)
429 if (alternate) attack ^= 0x1F;
435 if (alternate && (step & 0x10)) {
443inline void AY8910::Envelope::advance(
unsigned duration)
445 assert(count < period);
446 count += narrow<int>(duration * 2);
447 if (count >= period) {
448 int steps =
count / period;
449 count -= steps * period;
454inline void AY8910::Envelope::doNextEvent()
457 doSteps(period == 1 ? 2 : 1);
460inline unsigned AY8910::Envelope::getNextEventTime()
const
462 assert(count < period);
463 return (period - count + 1) / 2;
466inline void AY8910::Envelope::advanceFast(
unsigned duration)
468 count += narrow<int>(2 * duration);
469 assert(count < period);
479 , periphery(periphery_)
480 , debuggable(config.getMotherBoard(), getName())
482 config.getCommandController(),
tmpStrCat(getName(),
"_vibrato_percent"),
483 "controls strength of vibrato effect", 0.0, 0.0, 10.0)
485 config.getCommandController(),
tmpStrCat(getName(),
"_vibrato_frequency"),
486 "frequency of vibrato effect in Hertz", 5, 1.0, 10.0)
488 config.getCommandController(),
tmpStrCat(getName(),
"_detune_percent"),
489 "controls strength of detune effect", 0.0, 0.0, 10.0)
491 config.getCommandController(),
tmpStrCat(getName(),
"_detune_frequency"),
492 "frequency of detune effect in Hertz", 5.0, 1.0, 100.0)
493 , directionsCallback(
494 config.getGlobalSettings().getInvalidPsgDirectionsSetting())
496 , envelope(amplitude.getEnvVolTable())
497 , isAY8910(checkAY8910(config))
498 , ignorePortDirections(config.getChildDataAsBool(
"ignorePortDirections", true))
500 update(vibratoPercent);
509 vibratoPercent.
attach(*
this);
510 detunePercent .
attach(*
this);
515 vibratoPercent.
detach(*
this);
516 detunePercent .
detach(*
this);
524 for (
auto&
t : tone)
t.reset();
528 for (
auto reg :
xrange(16)) {
529 wrtReg(reg, 0, time);
536 if (reg >= 16)
return 255;
539 if (!(regs[AY_ENABLE] & PORT_A_DIRECTION)) {
540 regs[reg] = periphery.
readA(time);
544 if (!(regs[AY_ENABLE] & PORT_B_DIRECTION)) {
545 regs[reg] = periphery.
readB(time);
551 static constexpr std::array<uint8_t, 16> regMask = {
552 0xff, 0x0f, 0xff, 0x0f, 0xff, 0x0f, 0x1f, 0xff,
553 0x1f, 0x1f ,0x1f, 0xff, 0xff, 0x0f, 0xff, 0xff
555 return isAY8910 ? regs[reg] & regMask[reg]
561 if (reg >= 16)
return 255;
564 if (!(regs[AY_ENABLE] & PORT_A_DIRECTION)) {
565 return periphery.
readA(time);
569 if (!(regs[AY_ENABLE] & PORT_B_DIRECTION)) {
570 return periphery.
readB(time);
580 if (reg >= 16)
return;
581 if ((reg < AY_PORTA) && (reg == AY_ESHAPE || regs[reg] != value)) {
585 wrtReg(reg, value, time);
587void AY8910::wrtReg(
unsigned reg, uint8_t value, EmuTime::param time)
590 if (reg == AY_ENABLE) {
591 if (value & PORT_A_DIRECTION) {
594 if (ignorePortDirections)
598 value = (value & ~PORT_A_DIRECTION) | PORT_B_DIRECTION;
603 uint8_t diff = regs[reg] ^ value;
613 tone[reg / 2].setPeriod(regs[reg & ~1] + 256 * (regs[reg | 1] & 0x0F));
624 noise.setPeriod(2 * std::max(1, value & 0x1F));
629 amplitude.setChannelVolume(reg - AY_AVOL, value);
635 envelope.setPeriod(regs[AY_EFINE] + 256 * regs[AY_ECOARSE]);
638 envelope.setShape(value);
641 if (diff & PORT_A_DIRECTION) {
643 if (value & PORT_A_DIRECTION) {
645 periphery.
writeA(regs[AY_PORTA], time);
648 periphery.
writeA(0xff, time);
651 if (diff & PORT_B_DIRECTION) {
653 if (value & PORT_B_DIRECTION) {
655 periphery.
writeB(regs[AY_PORTB], time);
658 periphery.
writeB(0xff, time);
663 if (regs[AY_ENABLE] & PORT_A_DIRECTION) {
664 periphery.
writeA(value, time);
668 if (regs[AY_ENABLE] & PORT_B_DIRECTION) {
669 periphery.
writeB(value, time);
675[[nodiscard]]
static inline float calc(
bool b,
float f)
680 return narrow<float>(b) * f;
682[[nodiscard]]
static inline float calc(
bool b1,
bool b2,
float f)
684 return narrow<float>(b1 && b2) * f;
687void AY8910::generateChannels(std::span<float*> bufs,
unsigned num)
691 unsigned chanEnable = regs[AY_ENABLE];
692 for (
auto chan :
xrange(3)) {
693 if ((!amplitude.followsEnvelope(chan) &&
694 (amplitude.getVolume(chan) == 0.0f)) ||
695 (amplitude.followsEnvelope(chan) &&
696 !envelope.isChanging() &&
697 (envelope.getVolume() == 0.0f))) {
698 bufs[chan] =
nullptr;
699 tone[chan].advance(num);
700 chanEnable |= 0x09 << chan;
704 if ((chanEnable & 0x38) == 0x38) {
718 bool envelopeUpdated =
false;
719 Envelope initialEnvelope = envelope;
720 NoiseGenerator initialNoise = noise;
721 for (
unsigned chan = 0; chan < 3; ++chan, chanEnable >>= 1) {
722 auto* buf = bufs[chan];
724 ToneGenerator&
t = tone[chan];
725 if (envelope.isChanging() && amplitude.followsEnvelope(chan)) {
726 envelopeUpdated =
true;
727 envelope = initialEnvelope;
728 if ((chanEnable & 0x09) == 0x08) {
730 auto val = calc(
t.getOutput(), envelope.getVolume());
731 unsigned remaining = num;
732 unsigned nextE = envelope.getNextEventTime();
733 unsigned nextT =
t.getNextEventTime();
734 while ((nextT <= remaining) || (nextE <= remaining)) {
739 envelope.advanceFast(nextT);
740 t.doNextEvent(*
this);
741 nextT =
t.getNextEventTime();
742 }
else if (nextE < nextT) {
746 t.advanceFast(nextE);
747 envelope.doNextEvent();
748 nextE = envelope.getNextEventTime();
750 assert(nextT == nextE);
753 t.doNextEvent(*
this);
754 nextT =
t.getNextEventTime();
755 envelope.doNextEvent();
756 nextE = envelope.getNextEventTime();
758 val = calc(
t.getOutput(), envelope.getVolume());
763 t.advanceFast(remaining);
764 envelope.advanceFast(remaining);
767 }
else if ((chanEnable & 0x09) == 0x09) {
769 auto val = envelope.getVolume();
770 unsigned remaining = num;
771 unsigned next = envelope.getNextEventTime();
772 while (next <= remaining) {
775 envelope.doNextEvent();
776 val = envelope.getVolume();
777 next = envelope.getNextEventTime();
782 envelope.advanceFast(remaining);
786 }
else if ((chanEnable & 0x09) == 0x00) {
788 noise = initialNoise;
789 auto val = calc(noise.getOutput(),
t.getOutput(), envelope.getVolume());
790 unsigned remaining = num;
791 unsigned nextT =
t.getNextEventTime();
792 unsigned nextN = noise.getNextEventTime();
793 unsigned nextE = envelope.getNextEventTime();
794 unsigned next = std::min(std::min(nextT, nextN), nextE);
795 while (next <= remaining) {
804 t.doNextEvent(*
this);
805 nextT =
t.getNextEventTime();
808 noise.advanceFast(next);
811 nextN = noise.getNextEventTime();
814 envelope.advanceFast(next);
816 envelope.doNextEvent();
817 nextE = envelope.getNextEventTime();
819 next = std::min(std::min(nextT, nextN), nextE);
820 val = calc(noise.getOutput(),
t.getOutput(), envelope.getVolume());
825 t.advanceFast(remaining);
826 noise.advanceFast(remaining);
827 envelope.advanceFast(remaining);
832 noise = initialNoise;
833 auto val = calc(noise.getOutput(), envelope.getVolume());
834 unsigned remaining = num;
835 unsigned nextE = envelope.getNextEventTime();
836 unsigned nextN = noise.getNextEventTime();
837 while ((nextN <= remaining) || (nextE <= remaining)) {
842 envelope.advanceFast(nextN);
844 nextN = noise.getNextEventTime();
845 }
else if (nextE < nextN) {
849 noise.advanceFast(nextE);
850 envelope.doNextEvent();
851 nextE = envelope.getNextEventTime();
853 assert(nextN == nextE);
857 nextN = noise.getNextEventTime();
858 envelope.doNextEvent();
859 nextE = envelope.getNextEventTime();
861 val = calc(noise.getOutput(), envelope.getVolume());
866 noise.advanceFast(remaining);
867 envelope.advanceFast(remaining);
873 auto volume = amplitude.followsEnvelope(chan)
874 ? envelope.getVolume()
875 : amplitude.getVolume(chan);
876 if ((chanEnable & 0x09) == 0x08) {
878 auto val = calc(
t.getOutput(), volume);
879 unsigned remaining = num;
880 unsigned next =
t.getNextEventTime();
881 while (next <= remaining) {
885 t.doNextEvent(*
this);
886 next =
t.getNextEventTime();
891 t.advanceFast(remaining);
894 }
else if ((chanEnable & 0x09) == 0x09) {
899 }
else if ((chanEnable & 0x09) == 0x00) {
901 noise = initialNoise;
902 auto val1 = calc(
t.getOutput(), volume);
903 auto val2 = calc(noise.getOutput(), val1);
904 unsigned remaining = num;
905 unsigned nextN = noise.getNextEventTime();
906 unsigned nextT =
t.getNextEventTime();
907 while ((nextN <= remaining) || (nextT <= remaining)) {
912 noise.advanceFast(nextT);
913 t.doNextEvent(*
this);
914 nextT =
t.getNextEventTime();
915 val1 = volume - val1;
916 val2 = calc(noise.getOutput(), val1);
917 }
else if (nextN < nextT) {
921 t.advanceFast(nextN);
923 nextN = noise.getNextEventTime();
924 val2 = calc(noise.getOutput(), val1);
926 assert(nextT == nextN);
929 t.doNextEvent(*
this);
930 nextT =
t.getNextEventTime();
932 nextN = noise.getNextEventTime();
933 val1 = volume - val1;
934 val2 = calc(noise.getOutput(), val1);
940 t.advanceFast(remaining);
941 noise.advanceFast(remaining);
946 noise = initialNoise;
947 unsigned remaining = num;
948 auto val = calc(noise.getOutput(), volume);
949 unsigned next = noise.getNextEventTime();
950 while (next <= remaining) {
954 val = calc(noise.getOutput(), volume);
955 next = noise.getNextEventTime();
960 noise.advanceFast(remaining);
968 if (envelope.isChanging() && !envelopeUpdated) {
969 envelope.advance(num);
973float AY8910::getAmplificationFactorImpl()
const
978void AY8910::update(
const Setting&
setting)
noexcept
981 doDetune = (vibratoPercent.getFloat() != 0.0f) ||
982 (detunePercent .getFloat() != 0.0f);
992 : SimpleDebuggable(motherBoard_, name_ +
" regs",
"PSG", 0x10)
996uint8_t AY8910::Debuggable::read(
unsigned address, EmuTime::param time)
998 auto& ay8910 =
OUTER(AY8910, debuggable);
999 return ay8910.readRegister(address, time);
1002void AY8910::Debuggable::write(
unsigned address, uint8_t value, EmuTime::param time)
1004 auto& ay8910 =
OUTER(AY8910, debuggable);
1005 return ay8910.writeRegister(address, value, time);
1009template<
typename Archive>
1012 ar.serialize(
"toneGenerators", tone,
1013 "noiseGenerator", noise,
1014 "envelope", envelope,
1018 if constexpr (Archive::IS_LOADER) {
1019 for (
auto i :
xrange(3)) {
1020 amplitude.setChannelVolume(i, regs[i + AY_AVOL]);
1028template<
typename Archive>
1029void AY8910::Generator::serialize(Archive& ar,
unsigned )
1031 ar.serialize(
"period", period,
1038template<
typename Archive>
1039void AY8910::ToneGenerator::serialize(Archive& ar,
unsigned version)
1041 ar.template serializeInlinedBase<Generator>(*
this, version);
1042 ar.serialize(
"vibratoCount", vibratoCount,
1043 "detuneCount", detuneCount);
1044 if (ar.versionAtLeast(version, 2)) {
1045 ar.serialize(
"output", output);
1057template<
typename Archive>
1058void AY8910::NoiseGenerator::serialize(Archive& ar,
unsigned version)
1060 ar.template serializeInlinedBase<Generator>(*
this, version);
1061 ar.serialize(
"random", random);
1065template<
typename Archive>
1066void AY8910::Envelope::serialize(Archive& ar,
unsigned )
1068 ar.serialize(
"period", period,
1073 "alternate", alternate,
1074 "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)
This file implemented 3 utility functions:
constexpr void fill(ForwardRange &&range, const T &value)
uint32_t next(octet_iterator &it, octet_iterator end)
#define OUTER(type, member)
constexpr float getCanonicalFloat(uint32_t u)
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
TemporaryString tmpStrCat(Ts &&... ts)
constexpr auto xrange(T e)