fixGB/apu.c
2020-12-28 06:40:21 +01:00

947 lines
23 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 <stdlib.h>
#include <string.h>
#include "apu.h"
#include "audio.h"
#include "mem.h"
#include "cpu.h"
//Upper bits of FF25 are Left
#define P1_ENABLE_LEFT (1<<4)
#define P2_ENABLE_LEFT (1<<5)
#define WAV_ENABLE_LEFT (1<<6)
#define NOISE_ENABLE_LEFT (1<<7)
//Lower bits of FF25 are Right
#define P1_ENABLE_RIGHT (1<<0)
#define P2_ENABLE_RIGHT (1<<1)
#define WAV_ENABLE_RIGHT (1<<2)
#define NOISE_ENABLE_RIGHT (1<<3)
static uint8_t APU_IO_Reg[0x50];
#if AUDIO_FLOAT
static float lpVal;
static float hpVal;
static const float volLevel[8] = {
0.125f, 0.25f, 0.375f, 0.5f, 0.625f, 0.75f, 0.875f, 1.0f,
};
static float *apuOutBuf;
#else
static int32_t lpVal;
static int32_t hpVal;
static int16_t *apuOutBuf;
#endif
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 wavFreq;
static uint16_t noiseFreq;
static uint16_t noiseShiftReg;
static uint8_t p1LengthCtr, p2LengthCtr, noiseLengthCtr;
static uint8_t wavLinearCtr;
static uint16_t wavLengthCtr;
static uint8_t wavVolShift;
static uint16_t modeCurCtr;
static uint8_t modePos;
static uint16_t p1freqCtr, p2freqCtr, wavFreqCtr, noiseFreqCtr;
static uint8_t p1Cycle, p2Cycle, wavCycle;
static bool p1haltloop, p2haltloop, wavhaltloop, noisehaltloop;
static bool p1dacenable, p2dacenable, wavdacenable, noisedacenable;
static bool p1enable, p2enable, wavenable, noiseenable;
static bool soundEnabled;
static bool noiseMode1;
static bool wavEqual;
static envelope_t p1Env, p2Env, noiseEnv;
typedef struct _sweep_t {
bool enabled;
bool negative;
bool inNegative;
uint8_t period;
uint8_t divider;
uint8_t shift;
uint16_t pfreq;
} sweep_t;
static sweep_t p1Sweep;
//used externally
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 uint16_t noisePeriodNtsc[8] = {
8, 16, 32, 48, 64, 80, 96, 112,
};
//wav values from http://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware#Power_Control
static const uint8_t startWavSetDMG[0x10] = {
0x84, 0x40, 0x43, 0xAA, 0x2D, 0x78, 0x92, 0x3C,
0x60, 0x59, 0x59, 0xB0, 0x34, 0xB8, 0x2E, 0xDA,
};
static const uint8_t startWavSetCGB[0x10] = {
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF,
};
//used externally
const uint16_t *noisePeriod;
static const uint8_t *p1seq = pulseSeqs[0],
*p2seq = pulseSeqs[1];
#define M_2_PI 6.28318530717958647692
void apuInitBufs()
{
noisePeriod = noisePeriodNtsc;
//effective frequency for 60.000Hz Video out
//apuFrequency = 526680;
//effective frequency for original LCD Video out
apuFrequency = 262144;
double dt = 1.0/((double)apuFrequency);
//LP at 20kHz
double rc = 1.0/(M_2_PI * 20000.0);
#if AUDIO_FLOAT
lpVal = dt / (rc + dt);
#else
//convert to 32bit int for calcs later
lpVal = (int32_t)((dt / (rc + dt))*32768.0);
#endif
//HP at 20Hz for GB/GBA (150Hz for GBC)
rc = 1.0/(M_2_PI * 20.0);
#if AUDIO_FLOAT
hpVal = rc / (rc + dt);
#else
//convert to 32bit int for calcs later
hpVal = (int32_t)((rc / (rc + dt))*32768.0);
#endif
//keep exactly 1 frame buffer
apuBufSize = 70224/16*2;
#if AUDIO_FLOAT
apuBufSizeBytes = apuBufSize*sizeof(float);
apuOutBuf = (float*)malloc(apuBufSizeBytes);
printf("Audio: 32-bit Float Output\n");
#else
apuBufSizeBytes = apuBufSize*sizeof(int16_t);
apuOutBuf = (int16_t*)malloc(apuBufSizeBytes);
printf("Audio: 16-bit Short Output\n");
#endif
}
void apuDeinitBufs()
{
if(apuOutBuf)
free(apuOutBuf);
apuOutBuf = NULL;
}
#if AUDIO_FLOAT
static float lastHPOutLeft, lastHPOutRight, lastLPOutLeft, lastLPOutRight;
#else
static int32_t lastHPOutLeft, lastHPOutRight, lastLPOutLeft, lastLPOutRight;
#endif
//used externally
uint8_t curP1Out, curP2Out, curWavOut, curNoiseOut;
extern bool emuSkipVsync, emuSkipFrame;
extern bool gbCgbMode;
extern bool gbCgbBootrom;
void apuInit()
{
memset(APU_IO_Reg,0,0x50);
if(gbCgbMode) //essentially 50% duty pulse on CGB
memcpy(APU_IO_Reg+0x30,startWavSetCGB,0x10);
else //relatively random audio pattern on DMG
memcpy(APU_IO_Reg+0x30,startWavSetDMG,0x10);
memset(apuOutBuf, 0, apuBufSizeBytes);
curBufPos = 0;
modeCurCtr = 0;
modePos = 0;
freq1 = 0; freq2 = 0; wavFreq = 0; noiseFreq = 0;
noiseShiftReg = 0;
p1LengthCtr = 0; p2LengthCtr = 0;
noiseLengthCtr = 0; wavLengthCtr = 0;
wavLinearCtr = 0;
p1freqCtr = 0; p2freqCtr = 0; wavFreqCtr = 0, noiseFreqCtr = 0;
p1Cycle = 0; p2Cycle = 0; wavCycle = 0;
wavVolShift = 4; //default
memset(&p1Env,0,sizeof(envelope_t));
memset(&p2Env,0,sizeof(envelope_t));
memset(&noiseEnv,0,sizeof(envelope_t));
memset(&p1Sweep,0,sizeof(sweep_t));
p1haltloop = false; p2haltloop = false;
wavhaltloop = false; noisehaltloop = false;
p1enable = false; p2enable = false;
wavenable = false; noiseenable = false;
p1dacenable = false; p2dacenable = false;
wavdacenable = false; noisedacenable = false;
noiseMode1 = false;
wavEqual = false;
if(gbCgbBootrom)
soundEnabled = false;
else //GB Bootrom
{
soundEnabled = true;
APU_IO_Reg[0x24] = 0x77;
APU_IO_Reg[0x25] = 0xF3;
}
lastHPOutLeft = 0, lastHPOutRight = 0, lastLPOutLeft = 0, lastLPOutRight = 0;
curP1Out = 0, curP2Out = 0, curWavOut = 0, curNoiseOut = 0;
}
bool apuCycle()
{
if(curBufPos == apuBufSize)
{
#ifndef __LIBRETRO__
int updateRes = audioUpdate();
if(updateRes == 0)
{
emuSkipFrame = false;
emuSkipVsync = false;
return false;
}
if(updateRes > 6)
{
emuSkipVsync = true;
emuSkipFrame = true;
}
else
{
emuSkipFrame = false;
if(updateRes > 2)
emuSkipVsync = true;
else
emuSkipVsync = false;
}
#endif
curBufPos = 0;
}
int8_t p1Out = 0, p2Out = 0, noiseOut = 0, wavOut = 0;
int8_t p1OutLeft = 0, p2OutLeft = 0,
wavOutLeft = 0, noiseOutLeft = 0;
int8_t p1OutRight = 0, p2OutRight = 0,
wavOutRight = 0, noiseOutRight = 0;
int8_t apuMasterVolLeft = ((APU_IO_Reg[0x24]>>4)&7), apuMasterVolRight = (APU_IO_Reg[0x24]&7);
if(p1enable && p1dacenable)
{
if(p1seq[p1Cycle])
curP1Out = p1Env.curVol;
else
curP1Out = 0;
//actually audible output
if(freq1 > 0 && freq1 < 0x7FF)
{
//GB/GBC Behavior
//p1Out = (curP1Out<<1)-15;
//GBA Behavior
p1Out = (curP1Out<<1)-p1Env.curVol;
}
}
else
curP1Out = 0;
if(APU_IO_Reg[0x25] & P1_ENABLE_LEFT)
p1OutLeft = p1Out;
if(APU_IO_Reg[0x25] & P1_ENABLE_RIGHT)
p1OutRight = p1Out;
if(p2enable && p2dacenable)
{
if(p2seq[p2Cycle])
curP2Out = p2Env.curVol;
else
curP2Out = 0;
//actually audible output
if(freq2 > 0 && freq2 < 0x7FF)
{
//GB/GBC Behavior
//p2Out = (curP2Out<<1)-15;
//GBA Behavior
p2Out = (curP2Out<<1)-p2Env.curVol;
}
}
else
curP2Out = 0;
if(APU_IO_Reg[0x25] & P2_ENABLE_LEFT)
p2OutLeft = p2Out;
if(APU_IO_Reg[0x25] & P2_ENABLE_RIGHT)
p2OutRight = p2Out;
if(wavenable && wavdacenable)
{
curWavOut = APU_IO_Reg[0x30+(wavCycle>>1)];
if((wavCycle&1)==0)
curWavOut >>= 4;
else
curWavOut &= 0xF;
curWavOut >>= wavVolShift;
//actually audible output
if((wavFreq > 0 && wavFreq < 0x7FF) || wavEqual)
wavOut = (curWavOut<<1)-15;
}
else
curWavOut = 0;
if(APU_IO_Reg[0x25] & WAV_ENABLE_LEFT)
wavOutLeft = wavOut;
if(APU_IO_Reg[0x25] & WAV_ENABLE_RIGHT)
wavOutRight = wavOut;
if(noiseenable && noisedacenable)
{
if((noiseShiftReg&1) == 0)
curNoiseOut = noiseEnv.curVol;
else
curNoiseOut = 0;
//actually audible output
if(noiseFreq > 0)
{
//GB/GBC Behavior
//noiseOut = (curNoiseOut<<1)-15;
//GBA Behavior
noiseOut = (curNoiseOut<<1)-noiseEnv.curVol;
}
}
else
curNoiseOut = 0;
if(APU_IO_Reg[0x25] & NOISE_ENABLE_LEFT)
noiseOutLeft = noiseOut;
if(APU_IO_Reg[0x25] & NOISE_ENABLE_RIGHT)
noiseOutRight = noiseOut;
#if AUDIO_FLOAT
//gen output Left
float curInLeft = ((float)(p1OutLeft + p2OutLeft + wavOutLeft + noiseOutLeft))*volLevel[apuMasterVolLeft]/85.333333f;
float curLPOutLeft = lastLPOutLeft+(lpVal*(curInLeft-lastLPOutLeft));
float curHPOutLeft = hpVal*(lastHPOutLeft+lastLPOutLeft-curLPOutLeft);
//gen output Right
float curInRight = ((float)(p1OutRight + p2OutRight + wavOutRight + noiseOutRight))*volLevel[apuMasterVolRight]/85.333333f;
float curLPOutRight = lastLPOutRight+(lpVal*(curInRight-lastLPOutRight));
float curHPOutRight = hpVal*(lastHPOutRight+lastLPOutRight-curLPOutRight);
//set output Left
apuOutBuf[curBufPos++] = ((soundEnabled)?curHPOutLeft:0);
//set output Right
apuOutBuf[curBufPos++] = ((soundEnabled)?curHPOutRight:0);
//save HP and LP Left
lastLPOutLeft = curLPOutLeft;
lastHPOutLeft = curHPOutLeft;
//save HP and LP Right
lastLPOutRight = curLPOutRight;
lastHPOutRight = curHPOutRight;
#else
int32_t curIn, curOut;
//gen output Left
curIn = (p1OutLeft + p2OutLeft + wavOutLeft + noiseOutLeft)*(apuMasterVolLeft+1)*48;
curOut = lastLPOutLeft+((lpVal*(curIn-lastLPOutLeft))>>15); //Set Left Lowpass Output
curIn = (lastHPOutLeft+lastLPOutLeft-curOut); //Set Left Highpass Input
curIn += (curIn>>31)&1; //Add Sign Bit for proper Downshift later
lastLPOutLeft = curOut; //Save Left Lowpass Output
curOut = (hpVal*curIn)>>15; //Set Left Highpass Output
lastHPOutLeft = curOut; //Save Left Highpass Output
//Save Clipped Left Highpass Output
apuOutBuf[curBufPos++] = ((soundEnabled)?((curOut > 32767)?(32767):((curOut < -32768)?(-32768):curOut)):0);
//gen output Right
curIn = (p1OutRight + p2OutRight + wavOutRight + noiseOutRight)*(apuMasterVolRight+1)*48;
curOut = lastLPOutRight+((lpVal*(curIn-lastLPOutRight))>>15); //Set Right Lowpass Output
curIn = (lastHPOutRight+lastLPOutRight-curOut); //Set Right Highpass Input
curIn += (curIn>>31)&1; //Add Sign Bit for proper Downshift later
lastLPOutRight = curOut; //Save Right Lowpass Output
curOut = (hpVal*curIn)>>15; //Set Right Highpass Output
lastHPOutRight = curOut; //Save Right Highpass Output
//Save Clipped Right Highpass Output
apuOutBuf[curBufPos++] = ((soundEnabled)?((curOut > 32767)?(32767):((curOut < -32768)?(-32768):curOut)):0);
#endif
return true;
}
#ifdef __LIBRETRO__
void audioFrameEnd(int samples);
void apuFrameEnd()
{
audioFrameEnd(curBufPos>>1);
curBufPos = 0;
}
#endif
void doEnvelopeLogic(envelope_t *env)
{
if(env->divider == 0)
{
if(env->period)
{
if(env->modeadd)
{
if(env->curVol < 15)
env->curVol++;
}
else
{
if(env->curVol > 0)
env->curVol--;
}
}
//period 0 is actually period 8!
env->divider = (env->period-1)&7;
}
else
env->divider--;
}
void sweepUpdateFreq(sweep_t *sw, uint16_t *freq, bool update)
{
if(!sw->enabled)
return;
//printf("%i\n", *freq);
uint16_t inFreq = sw->pfreq;
uint16_t shiftVal = (inFreq >> sw->shift);
if(sw->negative)
{
sw->inNegative = true;
inFreq -= shiftVal;
}
else
inFreq += shiftVal;
if(inFreq <= 0x7FF)
{
if(sw->enabled && sw->shift && sw->period && update)
{
*freq = inFreq;
sw->pfreq = inFreq;
}
}
else
{
//printf("Freq disabled\n");
p1enable = false;
}
}
void doSweepLogic(sweep_t *sw, uint16_t *freq)
{
if(sw->divider == 0)
{
//printf("Divider 0\n");
if(sw->period)
{
sweepUpdateFreq(sw, freq, true);
//gameboy checks a SECOND time after updating...
uint16_t inFreq = sw->pfreq;
uint16_t shiftVal = (inFreq >> sw->shift);
if(sw->negative)
inFreq -= shiftVal;
else
inFreq += shiftVal;
if(inFreq > 0x7FF)
{
//printf("Freq disabled\n");
p1enable = false;
}
}
//period 0 is actually period 8!
sw->divider = (sw->period-1)&7;
}
else
sw->divider--;
}
void apuClockA()
{
//printf("Len clock\n");
if(p1LengthCtr && !p1haltloop)
{
p1LengthCtr--;
if(p1LengthCtr == 0)
p1enable = false;
}
if(p2LengthCtr && !p2haltloop)
{
p2LengthCtr--;
if(p2LengthCtr == 0)
p2enable = false;
}
if(wavLengthCtr && !wavhaltloop)
{
wavLengthCtr--;
if(wavLengthCtr == 0)
wavenable = false;
}
if(noiseLengthCtr && !noisehaltloop)
{
noiseLengthCtr--;
if(noiseLengthCtr == 0)
noiseenable = false;
}
}
void apuClockB()
{
if(p1LengthCtr)
doEnvelopeLogic(&p1Env);
if(p2LengthCtr)
doEnvelopeLogic(&p2Env);
if(noiseLengthCtr)
doEnvelopeLogic(&noiseEnv);
}
void apuClockTimers()
{
if(modeCurCtr == 0)
{
modePos++;
if(modePos&1)
apuClockA();
if(modePos == 3 || modePos == 7)
{
//printf("sweep clock\n");
if(p1LengthCtr)
doSweepLogic(&p1Sweep, &freq1);
}
if(modePos >= 8)
{
apuClockB();
modePos = 0;
}
modeCurCtr = 8192;
}
if(modeCurCtr)
modeCurCtr--;
if(p1freqCtr == 0)
{
if(freq1)
p1freqCtr = (2048-freq1)*4;
p1Cycle++;
if(p1Cycle >= 8)
p1Cycle = 0;
}
if(p1freqCtr)
p1freqCtr--;
if(p2freqCtr == 0)
{
if(freq2)
p2freqCtr = (2048-freq2)*4;
p2Cycle++;
if(p2Cycle >= 8)
p2Cycle = 0;
}
if(p2freqCtr)
p2freqCtr--;
if(wavFreqCtr == 0)
{
wavFreqCtr = (2048-wavFreq)*2;
wavCycle++;
if(wavCycle >= 32)
wavCycle = 0;
}
if(wavFreqCtr)
wavFreqCtr--;
if(noiseFreqCtr == 0)
{
noiseFreqCtr = noiseFreq;
uint8_t cmpRes = (noiseShiftReg&1)^((noiseShiftReg>>1)&1);
noiseShiftReg >>= 1;
noiseShiftReg |= cmpRes << (noiseMode1 ? 6 : 14);
}
if(noiseFreqCtr)
noiseFreqCtr--;
}
void apuSetReg8(uint16_t addr, uint8_t val)
{
uint8_t reg = addr&0xFF;
//printf("APU set %02x %02x\n", reg, val);
if(reg == 0x26)
{
bool wasEnabled = soundEnabled;
soundEnabled = (val&0x80)!=0;
if(!soundEnabled)
{
// FULL reset of nearly every reg
memset(APU_IO_Reg,0,0x30);
// except for the wav buffer
memset(APU_IO_Reg+0x40,0,0x10);
memset(&p1Env,0,sizeof(envelope_t));
memset(&p2Env,0,sizeof(envelope_t));
memset(&noiseEnv,0,sizeof(envelope_t));
memset(&p1Sweep,0,sizeof(sweep_t));
p1LengthCtr = 0; p2LengthCtr = 0;
wavLengthCtr = 0; noiseLengthCtr = 0;
p1enable = false; p2enable = false;
wavenable = false; noiseenable = false;
p1dacenable = false; p2dacenable = false;
wavdacenable = false; noisedacenable = false;
freq1 = 0; freq2 = 0; wavFreq = 0; noiseFreq = 0;
wavVolShift = 4; //default
}
else
{
APU_IO_Reg[0x26] = val;
//on sound powerup, reset frame sequencer
if(!wasEnabled)
{
modeCurCtr = 8192;
modePos = 0;
}
}
return;
}
//even if sound off, still update wav buffer
else if(reg >= 0x30 && reg < 0x40)
{
if(wavenable)
APU_IO_Reg[0x30+(wavCycle>>1)] = val;
else
APU_IO_Reg[reg] = val;
//allow for manual wav inputs if all wav inputs are equal
wavEqual = ((*(uint32_t*)(APU_IO_Reg+0x30) == *(uint32_t*)(APU_IO_Reg+0x34)) &&
(*(uint32_t*)(APU_IO_Reg+0x34) == *(uint32_t*)(APU_IO_Reg+0x38)) &&
(*(uint32_t*)(APU_IO_Reg+0x38) == *(uint32_t*)(APU_IO_Reg+0x3C)) );
return;
}
//dont even bother with the switch if sound is off
else if(!soundEnabled)
return;
bool p1prevhaltloop, p2prevhaltloop,
wavprevhaltloop, noiseprevhaltloop;
APU_IO_Reg[reg] = val;
switch(reg)
{
case 0x10:
//printf("P1 sweep %02x\n", val);
p1Sweep.shift = val&7;
p1Sweep.period = (val>>4)&7;
p1Sweep.negative = ((val&0x8) != 0);
if(p1Sweep.inNegative && !p1Sweep.negative)
p1enable = false;
break;
case 0x11:
p1seq = pulseSeqs[val>>6];
p1LengthCtr = 64-(val&0x3F);
break;
case 0x12:
p1Env.vol = (val>>4)&0xF;
p1Env.modeadd = (val&8)!=0;
if(p1Env.modeadd && p1Env.period == 0 && (val&7) == 0)
{
//"Zombie" Mode
p1Env.curVol++;
p1Env.curVol &= 0xF;
}
p1dacenable = (p1Env.modeadd || p1Env.vol);
if(!p1dacenable)
p1enable = false;
p1Env.period = val&7;
break;
case 0x13:
freq1 = ((freq1&~0xFF) | val);
//printf("P1 new freq %04x\n", freq1);
break;
case 0x14:
p1prevhaltloop = p1haltloop;
p1haltloop = ((val&(1<<6)) == 0);
freq1 = (freq1&0xFF) | ((val&7)<<8);
//if length was previously frozen and we are in
//an odd frame sequence, clock length right now
if(p1prevhaltloop && !p1haltloop && p1LengthCtr && (modePos&1))
{
p1LengthCtr--;
//disable channel immediately if length
//reached 0 from this extra clock
if(p1LengthCtr == 0)
p1enable = false;
}
if(val&(1<<7))
{
if(p1dacenable)
p1enable = true;
if(p1LengthCtr == 0)
{
p1LengthCtr = 64;
//if length enabled and we are in an odd frame
//sequence, subtract one from newly set clock length
if(!p1haltloop && (modePos&1))
p1LengthCtr--;
}
//trigger reloads frequency timers
p1Cycle = 0;
if(freq1)
p1freqCtr = (2048-freq1)*4;
//trigger resets env volume
p1Env.curVol = p1Env.vol;
//period 0 is actually period 8!
p1Env.divider = (p1Env.period-1)&7;
//trigger used to enable/disable sweep
if(p1Sweep.period || p1Sweep.shift)
p1Sweep.enabled = true;
else
p1Sweep.enabled = false;
//trigger also resets divider, neg mode and frequency
p1Sweep.inNegative = false;
p1Sweep.pfreq = freq1;
//period 0 is actually period 8!
p1Sweep.divider = (p1Sweep.period-1)&7;
//if sweep shift>0, pre-calc frequency
if(p1Sweep.shift)
sweepUpdateFreq(&p1Sweep, &freq1, false);
}
//printf("P1 new freq %04x\n", freq1);
break;
case 0x16:
p2seq = pulseSeqs[val>>6];
p2LengthCtr = 64-(val&0x3F);
break;
case 0x17:
p2Env.vol = (val>>4)&0xF;
p2Env.modeadd = (val&8)!=0;
if(p2Env.modeadd && p2Env.period == 0 && (val&7) == 0)
{
//"Zombie" Mode
p2Env.curVol++;
p2Env.curVol &= 0xF;
}
p2dacenable = (p2Env.modeadd || p2Env.vol);
if(!p2dacenable)
p2enable = false;
p2Env.period = val&7;
break;
case 0x18:
freq2 = ((freq2&~0xFF) | val);
//printf("P2 new freq %04x\n", freq2);
break;
case 0x19:
p2prevhaltloop = p2haltloop;
p2haltloop = ((val&(1<<6)) == 0);
freq2 = (freq2&0xFF) | ((val&7)<<8);
//if length was previously frozen and we are in
//an odd frame sequence, clock length right now
if(p2prevhaltloop && !p2haltloop && p2LengthCtr && (modePos&1))
{
p2LengthCtr--;
//disable channel immediately if length
//reached 0 from this extra clock
if(p2LengthCtr == 0)
p2enable = false;
}
if(val&(1<<7))
{
if(p2dacenable)
p2enable = true;
if(p2LengthCtr == 0)
{
p2LengthCtr = 64;
//if length enabled and we are in an odd frame
//sequence, subtract one from newly set clock length
if(!p2haltloop && (modePos&1))
p2LengthCtr--;
}
//trigger reloads frequency timers
p2Cycle = 0;
if(freq2)
p2freqCtr = (2048-freq2)*4;
//trigger resets env volume
p2Env.curVol = p2Env.vol;
//period 0 is actually period 8!
p2Env.divider = (p2Env.period-1)&7;
}
//printf("P2 new freq %04x\n", freq2);
break;
case 0x1A:
wavdacenable = ((val&0x80)!=0);
if(!wavdacenable)
wavenable = false;
break;
case 0x1B:
wavLengthCtr = 256-val;
break;
case 0x1C:
//printf("wavVolShift %i\n", (val>>5)&3);
switch((val>>5)&3)
{
case 0:
wavVolShift=4;
break;
case 1:
wavVolShift=0;
break;
case 2:
wavVolShift=1;
break;
case 3:
wavVolShift=2;
break;
}
break;
case 0x1D:
wavFreq = ((wavFreq&~0xFF) | val);
//printf("wav new freq %04x\n", wavFreq);
break;
case 0x1E:
wavprevhaltloop = wavhaltloop;
wavhaltloop = ((val&(1<<6)) == 0);
wavFreq = (wavFreq&0xFF) | ((val&7)<<8);
//if length was previously frozen and we are in
//an odd frame sequence, clock length right now
if(wavprevhaltloop && !wavhaltloop && wavLengthCtr && (modePos&1))
{
wavLengthCtr--;
//disable channel immediately if length
//reached 0 from this extra clock
if(wavLengthCtr == 0)
wavenable = false;
}
if(val&(1<<7))
{
if(wavdacenable)
wavenable = true;
if(wavLengthCtr == 0)
{
wavLengthCtr = 256;
//if length enabled and we are in an odd frame
//sequence, subtract one from newly set clock length
if(!wavhaltloop && (modePos&1))
wavLengthCtr--;
}
//trigger reloads frequency timers
wavCycle = 0;
//not sure why +4 needed to sync initally,
//probably because of sample buffer byte
wavFreqCtr = ((2048-wavFreq)*2)+4;
}
//printf("wav new freq %04x\n", wavFreq);
break;
case 0x20:
noiseLengthCtr = 64-(val&0x3F);
break;
case 0x21:
noiseEnv.vol = (val>>4)&0xF;
noiseEnv.modeadd = (val&8)!=0;
if(noiseEnv.modeadd && noiseEnv.period == 0 && (val&7) == 0)
{
//"Zombie" Mode
noiseEnv.curVol++;
noiseEnv.curVol &= 0xF;
}
noisedacenable = (noiseEnv.modeadd || noiseEnv.vol);
if(!noisedacenable)
noiseenable = false;
noiseEnv.period=val&7;
break;
case 0x22:
if((val>>4)<14)
noiseFreq = noisePeriod[val&0x7]<<(val>>4);
else
noiseFreq = 0;
noiseMode1 = ((val&0x8) != 0);
break;
case 0x23:
noiseprevhaltloop = noisehaltloop;
noisehaltloop = ((val&(1<<6)) == 0);
//if length was previously frozen and we are in
//an odd frame sequence, clock length right now
if(noiseprevhaltloop && !noisehaltloop && noiseLengthCtr && (modePos&1))
{
noiseLengthCtr--;
//disable channel immediately if length
//reached 0 from this extra clock
if(noiseLengthCtr == 0)
noiseenable = false;
}
if(val&(1<<7))
{
if(noisedacenable)
noiseenable = true;
if(noiseLengthCtr == 0)
{
noiseLengthCtr = 64;
//if length enabled and we are in an odd frame
//sequence, subtract one from newly set clock length
if(!noisehaltloop && (modePos&1))
noiseLengthCtr--;
}
//trigger reloads frequency timers
noiseFreqCtr = noiseFreq;
//trigger resets env volume
noiseEnv.curVol = noiseEnv.vol;
//period 0 is actually period 8!
noiseEnv.divider = (noiseEnv.period-1)&7;
//trigger sets all shift reg bits
noiseShiftReg = 0x7FFF;
}
break;
default:
break;
}
}
//write-only bits are always set on reads by the cpu
static const uint8_t apuReadMask[0x20] =
{
0x80, 0x3F, 0x00, 0xFF, 0xBF, 0xFF, 0x3F, 0x00, 0xFF, 0xBF, 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, 0xFF,
0xFF, 0x00, 0x00, 0xBF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};
uint8_t apuGetReg8(uint16_t addr)
{
uint8_t reg = addr&0xFF;
//printf("APU get %02x\n", reg);
switch(reg)
{
case 0x10: case 0x11: case 0x12: case 0x13: case 0x14: case 0x15: case 0x16: case 0x17:
case 0x18: case 0x19: case 0x1A: case 0x1B: case 0x1C: case 0x1D: case 0x1E: case 0x1F:
case 0x20: case 0x21: case 0x22: case 0x23: case 0x24: case 0x25: /*case 0x26:*/ case 0x27:
case 0x28: case 0x29: case 0x2A: case 0x2B: case 0x2C: case 0x2D: case 0x2E: case 0x2F:
return APU_IO_Reg[reg]|apuReadMask[reg-0x10];
case 0x26:
return soundEnabled?((p1enable) | ((p2enable)<<1) | ((wavenable)<<2) | ((noiseenable)<<3)|0xF0):0x70;
case 0x30: case 0x31: case 0x32: case 0x33: case 0x34: case 0x35: case 0x36: case 0x37:
case 0x38: case 0x39: case 0x3A: case 0x3B: case 0x3C: case 0x3D: case 0x3E: case 0x3F:
if(wavenable)
return APU_IO_Reg[0x30+(wavCycle>>1)];
return APU_IO_Reg[reg];
default:
break;
}
return 0xFF;
}
uint8_t *apuGetBuf()
{
return (uint8_t*)apuOutBuf;
}
uint32_t apuGetBufSize()
{
return apuBufSizeBytes;
}
uint32_t apuGetFrequency()
{
return apuFrequency;
}