diff --git a/Makefile b/Makefile index 78772eb..4513b41 100755 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ CXXFLAGS=-Wall -I .. -I . -I apple -I sdl -I/usr/local/include/SDL2 -O3 -g TSRC=cpu.cpp util/testharness.cpp -COMMONOBJS=cpu.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o RingBuffer.o globals.o apple/parallelcard.o apple/fx80.o apple/mockingboard.o apple/sy6522.o apple/ay8910.o +COMMONOBJS=cpu.o apple/appledisplay.o apple/applekeyboard.o apple/applemmu.o apple/applevm.o apple/diskii.o apple/nibutil.o RingBuffer.o globals.o apple/parallelcard.o apple/fx80.o apple/mockingboard.o apple/sy6522.o apple/ay8910.o lcg.o SDLOBJS=sdl/sdl-speaker.o sdl/sdl-display.o sdl/sdl-keyboard.o sdl/sdl-paddles.o sdl/sdl-filemanager.o sdl/aiie.o sdl/sdl-printer.o diff --git a/apple/ay8910.cpp b/apple/ay8910.cpp index db874ac..0bdf521 100644 --- a/apple/ay8910.cpp +++ b/apple/ay8910.cpp @@ -43,7 +43,7 @@ enum { * */ -AY8910::AY8910() +AY8910::AY8910() : lcg(0) { Reset(); } @@ -60,6 +60,8 @@ void AY8910::Reset() envCounter = 0; envelopeTimer = envelopeTime = 0; envDirection = 1; + noiseFlipTimer = 0; + noiseFlag = true; } uint8_t AY8910::read(uint8_t reg) @@ -95,7 +97,10 @@ void AY8910::write(uint8_t reg, uint8_t PortA) case DWS: // bDir==1 && BC1 == 0 (DWS) // Write current PortA to PSG r[curRegister] = PortA; + if (curRegister <= CHAN_A_COARSE) { + // FIXME: for all of A/B/C changes, figure out how much time had + // elapsed on the previous timer and apply it to the new one cycleTime[0] = cycleTimeForPSG(0); waveformFlipTimer[0] = g_cpu->cycles + cycleTime[0]; } else if (curRegister <= CHAN_B_COARSE) { @@ -123,12 +128,15 @@ void AY8910::write(uint8_t reg, uint8_t PortA) cycleTime[2] = cycleTimeForPSG(2); waveformFlipTimer[2] = g_cpu->cycles + cycleTime[2]; } - } else if (curRegister >= ENV_PERIOD_FINE && curRegister <= ENV_SHAPE) { + } else if (curRegister >= ENV_PERIOD_FINE && curRegister <= ENV_PERIOD_COARSE) { // Envelope control -- period or shape // FIXME: should envCounter be initialized to the start position? - envDirection = (r[ENV_SHAPE] & AY_ENV_ATTACK) ? 1 : -1; envelopeTime = calculateEnvelopeTime(); envelopeTimer = 0; // reset so it will pick up @ next tick + } else if (curRegister == ENV_SHAPE) { + envDirection = (r[ENV_SHAPE] & AY_ENV_ATTACK) ? 1 : -1; + } else if (curRegister == NOISE_PERIOD) { + noiseFlipTimer = g_cpu->cycles + cycleTimeForNoise(); } return; case INTAK: // bDir==1 && BC1 == 1 (INTAK) @@ -163,6 +171,15 @@ uint16_t AY8910::cycleTimeForPSG(uint8_t psg) return (32 * regVal) / 4; } +uint16_t AY8910::cycleTimeForNoise() +{ + uint8_t regval = r[NOISE_PERIOD]; + if (regval == 0) regval++; + + return (16 * regval) / 4; +} + + // Similar calculation: this one, for the envelope timer. // FIXME: I *think* this is right. Not sure. Needs validation. uint32_t AY8910::calculateEnvelopeTime() @@ -193,6 +210,8 @@ void AY8910::update(uint32_t cpuCycleCount) if (envelopeTimer <= cpuCycleCount) { // time to update the envelopeCounter. + envCounter += envDirection; + switch (r[ENV_SHAPE]) { // Continue / Attack / Alternate / Hold bits case 0x00: // 0 / 0 / x / x -- descend once, stay @ bottom @@ -211,10 +230,9 @@ void AY8910::update(uint32_t cpuCycleCount) // In all these cases, we go from start to finish once. In all // cases except 0x0b and 0x0d, when we're done, we go low. - envCounter += envDirection; if (envDirection > 0) { - // We were ascending: did we hit 16? If so, stop & go terminal - if (envCounter == 16) { + // We were ascending: did we hit 15? If so, stop & go terminal + if (envCounter == 15) { envDirection = 0; // One ascending case (0x0b) goes high after; all others are low envCounter = (r[ENV_SHAPE] == 0x0b ? 0x0F : 0x00); @@ -227,14 +245,46 @@ void AY8910::update(uint32_t cpuCycleCount) envCounter = (r[ENV_SHAPE] == 0x0d ? 0x0F : 0x00); } } - } + break; + case 0x80: + case 0xC0: + // These two jump back to the start when they get to the end. + + if (envCounter > 15) { + envCounter = 0; + } else if (envCounter < 0) { + envCounter = 15; + } + break; + + case 0xA0: + case 0xE0: + break; + // These two reverse direction. + if (envCounter == 15 || envCounter == 0) { + envDirection = -envDirection; + } + break; + } + // Set up the envelope timer for the next transition // FIXME: can set this to 0 if envDirection is 0, but have to be careful about setup of timer again when envDirection is re-set envelopeTimer += envelopeTime; } } + // For the noise timer: if it expires, we get another random bit + if (noiseFlipTimer && noiseFlipTimer <= cpuCycleCount) { + // FIXME: srnd() this somewhere when we initialized? Does it matter? + uint8_t rnd = lcg.rnd(); + // Use the higher bits of the RNG; the low bits have low periodicity. + // FIXME: is this sophisticated enough? Does it get the noise + // profile we want? Should we ^ with some other bit to get a + // different period profile? + noiseFlag = (rnd & 0x80); + } + // For any waveformFlipTimer that is > 0: if cpuCycleCount is larger // than the timer, we'll flip state. (It's a square wave!) @@ -254,17 +304,34 @@ void AY8910::update(uint32_t cpuCycleCount) // Figure out what output comes from this channel and send it to // the speaker. The output is controlled by outputState[i] (from // the square wave, above); the amplitude control line for this - // output (r[i+8], below) and the tone/noise selection (FIXME: - // currently unimplemented). + // output (r[i+8], below) and the tone/noise selection. - uint8_t amplitude = r[i+8] & 0xF; - // ... and if bit 0x10 is on, it's controlled by the envelope counter. - if (r[i+8] & 0x10) - amplitude = envCounter; + uint8_t amplitude = 0; + // If we're trying to output "high" this square-wave cycle, and if + // the ToneEnable bit is set for this register, then generate + // output. + if (!(r[ENAB] & (1 << i)) && outputState[i]) { + amplitude = r[i+8] & 0xF; + // ... and if bit 0x10 is on, it's modified by the envelope counter. + if (r[i+8] & 0x10) + amplitude = envCounter; + } - g_speaker->mixOutput(outputState[i] ? volumeLevels[amplitude] : 0x00); + // test the NoiseEnable bit for this register + if (!(r[ENAB] & (1 << (3+i)))) { + // FIXME: if the noiseFlag is off, do we keep the tone value + // above? Or set to 0? + if (noiseFlag) { + amplitude = 7; // median "0-value" level (fixme?) + // ... and if bit 0x10 is on, it's modified by the envelope counter. + if (r[i+8] & 0x10) + amplitude = envCounter >> 1; + } else { + amplitude = 0; + } + } + g_speaker->mixOutput(volumeLevels[amplitude]); } - } diff --git a/apple/ay8910.h b/apple/ay8910.h index a19a1b5..bcedb13 100644 --- a/apple/ay8910.h +++ b/apple/ay8910.h @@ -2,6 +2,7 @@ #define __AY8910_H #include +#include "lcg.h" // Operations... enum { @@ -53,6 +54,7 @@ class AY8910 { protected: uint16_t cycleTimeForPSG(uint8_t psg); + uint16_t cycleTimeForNoise(); uint32_t calculateEnvelopeTime(); private: @@ -65,6 +67,10 @@ class AY8910 { int8_t envDirection; uint32_t envelopeTime; uint32_t envelopeTimer; + uint32_t noiseFlipTimer; + bool noiseFlag; + + LCG lcg; }; #endif diff --git a/lcg.cpp b/lcg.cpp new file mode 100644 index 0000000..1846cb5 --- /dev/null +++ b/lcg.cpp @@ -0,0 +1,33 @@ +#include "lcg.h" + +/* Dead simple 8-bit Linear Congruential Generator (PRNG). + * + * An LCG is defined as + * + * X(n+1) = ( a * X(n) + c ) % m + * + * In this implementation: + * a = 2^7 = 128 + * c = 251 + * m = 256 + * + * Like all LCGs, the low-order bits of this cycle quickly. The + * high-order bits have better (longer) periods. + */ + +LCG::LCG(uint16_t s) +{ + seed = s; +} + +void LCG::srnd(uint16_t s) +{ + seed = s; +} + +uint8_t LCG::rnd() +{ + seed = (seed << 7) - seed + 251; + + return (uint8_t)(seed + (seed>>8)); +} diff --git a/lcg.h b/lcg.h new file mode 100644 index 0000000..e4588d4 --- /dev/null +++ b/lcg.h @@ -0,0 +1,17 @@ +#ifndef __LCG_H +#define __LCG_H + +#include + +class LCG { + public: + LCG(uint16_t s); + + void srnd(uint16_t s); + uint8_t rnd(); + + private: + uint16_t seed; +}; + +#endif