added noise generator

This commit is contained in:
Jorj Bauer 2017-02-28 17:51:42 -05:00
parent 86decffbb1
commit f4dced45c8
5 changed files with 139 additions and 16 deletions

View File

@ -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

View File

@ -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,6 +245,27 @@ 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
@ -235,6 +274,17 @@ void AY8910::update(uint32_t cpuCycleCount)
}
}
// 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.
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]);
}
}

View File

@ -2,6 +2,7 @@
#define __AY8910_H
#include <stdint.h>
#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

33
lcg.cpp Normal file
View File

@ -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));
}

17
lcg.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef __LCG_H
#define __LCG_H
#include <stdint.h>
class LCG {
public:
LCG(uint16_t s);
void srnd(uint16_t s);
uint8_t rnd();
private:
uint16_t seed;
};
#endif