openMSX
AY8910.cc
Go to the documentation of this file.
1/*
2 * Emulation of the AY-3-8910
3 *
4 * Original code taken from xmame-0.37b16.1
5 * Based on various code snippets by Ville Hallik, Michael Cuddy,
6 * Tatsuyuki Satoh, Fabrice Frances, Nicola Salmoria.
7 * Integrated into openMSX by ???.
8 * Refactored in C++ style by Maarten ter Huurne.
9 */
10
11#include "AY8910.hh"
12#include "AY8910Periphery.hh"
13#include "DeviceConfig.hh"
14#include "GlobalSettings.hh"
15#include "MSXException.hh"
16#include "Math.hh"
17#include "StringOp.hh"
18#include "serialize.hh"
19#include "cstd.hh"
20#include "narrow.hh"
21#include "one_of.hh"
22#include "outer.hh"
23#include "random.hh"
24#include "xrange.hh"
25#include <array>
26#include <cassert>
27#include <iostream>
28
29namespace openmsx {
30
31// The step clock for the tone and noise generators is the chip clock
32// divided by 8; for the envelope generator of the AY-3-8910, it is half
33// that much (clock/16).
34static constexpr float NATIVE_FREQ_FLOAT = (3579545.0f / 2) / 8;
35static constexpr int NATIVE_FREQ_INT = int(cstd::round(NATIVE_FREQ_FLOAT));
36
37static constexpr int PORT_A_DIRECTION = 0x40;
38static constexpr int PORT_B_DIRECTION = 0x80;
39
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;
56
57// Calculate the volume->voltage conversion table. The AY-3-8910 has 16 levels,
58// in a logarithmic scale (3dB per step). YM2149 has 32 levels, the 16 extra
59// levels are only used for envelope volumes
60static constexpr auto YM2149EnvelopeTab = [] {
61 std::array<float, 32> result = {};
62 double out = 1.0;
63 double factor = cstd::pow<5, 3>(0.5, 0.25); // 1/sqrt(sqrt(2)) ~= 1/(1.5dB)
64 for (int i = 31; i > 0; --i) {
65 result[i] = float(out);
66 out *= factor;
67 }
68 result[0] = 0.0f;
69 return result;
70}();
71static constexpr auto AY8910EnvelopeTab = [] {
72 // only 16 envelope steps, duplicate every step
73 std::array<float, 32> result = {};
74 result[0] = 0.0f;
75 result[1] = 0.0f;
76 for (int i = 2; i < 32; i += 2) {
77 result[i + 0] = YM2149EnvelopeTab[i + 1];
78 result[i + 1] = YM2149EnvelopeTab[i + 1];
79 }
80 return result;
81}();
82static constexpr auto volumeTab = [] {
83 std::array<float, 16> result = {};
84 result[0] = 0.0f;
85 for (auto i : xrange(1, 16)) {
86 result[i] = YM2149EnvelopeTab[2 * i + 1];
87 }
88 return result;
89}();
90
91
92// 256 random floats, each in range [-1, 1).
93// (with entries 0..2 repeated at entries 256..258).
94static constexpr auto noiseTab = []{
95 std::array<float, 256 + 3> result = {};
96 // Seed doesn't matter. It's OK that these 'random' numbers are the same
97 // in each openMSX run.
99 for (int i = 0; i < 256; ++i) {
100 result[i] = 2.0f * getCanonicalFloat(pcg()) - 1.0f;
101 }
102 result[256] = result[0];
103 result[257] = result[1];
104 result[258] = result[2];
105 return result;
106}();
107
108static float noiseValue(float x)
109{
110 // cubic hermite spline interpolation
111 assert(0.0f <= x);
112 auto xi = int(x);
113 auto xf = x - narrow_cast<float>(xi);
114 xi &= 255;
115 return Math::cubicHermite(subspan<4>(noiseTab, xi), xf);
116}
117
118
119// Generator:
120
121inline void AY8910::Generator::reset()
122{
123 count = 0;
124}
125
126inline void AY8910::Generator::setPeriod(int value)
127{
128 // Careful studies of the chip output prove that it instead counts up from
129 // 0 until the counter becomes greater or equal to the period. This is an
130 // important difference when the program is rapidly changing the period to
131 // modulate the sound.
132 // Also, note that period = 0 is the same as period = 1. This is mentioned
133 // in the YM2203 data sheets. However, this does NOT apply to the Envelope
134 // period. In that case, period = 0 is half as period = 1.
135 period = std::max(1, value);
136 count = std::min(count, period - 1);
137}
138
139inline unsigned AY8910::Generator::getNextEventTime() const
140{
141 assert(count < period);
142 return period - count;
143}
144
145inline void AY8910::Generator::advanceFast(unsigned duration)
146{
147 count += narrow<int>(duration);
148 assert(count < period);
149}
150
151
152// ToneGenerator:
153
154AY8910::ToneGenerator::ToneGenerator()
155{
156 reset();
157}
158
159inline void AY8910::ToneGenerator::reset()
160{
161 Generator::reset();
162 output = false;
163}
164
165int AY8910::ToneGenerator::getDetune(const AY8910& ay8910)
166{
167 int result = 0;
168 if (float vibPerc = ay8910.vibratoPercent.getFloat();
169 vibPerc != 0.0f) {
170 auto vibratoPeriod = int(
171 NATIVE_FREQ_FLOAT /
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));
178 }
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));
188 }
189 return std::min(result, period - 1);
190}
191
192inline void AY8910::ToneGenerator::advance(unsigned duration)
193{
194 assert(count < period);
195 count += narrow<int>(duration);
196 if (count >= period) {
197 // Calculate number of output transitions.
198 int cycles = count / period;
199 count -= period * cycles; // equivalent to count %= period;
200 output ^= cycles & 1;
201 }
202}
203
204inline void AY8910::ToneGenerator::doNextEvent(const AY8910& ay8910)
205{
206 if (ay8910.doDetune) [[unlikely]] {
207 count = getDetune(ay8910);
208 } else {
209 count = 0;
210 }
211 output ^= 1;
212}
213
214
215// NoiseGenerator:
216
217AY8910::NoiseGenerator::NoiseGenerator()
218{
219 reset();
220}
221
222inline void AY8910::NoiseGenerator::reset()
223{
224 Generator::reset();
225 random = 1;
226}
227
228inline void AY8910::NoiseGenerator::doNextEvent()
229{
230 count = 0;
231
232 // The Random Number Generator of the 8910 is a 17-bit shift register.
233 // The input to the shift register is bit0 XOR bit3 (bit0 is the
234 // output). Verified on real AY8910 and YM2149 chips.
235 //
236 // Fibonacci configuration:
237 // random ^= ((random & 1) ^ ((random >> 3) & 1)) << 17;
238 // random >>= 1;
239 // Galois configuration:
240 // if (random & 1) random ^= 0x24000;
241 // random >>= 1;
242 // or alternatively:
243 random = (random >> 1) ^ ((random & 1) << 13) ^ ((random & 1) << 16);
244}
245
246inline void AY8910::NoiseGenerator::advance(unsigned duration)
247{
248 assert(count < period);
249 count += narrow<int>(duration);
250 int cycles = count / period;
251 count -= cycles * period; // equivalent to count %= period
252
253 // The following loops advance the random state N steps at once. The
254 // values for N (4585, 275, 68, 8, 1) are chosen so that:
255 // - The formulas are relatively simple.
256 // - The ratio between the step sizes is roughly the same.
257 for (; cycles >= 4585; cycles -= 4585) {
258 random = ((random & 0x1f) << 12)
259 ^ ((random & 0x1f) << 9)
260 ^ random
261 ^ ( random >> 5);
262 }
263 for (; cycles >= 275; cycles -= 275) {
264 random = ((random & 0x03f) << 11)
265 ^ ((random & 0x1c0) << 8)
266 ^ ((random & 0x1ff) << 5)
267 ^ random
268 ^ ( random >> 6)
269 ^ ( random >> 9);
270 }
271 for (; cycles >= 68; cycles -= 68) {
272 random = ((random & 0xfff) << 5)
273 ^ ((random & 0xfff) << 2)
274 ^ random
275 ^ ( random >> 12);
276 }
277 for (; cycles >= 8; cycles -= 8) {
278 random = ((random & 0xff) << 9)
279 ^ ((random & 0xff) << 6)
280 ^ ( random >> 8);
281 }
282 for (; cycles >= 1; cycles -= 1) {
283 random = ((random & 1) << 16)
284 ^ ((random & 1) << 13)
285 ^ ( random >> 1);
286 }
287}
288
289
290// Amplitude:
291
292static bool checkAY8910(const DeviceConfig& config)
293{
294 auto type = config.getChildData("type", "ay8910");
296 if (cmp(type, "ay8910")) {
297 return true;
298 } else if (cmp(type, "ym2149")) {
299 return false;
300 }
301 throw FatalError("Unknown PSG type: ", type);
302}
303
304AY8910::Amplitude::Amplitude(const DeviceConfig& config)
305 : isAY8910(checkAY8910(config))
306 , envVolTable(isAY8910 ? AY8910EnvelopeTab : YM2149EnvelopeTab)
307{
308 vol[0] = vol[1] = vol[2] = 0.0f;
309 envChan[0] = false;
310 envChan[1] = false;
311 envChan[2] = false;
312
313 if (false) {
314 std::cout << "YM2149Envelope:";
315 for (const auto& e : YM2149EnvelopeTab) {
316 std::cout << ' ' << std::hexfloat << e;
317 }
318 std::cout << "\nAY8910Envelope:";
319 for (const auto& e : AY8910EnvelopeTab) {
320 std::cout << ' ' << std::hexfloat << e;
321 }
322 std::cout << "\nvolume:";
323 for (const auto& e : volumeTab) {
324 std::cout << ' ' << std::hexfloat << e;
325 }
326 std::cout << '\n';
327 }
328}
329
330inline float AY8910::Amplitude::getVolume(unsigned chan) const
331{
332 assert(!followsEnvelope(chan));
333 return vol[chan];
334}
335
336inline void AY8910::Amplitude::setChannelVolume(unsigned chan, unsigned value)
337{
338 envChan[chan] = (value & 0x10) != 0;
339 vol[chan] = volumeTab[value & 0x0F];
340}
341
342inline bool AY8910::Amplitude::followsEnvelope(unsigned chan) const
343{
344 return envChan[chan];
345}
346
347
348// Envelope:
349
350// AY8910 and YM2149 behave different here:
351// YM2149 envelope goes twice as fast and has twice as many levels. Here
352// we implement the YM2149 behaviour, but to get the AY8910 behaviour we
353// repeat every level twice in the envVolTable
354
355inline AY8910::Envelope::Envelope(std::span<const float, 32> envVolTable_)
356 : envVolTable(envVolTable_)
357{
358}
359
360inline void AY8910::Envelope::reset()
361{
362 count = 0;
363}
364
365inline void AY8910::Envelope::setPeriod(int value)
366{
367 // twice as fast as AY8910
368 // see also Generator::setPeriod()
369 period = std::max(1, 2 * value);
370 count = std::min(count, period - 1);
371}
372
373inline float AY8910::Envelope::getVolume() const
374{
375 return envVolTable[step ^ attack];
376}
377
378inline void AY8910::Envelope::setShape(unsigned shape)
379{
380 // do 32 steps for both AY8910 and YM2149
381 /*
382 envelope shapes:
383 C AtAlH
384 0 0 x x \___
385 0 1 x x /___
386 1 0 0 0 \\\\
387 1 0 0 1 \___
388 1 0 1 0 \/\/
389 1 0 1 1 \
390 1 1 0 0 ////
391 1 1 0 1 /
392 1 1 1 0 /\/\
393 1 1 1 1 /___
394 */
395 attack = (shape & 0x04) ? 0x1F : 0x00;
396 if ((shape & 0x08) == 0) {
397 // If Continue = 0, map the shape to the equivalent one
398 // which has Continue = 1.
399 hold = true;
400 alternate = attack != 0;
401 } else {
402 hold = (shape & 0x01) != 0;
403 alternate = (shape & 0x02) != 0;
404 }
405 count = 0;
406 step = 0x1F;
407 holding = false;
408}
409
410inline bool AY8910::Envelope::isChanging() const
411{
412 return !holding;
413}
414
415inline void AY8910::Envelope::doSteps(int steps)
416{
417 // For best performance callers should check upfront whether
418 // isChanging() == true
419 // Though we can't assert on it because the condition might change
420 // in the inner loop(s) of generateChannels().
421 //assert(!holding);
422
423 if (holding) return;
424 step -= steps;
425
426 // Check current envelope position.
427 if (step < 0) {
428 if (hold) {
429 if (alternate) attack ^= 0x1F;
430 holding = true;
431 step = 0;
432 } else {
433 // If step has looped an odd number of times
434 // (usually 1), invert the output.
435 if (alternate && (step & 0x10)) {
436 attack ^= 0x1F;
437 }
438 step &= 0x1F;
439 }
440 }
441}
442
443inline void AY8910::Envelope::advance(unsigned duration)
444{
445 assert(count < period);
446 count += narrow<int>(duration * 2);
447 if (count >= period) {
448 int steps = count / period;
449 count -= steps * period; // equivalent to count %= period;
450 doSteps(steps);
451 }
452}
453
454inline void AY8910::Envelope::doNextEvent()
455{
456 count = 0;
457 doSteps(period == 1 ? 2 : 1);
458}
459
460inline unsigned AY8910::Envelope::getNextEventTime() const
461{
462 assert(count < period);
463 return (period - count + 1) / 2;
464}
465
466inline void AY8910::Envelope::advanceFast(unsigned duration)
467{
468 count += narrow<int>(2 * duration);
469 assert(count < period);
470}
471
472
473
474// AY8910 main class:
475
476AY8910::AY8910(const std::string& name_, AY8910Periphery& periphery_,
477 const DeviceConfig& config, EmuTime::param time)
478 : ResampledSoundDevice(config.getMotherBoard(), name_, "PSG", 3, NATIVE_FREQ_INT, false)
479 , periphery(periphery_)
480 , debuggable(config.getMotherBoard(), getName())
481 , vibratoPercent(
482 config.getCommandController(), tmpStrCat(getName(), "_vibrato_percent"),
483 "controls strength of vibrato effect", 0.0, 0.0, 10.0)
484 , vibratoFrequency(
485 config.getCommandController(), tmpStrCat(getName(), "_vibrato_frequency"),
486 "frequency of vibrato effect in Hertz", 5, 1.0, 10.0)
487 , detunePercent(
488 config.getCommandController(), tmpStrCat(getName(), "_detune_percent"),
489 "controls strength of detune effect", 0.0, 0.0, 10.0)
490 , detuneFrequency(
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())
495 , amplitude(config)
496 , envelope(amplitude.getEnvVolTable())
497 , isAY8910(checkAY8910(config))
498 , ignorePortDirections(config.getChildDataAsBool("ignorePortDirections", true))
499{
500 update(vibratoPercent);
501
502 // make valgrind happy
503 ranges::fill(regs, 0);
504
505 reset(time);
506 registerSound(config);
507
508 // only attach once all initialization is successful
509 vibratoPercent.attach(*this);
510 detunePercent .attach(*this);
511}
512
514{
515 vibratoPercent.detach(*this);
516 detunePercent .detach(*this);
517
519}
520
521void AY8910::reset(EmuTime::param time)
522{
523 // Reset generators and envelope.
524 for (auto& t : tone) t.reset();
525 noise.reset();
526 envelope.reset();
527 // Reset registers and values derived from them.
528 for (auto reg : xrange(16)) {
529 wrtReg(reg, 0, time);
530 }
531}
532
533
534uint8_t AY8910::readRegister(unsigned reg, EmuTime::param time)
535{
536 if (reg >= 16) return 255;
537 switch (reg) {
538 case AY_PORTA:
539 if (!(regs[AY_ENABLE] & PORT_A_DIRECTION)) { // input
540 regs[reg] = periphery.readA(time);
541 }
542 break;
543 case AY_PORTB:
544 if (!(regs[AY_ENABLE] & PORT_B_DIRECTION)) { // input
545 regs[reg] = periphery.readB(time);
546 }
547 break;
548 }
549
550 // TODO some AY8910 models have 1F as mask for registers 1, 3, 5
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
554 };
555 return isAY8910 ? regs[reg] & regMask[reg]
556 : regs[reg];
557}
558
559uint8_t AY8910::peekRegister(unsigned reg, EmuTime::param time) const
560{
561 if (reg >= 16) return 255;
562 switch (reg) {
563 case AY_PORTA:
564 if (!(regs[AY_ENABLE] & PORT_A_DIRECTION)) { // input
565 return periphery.readA(time);
566 }
567 break;
568 case AY_PORTB:
569 if (!(regs[AY_ENABLE] & PORT_B_DIRECTION)) { // input
570 return periphery.readB(time);
571 }
572 break;
573 }
574 return regs[reg];
575}
576
577
578void AY8910::writeRegister(unsigned reg, uint8_t value, EmuTime::param time)
579{
580 if (reg >= 16) return;
581 if ((reg < AY_PORTA) && (reg == AY_ESHAPE || regs[reg] != value)) {
582 // Update the output buffer before changing the register.
583 updateStream(time);
584 }
585 wrtReg(reg, value, time);
586}
587void AY8910::wrtReg(unsigned reg, uint8_t value, EmuTime::param time)
588{
589 // Warn/force port directions
590 if (reg == AY_ENABLE) {
591 if (value & PORT_A_DIRECTION) {
592 directionsCallback.execute();
593 }
594 if (ignorePortDirections)
595 {
596 // portA -> input
597 // portB -> output
598 value = (value & ~PORT_A_DIRECTION) | PORT_B_DIRECTION;
599 }
600 }
601
602 // Note: unused bits are stored as well; they can be read back.
603 uint8_t diff = regs[reg] ^ value;
604 regs[reg] = value;
605
606 switch (reg) {
607 case AY_AFINE:
608 case AY_ACOARSE:
609 case AY_BFINE:
610 case AY_BCOARSE:
611 case AY_CFINE:
612 case AY_CCOARSE:
613 tone[reg / 2].setPeriod(regs[reg & ~1] + 256 * (regs[reg | 1] & 0x0F));
614 break;
615 case AY_NOISEPER:
616 // Half the frequency of tone generation.
617 //
618 // Verified on turboR GT: value=0 and value=1 sound the same.
619 //
620 // Likely in real AY8910 this is implemented by driving the
621 // noise generator at halve the frequency instead of
622 // multiplying the value by 2 (hence the correction for value=0
623 // here). But the effect is the same(?).
624 noise.setPeriod(2 * std::max(1, value & 0x1F));
625 break;
626 case AY_AVOL:
627 case AY_BVOL:
628 case AY_CVOL:
629 amplitude.setChannelVolume(reg - AY_AVOL, value);
630 break;
631 case AY_EFINE:
632 case AY_ECOARSE:
633 // also half the frequency of tone generation, but handled
634 // inside Envelope::setPeriod()
635 envelope.setPeriod(regs[AY_EFINE] + 256 * regs[AY_ECOARSE]);
636 break;
637 case AY_ESHAPE:
638 envelope.setShape(value);
639 break;
640 case AY_ENABLE:
641 if (diff & PORT_A_DIRECTION) {
642 // port A changed
643 if (value & PORT_A_DIRECTION) {
644 // from input to output
645 periphery.writeA(regs[AY_PORTA], time);
646 } else {
647 // from output to input
648 periphery.writeA(0xff, time);
649 }
650 }
651 if (diff & PORT_B_DIRECTION) {
652 // port B changed
653 if (value & PORT_B_DIRECTION) {
654 // from input to output
655 periphery.writeB(regs[AY_PORTB], time);
656 } else {
657 // from output to input
658 periphery.writeB(0xff, time);
659 }
660 }
661 break;
662 case AY_PORTA:
663 if (regs[AY_ENABLE] & PORT_A_DIRECTION) { // output
664 periphery.writeA(value, time);
665 }
666 break;
667 case AY_PORTB:
668 if (regs[AY_ENABLE] & PORT_B_DIRECTION) { // output
669 periphery.writeB(value, time);
670 }
671 break;
672 }
673}
674
675[[nodiscard]] static inline float calc(bool b, float f)
676{
677 // Calculates:
678 // return b ? f : 0.0f;
679 // Though this generates branchless code, might be faster
680 return narrow<float>(b) * f;
681}
682[[nodiscard]] static inline float calc(bool b1, bool b2, float f)
683{
684 return narrow<float>(b1 && b2) * f;
685}
686
687void AY8910::generateChannels(std::span<float*> bufs, unsigned num)
688{
689 // Disable channels with volume 0: since the sample value doesn't matter,
690 // we can use the fastest path.
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;
701 }
702 }
703 // Noise disabled on all channels?
704 if ((chanEnable & 0x38) == 0x38) {
705 noise.advance(num);
706 }
707
708 // Calculate samples.
709 // The 8910 has three outputs, each output is the mix of one of the
710 // three tone generators and of the (single) noise generator. The two
711 // are mixed BEFORE going into the DAC. The formula to mix each channel
712 // is:
713 // (ToneOn | ToneDisable) & (NoiseOn | NoiseDisable),
714 // where ToneOn and NoiseOn are the current generator state
715 // and ToneDisable and NoiseDisable come from the enable reg.
716 // Note that this means that if both tone and noise are disabled, the
717 // output is 1, not 0, and can be modulated by changing the volume.
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];
723 if (!buf) continue;
724 ToneGenerator& t = tone[chan];
725 if (envelope.isChanging() && amplitude.followsEnvelope(chan)) {
726 envelopeUpdated = true;
727 envelope = initialEnvelope;
728 if ((chanEnable & 0x09) == 0x08) {
729 // no noise, square wave: alternating between 0 and 1.
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)) {
735 if (nextT < nextE) {
736 addFill(buf, val, nextT);
737 remaining -= nextT;
738 nextE -= nextT;
739 envelope.advanceFast(nextT);
740 t.doNextEvent(*this);
741 nextT = t.getNextEventTime();
742 } else if (nextE < nextT) {
743 addFill(buf, val, nextE);
744 remaining -= nextE;
745 nextT -= nextE;
746 t.advanceFast(nextE);
747 envelope.doNextEvent();
748 nextE = envelope.getNextEventTime();
749 } else {
750 assert(nextT == nextE);
751 addFill(buf, val, nextT);
752 remaining -= nextT;
753 t.doNextEvent(*this);
754 nextT = t.getNextEventTime();
755 envelope.doNextEvent();
756 nextE = envelope.getNextEventTime();
757 }
758 val = calc(t.getOutput(), envelope.getVolume());
759 }
760 if (remaining) {
761 // last interval (without events)
762 addFill(buf, val, remaining);
763 t.advanceFast(remaining);
764 envelope.advanceFast(remaining);
765 }
766
767 } else if ((chanEnable & 0x09) == 0x09) {
768 // no noise, channel disabled: always 1.
769 auto val = envelope.getVolume();
770 unsigned remaining = num;
771 unsigned next = envelope.getNextEventTime();
772 while (next <= remaining) {
773 addFill(buf, val, next);
774 remaining -= next;
775 envelope.doNextEvent();
776 val = envelope.getVolume();
777 next = envelope.getNextEventTime();
778 }
779 if (remaining) {
780 // last interval (without events)
781 addFill(buf, val, remaining);
782 envelope.advanceFast(remaining);
783 }
784 t.advance(num);
785
786 } else if ((chanEnable & 0x09) == 0x00) {
787 // noise enabled, tone enabled
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) {
796 addFill(buf, val, next);
797 remaining -= next;
798 nextT -= next;
799 nextN -= next;
800 nextE -= next;
801 if (nextT) {
802 t.advanceFast(next);
803 } else {
804 t.doNextEvent(*this);
805 nextT = t.getNextEventTime();
806 }
807 if (nextN) {
808 noise.advanceFast(next);
809 } else {
810 noise.doNextEvent();
811 nextN = noise.getNextEventTime();
812 }
813 if (nextE) {
814 envelope.advanceFast(next);
815 } else {
816 envelope.doNextEvent();
817 nextE = envelope.getNextEventTime();
818 }
819 next = std::min(std::min(nextT, nextN), nextE);
820 val = calc(noise.getOutput(), t.getOutput(), envelope.getVolume());
821 }
822 if (remaining) {
823 // last interval (without events)
824 addFill(buf, val, remaining);
825 t.advanceFast(remaining);
826 noise.advanceFast(remaining);
827 envelope.advanceFast(remaining);
828 }
829
830 } else {
831 // noise enabled, tone disabled
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)) {
838 if (nextN < nextE) {
839 addFill(buf, val, nextN);
840 remaining -= nextN;
841 nextE -= nextN;
842 envelope.advanceFast(nextN);
843 noise.doNextEvent();
844 nextN = noise.getNextEventTime();
845 } else if (nextE < nextN) {
846 addFill(buf, val, nextE);
847 remaining -= nextE;
848 nextN -= nextE;
849 noise.advanceFast(nextE);
850 envelope.doNextEvent();
851 nextE = envelope.getNextEventTime();
852 } else {
853 assert(nextN == nextE);
854 addFill(buf, val, nextN);
855 remaining -= nextN;
856 noise.doNextEvent();
857 nextN = noise.getNextEventTime();
858 envelope.doNextEvent();
859 nextE = envelope.getNextEventTime();
860 }
861 val = calc(noise.getOutput(), envelope.getVolume());
862 }
863 if (remaining) {
864 // last interval (without events)
865 addFill(buf, val, remaining);
866 noise.advanceFast(remaining);
867 envelope.advanceFast(remaining);
868 }
869 t.advance(num);
870 }
871 } else {
872 // no (changing) envelope on this channel
873 auto volume = amplitude.followsEnvelope(chan)
874 ? envelope.getVolume()
875 : amplitude.getVolume(chan);
876 if ((chanEnable & 0x09) == 0x08) {
877 // no noise, square wave: alternating between 0 and 1.
878 auto val = calc(t.getOutput(), volume);
879 unsigned remaining = num;
880 unsigned next = t.getNextEventTime();
881 while (next <= remaining) {
882 addFill(buf, val, next);
883 val = volume - val;
884 remaining -= next;
885 t.doNextEvent(*this);
886 next = t.getNextEventTime();
887 }
888 if (remaining) {
889 // last interval (without events)
890 addFill(buf, val, remaining);
891 t.advanceFast(remaining);
892 }
893
894 } else if ((chanEnable & 0x09) == 0x09) {
895 // no noise, channel disabled: always 1.
896 addFill(buf, volume, num);
897 t.advance(num);
898
899 } else if ((chanEnable & 0x09) == 0x00) {
900 // noise enabled, tone enabled
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)) {
908 if (nextT < nextN) {
909 addFill(buf, val2, nextT);
910 remaining -= nextT;
911 nextN -= nextT;
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) {
918 addFill(buf, val2, nextN);
919 remaining -= nextN;
920 nextT -= nextN;
921 t.advanceFast(nextN);
922 noise.doNextEvent();
923 nextN = noise.getNextEventTime();
924 val2 = calc(noise.getOutput(), val1);
925 } else {
926 assert(nextT == nextN);
927 addFill(buf, val2, nextT);
928 remaining -= nextT;
929 t.doNextEvent(*this);
930 nextT = t.getNextEventTime();
931 noise.doNextEvent();
932 nextN = noise.getNextEventTime();
933 val1 = volume - val1;
934 val2 = calc(noise.getOutput(), val1);
935 }
936 }
937 if (remaining) {
938 // last interval (without events)
939 addFill(buf, val2, remaining);
940 t.advanceFast(remaining);
941 noise.advanceFast(remaining);
942 }
943
944 } else {
945 // noise enabled, tone disabled
946 noise = initialNoise;
947 unsigned remaining = num;
948 auto val = calc(noise.getOutput(), volume);
949 unsigned next = noise.getNextEventTime();
950 while (next <= remaining) {
951 addFill(buf, val, next);
952 remaining -= next;
953 noise.doNextEvent();
954 val = calc(noise.getOutput(), volume);
955 next = noise.getNextEventTime();
956 }
957 if (remaining) {
958 // last interval (without events)
959 addFill(buf, val, remaining);
960 noise.advanceFast(remaining);
961 }
962 t.advance(num);
963 }
964 }
965 }
966
967 // Envelope not yet updated?
968 if (envelope.isChanging() && !envelopeUpdated) {
969 envelope.advance(num);
970 }
971}
972
973float AY8910::getAmplificationFactorImpl() const
974{
975 return 1.0f;
976}
977
978void AY8910::update(const Setting& setting) noexcept
979{
980 if (&setting == one_of(&vibratoPercent, &detunePercent)) {
981 doDetune = (vibratoPercent.getFloat() != 0.0f) ||
982 (detunePercent .getFloat() != 0.0f);
983 } else {
985 }
986}
987
988
989// Debuggable
990
991AY8910::Debuggable::Debuggable(MSXMotherBoard& motherBoard_, const std::string& name_)
992 : SimpleDebuggable(motherBoard_, name_ + " regs", "PSG", 0x10)
993{
994}
995
996uint8_t AY8910::Debuggable::read(unsigned address, EmuTime::param time)
997{
998 auto& ay8910 = OUTER(AY8910, debuggable);
999 return ay8910.readRegister(address, time);
1000}
1001
1002void AY8910::Debuggable::write(unsigned address, uint8_t value, EmuTime::param time)
1003{
1004 auto& ay8910 = OUTER(AY8910, debuggable);
1005 return ay8910.writeRegister(address, value, time);
1006}
1007
1008
1009template<typename Archive>
1010void AY8910::serialize(Archive& ar, unsigned /*version*/)
1011{
1012 ar.serialize("toneGenerators", tone,
1013 "noiseGenerator", noise,
1014 "envelope", envelope,
1015 "registers", regs);
1016
1017 // amplitude
1018 if constexpr (Archive::IS_LOADER) {
1019 for (auto i : xrange(3)) {
1020 amplitude.setChannelVolume(i, regs[i + AY_AVOL]);
1021 }
1022 }
1023}
1025
1026// version 1: initial version
1027// version 2: removed 'output' member variable
1028template<typename Archive>
1029void AY8910::Generator::serialize(Archive& ar, unsigned /*version*/)
1030{
1031 ar.serialize("period", period,
1032 "count", count);
1033}
1034INSTANTIATE_SERIALIZE_METHODS(AY8910::Generator);
1035
1036// version 1: initial version
1037// version 2: moved 'output' variable from base class to here
1038template<typename Archive>
1039void AY8910::ToneGenerator::serialize(Archive& ar, unsigned version)
1040{
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);
1046 } else {
1047 // don't bother trying to restore this from the old location:
1048 // it doesn't influence any MSX-observable state, and the
1049 // difference in generated sound will likely be inaudible
1050 }
1051}
1052INSTANTIATE_SERIALIZE_METHODS(AY8910::ToneGenerator);
1053
1054// version 1: initial version
1055// version 2: removed 'output' variable from base class, not stored here but
1056// instead it's calculated from 'random' when needed
1057template<typename Archive>
1058void AY8910::NoiseGenerator::serialize(Archive& ar, unsigned version)
1059{
1060 ar.template serializeInlinedBase<Generator>(*this, version);
1061 ar.serialize("random", random);
1062}
1063INSTANTIATE_SERIALIZE_METHODS(AY8910::NoiseGenerator);
1064
1065template<typename Archive>
1066void AY8910::Envelope::serialize(Archive& ar, unsigned /*version*/)
1067{
1068 ar.serialize("period", period,
1069 "count", count,
1070 "step", step,
1071 "attack", attack,
1072 "hold", hold,
1073 "alternate", alternate,
1074 "holding", holding);
1075}
1076INSTANTIATE_SERIALIZE_METHODS(AY8910::Envelope);
1077
1078} // namespace openmsx
BaseSetting * setting
TclObject t
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.
Definition AY8910.hh:22
uint8_t readRegister(unsigned reg, EmuTime::param time)
Definition AY8910.cc:534
void reset(EmuTime::param time)
Definition AY8910.cc:521
void writeRegister(unsigned reg, uint8_t value, EmuTime::param time)
Definition AY8910.cc:578
uint8_t peekRegister(unsigned reg, EmuTime::param time) const
Definition AY8910.cc:559
void serialize(Archive &ar, unsigned version)
Definition AY8910.cc:1010
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)
Definition Subject.hh:60
void attach(Observer< T > &observer)
Definition Subject.hh:54
TclObject execute() const
ALWAYS_INLINE unsigned count(const uint8_t *pIn, const uint8_t *pMatch, const uint8_t *pInLimit)
Definition lz4.cc:146
constexpr double pi
Definition Math.hh:24
constexpr float cubicHermite(std::span< const float, 4 > y, float x)
Definition Math.hh:167
constexpr double e
Definition Math.hh:21
constexpr double round(double x)
Definition cstd.hh:247
This file implemented 3 utility functions:
Definition Autofire.cc:11
constexpr void fill(ForwardRange &&range, const T &value)
Definition ranges.hh:307
uint32_t next(octet_iterator &it, octet_iterator end)
#define OUTER(type, member)
Definition outer.hh:42
constexpr float getCanonicalFloat(uint32_t u)
Definition random.hh:104
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
Definition random.hh:85
constexpr auto xrange(T e)
Definition xrange.hh:132