fixNES/apu.c

674 lines
14 KiB
C

/*
* Copyright (C) 2017 FIX94
*
* This software may be modified and distributed under the terms
* of the MIT license. See the LICENSE file for details.
*/
#include <stdio.h>
#include <stdbool.h>
#include <inttypes.h>
#include <malloc.h>
#include "apu.h"
#include "audio.h"
#include "mem.h"
#include "cpu.h"
#define P1_ENABLE (1<<0)
#define P2_ENABLE (1<<1)
#define TRI_ENABLE (1<<2)
#define NOISE_ENABLE (1<<3)
#define DMC_ENABLE (1<<4)
#define PULSE_CONST_V (1<<4)
#define PULSE_HALT_LOOP (1<<5)
#define TRI_HALT_LOOP (1<<7)
#define DMC_HALT_LOOP (1<<6)
#define DMC_IRQ_ENABLE (1<<7)
static uint8_t APU_IO_Reg[0x18];
static float lpVal;
static float hpVal;
static float *apuOutBuf;
static uint32_t apuBufSize;
static uint32_t apuBufSizeBytes;
static uint32_t curBufPos;
static uint32_t apuFrequency;
static uint16_t freq1;
static uint16_t freq2;
static uint16_t triFreq;
static uint16_t noiseFreq;
static uint16_t noiseShiftReg;
static uint16_t dmcFreq;
static uint16_t dmcAddr, dmcLen, dmcSampleBuf;
static uint16_t dmcCurAddr, dmcCurLen;
static uint8_t p1LengthCtr, p2LengthCtr, noiseLengthCtr;
static uint8_t triLengthCtr, triLinearCtr, triCurLinearCtr;
static uint8_t dmcVol, dmcCurVol;
static uint8_t dmcSampleRemain;
static bool mode5 = false;
static int modePos = 0;
static uint16_t p1freqCtr, p2freqCtr, triFreqCtr, noiseFreqCtr, dmcFreqCtr;
static uint8_t p1Cycle, p2Cycle, triCycle, dmcCycle;
static bool p1haltloop, p2haltloop, trihaltloop, noisehaltloop, dmchaltloop;
static bool dmcstart;
static bool dmcirqenable;
static bool trireload;
static bool noiseMode1;
static bool apu_enable_irq;
typedef struct _envelope_t {
bool start;
bool constant;
bool loop;
uint8_t vol;
//uint8_t envelope;
uint8_t divider;
uint8_t decay;
} envelope_t;
static envelope_t p1Env, p2Env, noiseEnv;
typedef struct _sweep_t {
bool enabled;
bool start;
bool negative;
bool mute;
bool chan1;
uint8_t period;
uint8_t divider;
uint8_t shift;
} sweep_t;
static sweep_t p1Sweep, p2Sweep;
static float pulseLookupTbl[32];
static float tndLookupTbl[204];
static const uint8_t lengthLookupTbl[0x20] = {
10,254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14,
12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30
};
static const uint8_t pulseSeqs[4][8] = {
{ 0, 1, 0, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 0, 0, 0, 0, 0 },
{ 0, 1, 1, 1, 1, 0, 0, 0 },
{ 1, 0, 0, 1, 1, 1, 1, 1 },
};
static const uint8_t triSeq[32] = {
15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0,
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
};
static const uint16_t noisePeriodNtsc[16] = {
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
};
static const uint16_t noisePeriodPal[16] = {
4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
};
static const uint16_t dmcPeriodNtsc[16] = {
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
};
static const uint16_t dmcPeriodPal[16] = {
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
};
static const uint16_t *dmcPeriod, *noisePeriod;
static const uint8_t *p1seq = pulseSeqs[0],
*p2seq = pulseSeqs[1];
extern bool dmc_interrupt;
#define M_2_PI 6.28318530717958647692
extern bool nesPAL;
void apuInit()
{
memset(APU_IO_Reg,0,0x18);
noisePeriod = nesPAL ? noisePeriodPal : noisePeriodNtsc;
dmcPeriod = nesPAL ? dmcPeriodPal : dmcPeriodNtsc;
//effective frequencies for 50.000Hz and 60.000Hz Video out
apuFrequency = nesPAL ? 831187 : 893415;
double dt = 1.0/((double)apuFrequency);
//LP at 22kHz
double rc = 1.0/(M_2_PI * 22000.0);
lpVal = dt / (rc + dt);
//HP at 40Hz
rc = 1.0/(M_2_PI * 40.0);
hpVal = rc / (rc + dt);
apuBufSize = apuFrequency/60;
apuBufSizeBytes = apuBufSize*sizeof(float);
apuOutBuf = (float*)malloc(apuBufSizeBytes);
memset(apuOutBuf, 0, apuBufSizeBytes);
curBufPos = 0;
freq1 = 0; freq2 = 0; triFreq = 0; noiseFreq = 0, dmcFreq = 0;
noiseShiftReg = 1;
p1LengthCtr = 0; p2LengthCtr = 0;
noiseLengthCtr = 0; triLengthCtr = 0;
triLinearCtr = 0; triCurLinearCtr = 0;
dmcAddr = 0, dmcLen = 0, dmcVol = 0; dmcSampleBuf = 0;
dmcCurAddr = 0, dmcCurLen = 0; dmcCurVol = 0;
dmcSampleRemain = 0;
p1freqCtr = 0; p2freqCtr = 0; triFreqCtr = 0, noiseFreqCtr = 0, dmcFreqCtr = 0;
p1Cycle = 0; p2Cycle = 0; triCycle = 0; dmcCycle = 0;
memset(&p1Env,0,sizeof(envelope_t));
memset(&p2Env,0,sizeof(envelope_t));
memset(&noiseEnv,0,sizeof(envelope_t));
memset(&p1Sweep,0,sizeof(sweep_t));
p1Sweep.chan1 = true; //for negative sweep
memset(&p2Sweep,0,sizeof(sweep_t));
p2Sweep.chan1 = false;
p1haltloop = false; p2haltloop = false;
trihaltloop = false; noisehaltloop = false;
dmcstart = false;
dmcirqenable = false;
trireload = false;
noiseMode1 = false;
//4017 starts out as 0, so enable
apu_enable_irq = true;
/* https://wiki.nesdev.com/w/index.php/APU_Mixer#Lookup_Table */
int i;
for(i = 0; i < 32; i++)
pulseLookupTbl[i] = 95.52 / ((8128.0 / i) + 100);
for(i = 0; i < 204; i++)
tndLookupTbl[i] = 163.67 / ((24329.0 / i) + 100);
}
void apuDeinit()
{
if(apuOutBuf)
free(apuOutBuf);
apuOutBuf = NULL;
}
void apuClockTimers()
{
if(p1LengthCtr && (APU_IO_Reg[0x15] & P1_ENABLE))
{
if(p1freqCtr/2 > freq1)
{
p1freqCtr = 0;
p1Cycle++;
}
if(p1Cycle >= 8)
p1Cycle = 0;
p1freqCtr++;
}
if(p2LengthCtr && (APU_IO_Reg[0x15] & P2_ENABLE))
{
if(p2freqCtr/2 > freq2)
{
p2freqCtr = 0;
p2Cycle++;
}
if(p2Cycle >= 8)
p2Cycle = 0;
p2freqCtr++;
}
if(triLengthCtr && triCurLinearCtr && (APU_IO_Reg[0x15] & TRI_ENABLE))
{
if(triFreqCtr > triFreq)
{
triFreqCtr = 0;
triCycle++;
}
if(triCycle >= 32)
triCycle = 0;
triFreqCtr++;
}
if(noiseLengthCtr && (APU_IO_Reg[0x15] & NOISE_ENABLE))
{
if(noiseFreqCtr > noiseFreq)
{
noiseFreqCtr = 0;
uint8_t cmpBit = noiseMode1 ? (noiseShiftReg>>6)&1 : (noiseShiftReg>>1)&1;
uint8_t cmpRes = (noiseShiftReg&1)^cmpBit;
noiseShiftReg>>=1;
noiseShiftReg|=cmpRes<<14;
}
noiseFreqCtr++;
}
if(dmcLen && (APU_IO_Reg[0x15] & DMC_ENABLE))
{
if(dmcstart)
{
dmcCurAddr = dmcAddr;
dmcCurLen = dmcLen;
//dmcCurVol = dmcVol;
//if(!dmcSampleRemain)
dmcCycle = 8;
dmcstart = false;
}
if(dmcFreqCtr >= dmcFreq)
{
dmcFreqCtr = 0;
dmcCycle++;
if(dmcSampleRemain)
{
if(dmcSampleBuf&1)
{
if(dmcVol <= 125)
dmcVol += 2;
}
else if(dmcVol >= 2)
dmcVol -= 2;
dmcSampleBuf>>=1;
dmcSampleRemain--;
}
}
if(dmcCycle >= 8)
{
if(dmcCurLen)
{
dmcSampleBuf = memGet8(dmcCurAddr);
cpuIncWaitCycles(4);
dmcCurAddr++;
if(dmcCurAddr < 0x8000)
dmcCurAddr |= 0x8000;
dmcCurLen--;
dmcSampleRemain = 8;
}
else if(dmchaltloop)
dmcstart = true;
else if(dmcirqenable)
dmc_interrupt = true;
dmcCycle = 0;
}
dmcFreqCtr++;
}
}
static float lastHPOut = 0, lastLPOut = 0;
static uint8_t lastP1Out = 0, lastP2Out = 0, lastTriOut = 0, lastNoiseOut = 0;
extern bool emuSkipVsync;
int apuCycle()
{
if(curBufPos == apuBufSize)
{
int updateRes = audioUpdate();
if(updateRes == 0)
{
emuSkipVsync = false;
return 0;
}
if(updateRes > 4)
emuSkipVsync = true;
else
emuSkipVsync = false;
curBufPos = 0;
}
uint8_t p1Out = lastP1Out, p2Out = lastP2Out,
triOut = lastTriOut, noiseOut = lastNoiseOut;
if(p1LengthCtr && (APU_IO_Reg[0x15] & P1_ENABLE))
{
if(p1seq[p1Cycle] && !p1Sweep.mute && freq1 >= 8 && freq1 < 0x7FF)
lastP1Out = p1Out = (p1Env.constant ? p1Env.vol : p1Env.decay);
else
p1Out = 0;
}
if(p2LengthCtr && (APU_IO_Reg[0x15] & P2_ENABLE))
{
if(p2seq[p2Cycle] && !p2Sweep.mute && freq2 >= 8 && freq2 < 0x7FF)
lastP2Out = p2Out = (p2Env.constant ? p2Env.vol : p2Env.decay);
else
p2Out = 0;
}
if(triLengthCtr && triCurLinearCtr && (APU_IO_Reg[0x15] & TRI_ENABLE))
{
if(triSeq[triCycle] && triFreq >= 2)
lastTriOut = triOut = triSeq[triCycle];
else
triOut = 0;
}
if(noiseLengthCtr && (APU_IO_Reg[0x15] & NOISE_ENABLE))
{
if((noiseShiftReg&1) == 0 && noiseFreq > 0)
lastNoiseOut = noiseOut = (noiseEnv.constant ? noiseEnv.vol : noiseEnv.decay);
else
noiseOut = 0;
}
float curIn = pulseLookupTbl[p1Out + p2Out] + tndLookupTbl[(3*triOut) + (2*noiseOut) + dmcVol];
float curLPout = lastLPOut+(lpVal*(curIn-lastLPOut));
float curHPOut = hpVal*(lastHPOut+curLPout-curIn);
//set output
apuOutBuf[curBufPos] = -curHPOut;
lastLPOut = curLPout;
lastHPOut = curHPOut;
curBufPos++;
return 1;
}
static void doEnvelopeLogic(envelope_t *env)
{
if(env->start)
{
env->start = false;
env->divider = env->vol;
env->decay = 15;
}
else
{
if(env->divider == 0)
{
env->divider = env->vol;
if(env->decay == 0)
{
if(env->loop)
env->decay = 15;
}
else
env->decay--;
}
else
env->divider--;
}
//too slow on its own?
//env->envelope = (env->constant ? env->vol : env->decay);
}
void sweepUpdateFreq(sweep_t *sw, uint16_t *freq)
{
uint16_t inFreq = *freq;
if(sw->negative)
{
inFreq -= (inFreq >> sw->shift);
if(sw->chan1 == true) inFreq--;
}
else
inFreq += (inFreq >> sw->shift);
//if(inFreq > 8 && (inFreq < 0x7FF))
{
if(sw->enabled && sw->shift)
*freq = inFreq;
}
}
void doSweepLogic(sweep_t *sw, uint16_t *freq)
{
if(sw->start)
{
uint8_t prevDiv = sw->divider;
sw->divider = sw->period;
sw->start = false;
if(prevDiv == 0)
sweepUpdateFreq(sw, freq);
}
else
{
if(sw->divider == 0)
{
sweepUpdateFreq(sw, freq);
sw->divider = sw->period;
}
else
sw->divider--;
}
//gets clocked too little on its own?
/*if(inFreq < 8 || (inFreq >= 0x7FF))
sw->mute = true;
else
sw->mute = false;*/
}
void apuClockA()
{
if(p1LengthCtr)
{
doSweepLogic(&p1Sweep, &freq1);
if(!p1haltloop)
p1LengthCtr--;
}
if(p2LengthCtr)
{
doSweepLogic(&p2Sweep, &freq2);
if(!p2haltloop)
p2LengthCtr--;
}
if(triLengthCtr && !trihaltloop)
triLengthCtr--;
if(noiseLengthCtr && !noisehaltloop)
noiseLengthCtr--;
}
void apuClockB()
{
if(p1LengthCtr)
doEnvelopeLogic(&p1Env);
if(p2LengthCtr)
doEnvelopeLogic(&p2Env);
if(noiseLengthCtr)
doEnvelopeLogic(&noiseEnv);
if(trireload)
triCurLinearCtr = triLinearCtr;
else if(triCurLinearCtr)
triCurLinearCtr--;
if(!trihaltloop)
trireload = false;
}
extern bool apu_interrupt;
extern void nesEmuResetApuClock(void);
void apuLenCycle()
{
if(mode5 == false)
{
if(modePos >= 4)
modePos = 0;
if(modePos == 1)
apuClockA();
if(modePos == 3)
{
apuClockA();
if(apu_enable_irq)
apu_interrupt = true;
}
apuClockB();
}
else
{
if(modePos >= 5)
modePos = 0;
if(modePos == 0 || modePos == 2)
apuClockA();
if(modePos != 4)
apuClockB();
}
modePos++;
}
void apuSet8(uint8_t reg, uint8_t val)
{
//printf("%02x %02x %04x\n", reg, val, cpuGetPc());
APU_IO_Reg[reg] = val;
if(reg == 0)
{
p1Env.vol = val&0xF;
p1seq = pulseSeqs[val>>6];
p1Env.constant = ((val&PULSE_CONST_V) != 0);
p1Env.loop = p1haltloop = ((val&PULSE_HALT_LOOP) != 0);
}
else if(reg == 1)
{
//printf("P1 sweep %02x\n", val);
p1Sweep.enabled = ((val&0x80) != 0);
p1Sweep.shift = val&7;
p1Sweep.period = (val>>4)&7;
p1Sweep.negative = ((val&0x8) != 0);
p1Sweep.start = true;
doSweepLogic(&p1Sweep, &freq1);
}
else if(reg == 2)
{
//printf("P1 time low %02x\n", val);
freq1 = ((freq1&~0xFF) | val);
}
else if(reg == 3)
{
p1Cycle = 0;
if(APU_IO_Reg[0x15] & P1_ENABLE)
p1LengthCtr = lengthLookupTbl[val>>3];
freq1 = (freq1&0xFF) | ((val&7)<<8);
//printf("P1 new freq %04x\n", freq2);
p1Env.start = true;
}
else if(reg == 4)
{
p2Env.vol = val&0xF;
p2seq = pulseSeqs[val>>6];
p2Env.constant = ((val&PULSE_CONST_V) != 0);
p2Env.loop = p2haltloop = ((val&PULSE_HALT_LOOP) != 0);
}
else if(reg == 5)
{
//printf("P2 sweep %02x\n", val);
p2Sweep.enabled = ((val&0x80) != 0);
p2Sweep.shift = val&7;
p2Sweep.period = (val>>4)&7;
p2Sweep.negative = ((val&0x8) != 0);
p2Sweep.start = true;
doSweepLogic(&p2Sweep, &freq2);
}
else if(reg == 6)
{
//printf("P2 time low %02x\n", val);
freq2 = ((freq2&~0xFF) | val);
}
else if(reg == 7)
{
p2Cycle = 0;
if(APU_IO_Reg[0x15] & P2_ENABLE)
p2LengthCtr = lengthLookupTbl[val>>3];
freq2 = (freq2&0xFF) | ((val&7)<<8);
//printf("P2 new freq %04x\n", freq2);
p2Env.start = true;
}
else if(reg == 8)
{
triLinearCtr = val&0x7F;
trihaltloop = ((val&TRI_HALT_LOOP) != 0);
}
else if(reg == 0xA)
{
triFreq = ((triFreq&~0xFF) | val);
//if(triFreq < 2)
// triLengthCtr = 0;
}
else if(reg == 0xB)
{
if(APU_IO_Reg[0x15] & TRI_ENABLE)
triLengthCtr = lengthLookupTbl[val>>3];
triFreq = (triFreq&0xFF) | ((val&7)<<8);
//printf("Tri new freq %04x\n", triFreq);
//if(triFreq < 2)
// triLengthCtr = 0;
trireload = true;
}
else if(reg == 0xC)
{
noiseEnv.vol = val&0xF;
noiseEnv.constant = ((val&PULSE_CONST_V) != 0);
noiseEnv.loop = noisehaltloop = ((val&PULSE_HALT_LOOP) != 0);
}
else if(reg == 0xE)
{
noiseMode1 = ((val&0x80) != 0);
noiseFreq = noisePeriod[val&0xF];
}
else if(reg == 0xF)
{
if(APU_IO_Reg[0x15] & NOISE_ENABLE)
noiseLengthCtr = lengthLookupTbl[val>>3];
noiseEnv.start = true;
}
else if(reg == 0x10)
{
dmcFreq = dmcPeriod[val&0xF];
dmchaltloop = ((val&DMC_HALT_LOOP) != 0);
dmcirqenable = ((val&DMC_IRQ_ENABLE) != 0);
//printf("%d\n", dmcirqenable);
if(!dmcirqenable)
dmc_interrupt = false;
}
else if(reg == 0x11)
dmcVol = val&0x7F;
else if(reg == 0x12)
dmcAddr = 0xC000+(val*64);
else if(reg == 0x13)
dmcLen = (val*16)+1;
else if(reg == 0x15)
{
//printf("Set 0x15 %02x\n",val);
if(!(val & P1_ENABLE))
p1LengthCtr = 0;
if(!(val & P2_ENABLE))
p2LengthCtr = 0;
if(!(val & TRI_ENABLE))
triLengthCtr = 0;
if(!(val & NOISE_ENABLE))
noiseLengthCtr = 0;
if(!(val & DMC_ENABLE))
dmcCurLen = 0;
else if(dmcCurLen == 0)
dmcstart = true;
dmc_interrupt = false;
}
else if(reg == 0x17)
{
apu_enable_irq = ((val&(1<<6)) == 0);
if(!apu_enable_irq)
apu_interrupt = false;
mode5 = ((val&(1<<7)) != 0);
//printf("Set 0x17 %d %d\n", apu_enable_irq, mode5);
modePos = 0;
nesEmuResetApuClock();
if(mode5)
apuLenCycle();
}
}
uint8_t apuGet8(uint8_t reg)
{
//printf("%08x\n", reg);
if(reg == 0x15)
{
uint8_t intrflags = ((apu_interrupt<<6) | (dmc_interrupt<<7));
//printf("Get 0x15 %02x\n",intrflags);
apu_interrupt = false;
return ((p1LengthCtr > 0) | ((p2LengthCtr > 0)<<1) | ((triLengthCtr > 0)<<2) | ((noiseLengthCtr > 0)<<3) | ((dmcCurLen > 0)<<4) | intrflags);
}
return APU_IO_Reg[reg];
}
uint8_t *apuGetBuf()
{
return (uint8_t*)apuOutBuf;
}
uint32_t apuGetBufSize()
{
return apuBufSizeBytes;
}
uint32_t apuGetFrequency()
{
return apuFrequency;
}