mirror of https://github.com/JorjBauer/aiie.git
added noise generator
This commit is contained in:
parent
86decffbb1
commit
f4dced45c8
2
Makefile
2
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
|
||||
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
Loading…
Reference in New Issue