From a41e3b497ad6277a6a8304be1bce343d17deda26 Mon Sep 17 00:00:00 2001 From: FIX94 Date: Fri, 26 May 2017 07:52:21 +0200 Subject: [PATCH] massive audio accuracy improvements, better gbs music compatibility and fixed 8x16 sprite mode --- apu.c | 174 +++++++++++++++++++++++++++++++++++++++++++++++++-------- apu.h | 1 + cpu.c | 40 ++++++++++--- main.c | 2 +- ppu.c | 69 ++++++++++++++--------- 5 files changed, 231 insertions(+), 55 deletions(-) diff --git a/apu.c b/apu.c index 81db287..b9b61b3 100644 --- a/apu.c +++ b/apu.c @@ -73,6 +73,17 @@ 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; @@ -85,7 +96,9 @@ void apuInitBufs() { noisePeriod = noisePeriodNtsc; //effective frequency for 60.000Hz Video out - apuFrequency = 526680; + //apuFrequency = 526680; + //effective frequency for original LCD Video out + apuFrequency = 524288; double dt = 1.0/((double)apuFrequency); //LP at 22kHz double rc = 1.0/(M_2_PI * 22000.0); @@ -107,9 +120,14 @@ void apuDeinitBufs() apuOutBuf = NULL; } +extern bool allowCgbRegs; void apuInit() { memset(APU_IO_Reg,0,0x50); + if(allowCgbRegs) //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; @@ -143,8 +161,6 @@ void apuInit() extern uint32_t cpu_oam_dma; void apuClockTimers() { - if(p1freqCtr) - p1freqCtr--; if(p1freqCtr == 0) { if(freq1) @@ -153,9 +169,9 @@ void apuClockTimers() if(p1Cycle >= 8) p1Cycle = 0; } + if(p1freqCtr) + p1freqCtr--; - if(p2freqCtr) - p2freqCtr--; if(p2freqCtr == 0) { if(freq2) @@ -164,9 +180,9 @@ void apuClockTimers() if(p2Cycle >= 8) p2Cycle = 0; } + if(p2freqCtr) + p2freqCtr--; - if(wavFreqCtr) - wavFreqCtr--; if(wavFreqCtr == 0) { wavFreqCtr = (2048-wavFreq)*2; @@ -174,9 +190,9 @@ void apuClockTimers() if(wavCycle >= 32) wavCycle = 0; } + if(wavFreqCtr) + wavFreqCtr--; - if(noiseFreqCtr) - noiseFreqCtr--; if(noiseFreqCtr == 0) { noiseFreqCtr = noiseFreq; @@ -185,6 +201,8 @@ void apuClockTimers() noiseShiftReg >>= 1; noiseShiftReg |= cmpRes<<14; } + if(noiseFreqCtr) + noiseFreqCtr--; } static float lastHPOut[2] = { 0, 0 }, lastLPOut[2] = { 0, 0 }; @@ -225,14 +243,14 @@ bool apuCycle() if(p1enable && p1dacenable && (apuEnableReg & P1_ENABLE)) { if(p1seq[p1Cycle] && freq1 > 0 && freq1 <= 0x7FF) - lastP1Out[apuCurChan] = p1Out = p1Env.vol; + lastP1Out[apuCurChan] = p1Out = p1Env.curVol; else p1Out = 0; } if(p2enable && p2dacenable && (apuEnableReg & P2_ENABLE)) { if(p2seq[p2Cycle] && freq2 > 0 && freq2 <= 0x7FF) - lastP2Out[apuCurChan] = p2Out = p2Env.vol; + lastP2Out[apuCurChan] = p2Out = p2Env.curVol; else p2Out = 0; } @@ -252,7 +270,7 @@ bool apuCycle() if(noiseenable && noisedacenable && (apuEnableReg & NOISE_ENABLE)) { if((noiseShiftReg&1) == 0 && noiseFreq > 0) - lastNoiseOut[apuCurChan] = noiseOut = noiseEnv.vol; + lastNoiseOut[apuCurChan] = noiseOut = noiseEnv.curVol; else noiseOut = 0; } @@ -279,13 +297,13 @@ void doEnvelopeLogic(envelope_t *env) { if(env->modeadd) { - if(env->vol < 15) - env->vol++; + if(env->curVol < 15) + env->curVol++; } else { - if(env->vol > 0) - env->vol--; + if(env->curVol > 0) + env->curVol--; } } //period 0 is actually period 8! @@ -361,10 +379,7 @@ void apuClockA() { p1LengthCtr--; if(p1LengthCtr == 0) - { - //printf("Len ran out\n"); p1enable = false; - } } if(p2LengthCtr && !p2haltloop) { @@ -427,11 +442,13 @@ void apuSet8(uint8_t reg, uint8_t val) //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)); @@ -444,8 +461,22 @@ void apuSet8(uint8_t reg, uint8_t val) p1dacenable = false; p2dacenable = false; wavdacenable = false; noisedacenable = false; freq1 = 0; freq2 = 0; wavFreq = 0; noiseFreq = 0; + } //on sound powerup, reset frame sequencer + else if(!wasEnabled) + { + modeCurCtr = 2048; + modePos = 0; } } + //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; + return; + } if(!soundEnabled) return; APU_IO_Reg[reg] = val; @@ -466,12 +497,12 @@ void apuSet8(uint8_t reg, uint8_t val) else if(reg == 0x12) { p1Env.vol = (val>>4)&0xF; + p1Env.curVol = p1Env.vol; p1Env.modeadd = (val&8)!=0; p1dacenable = (p1Env.modeadd || p1Env.vol); if(!p1dacenable) p1enable = false; p1Env.period = val&7; - p1Env.divider = p1Env.period; } else if(reg == 0x13) { @@ -479,15 +510,39 @@ void apuSet8(uint8_t reg, uint8_t val) } else if(reg == 0x14) { + bool 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; @@ -512,12 +567,12 @@ void apuSet8(uint8_t reg, uint8_t val) else if(reg == 0x17) { p2Env.vol = (val>>4)&0xF; + p2Env.curVol = p2Env.vol; p2Env.modeadd = (val&8)!=0; p2dacenable = (p2Env.modeadd || p2Env.vol); if(!p2dacenable) p2enable = false; p2Env.period = val&7; - p2Env.divider = p2Env.period; } else if(reg == 0x18) { @@ -525,15 +580,39 @@ void apuSet8(uint8_t reg, uint8_t val) } else if(reg == 0x19) { + bool 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); } @@ -571,15 +650,36 @@ void apuSet8(uint8_t reg, uint8_t val) } else if(reg == 0x1E) { + bool 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); } @@ -590,12 +690,12 @@ void apuSet8(uint8_t reg, uint8_t val) else if(reg == 0x21) { noiseEnv.vol = (val>>4)&0xF; + noiseEnv.curVol = noiseEnv.vol; noiseEnv.modeadd = (val&8)!=0; noisedacenable = (noiseEnv.modeadd || noiseEnv.vol); if(!noisedacenable) noiseenable = false; noiseEnv.period=val&7; - noiseEnv.divider = noiseEnv.period; } else if(reg == 0x22) { @@ -607,13 +707,36 @@ void apuSet8(uint8_t reg, uint8_t val) } else if(reg == 0x23) { + bool prevnoisehaltloop = noisehaltloop; noisehaltloop = ((val&(1<<6)) == 0); + //if length was previously frozen and we are in + //an odd frame sequence, clock length right now + if(prevnoisehaltloop && !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; } } } @@ -638,6 +761,13 @@ uint8_t apuGet8(uint8_t reg) uint8_t val; if(reg >= 0x10 && reg < 0x30) val = APU_IO_Reg[reg]|apuReadMask[reg-0x10]; + else if(reg >= 0x30 && reg < 0x40) + { + if(wavenable) + val = APU_IO_Reg[0x30+(wavCycle>>1)]; + else + val = APU_IO_Reg[reg]; + } else val = APU_IO_Reg[reg]; //printf("APU get %02x %02x\n", reg, val); diff --git a/apu.h b/apu.h index cb326cc..7640a25 100644 --- a/apu.h +++ b/apu.h @@ -26,6 +26,7 @@ void apuLenCycle(); typedef struct _envelope_t { bool modeadd; uint8_t vol; + uint8_t curVol; uint8_t period; uint8_t divider; } envelope_t; diff --git a/cpu.c b/cpu.c index 1b46a02..6d67d82 100644 --- a/cpu.c +++ b/cpu.c @@ -35,6 +35,10 @@ void cpuSetupActionArr(); static uint16_t sp, pc, cpuTmp16; static uint8_t a,b,c,d,e,f,h,l,cpuTmp; + +//gbs stuff +static bool gbsInitRet, gbsPlayRet; + static uint8_t sub_in_val; static bool irqEnable; static bool cpuHaltLoop,cpuStopLoop,cpuHaltBug; @@ -51,6 +55,9 @@ void cpuInit() cpuHaltBug = false; cpuCgbSpeed = false; cpuSetupActionArr(); + //gbs stuff + gbsInitRet = false; //for first init + gbsPlayRet = true; //for first play call } static void setAImmRegStats() @@ -1062,11 +1069,27 @@ void cpuGetInstruction() cpu_arr_pos = 0; return; } - if(gbEmuGBSPlayback && pc == 0x8765) + if(gbEmuGBSPlayback) { - cpu_action_arr = cpu_nop_arr; - cpu_arr_pos = 0; - return; + //init return + if(pc == 0x8764) + { + //if(!gbsInitRet) + // printf("Init return\n"); + gbsInitRet = true; //allow play call + cpu_action_arr = cpu_nop_arr; + cpu_arr_pos = 0; + return; + } //play return + else if(pc == 0x8765) + { + //if(!gbsPlayRet) + // printf("Play return\n"); + gbsPlayRet = true; //allow next play call + cpu_action_arr = cpu_nop_arr; + cpu_arr_pos = 0; + return; + } } if(cpuHaltLoop) { @@ -1090,8 +1113,8 @@ void cpuGetInstruction() cpu_arr_pos = 0; cpu_action_func = cpu_actions_arr[curInstr]; - //if(pc>=0 && pc<0x30) - // printf("%04x %02x hl %04x\n", pc, curInstr, (l|(h<<8))); + //if(pc==0xABC || pc == 0xAC1 || pc == 0x5E0E || pc == 0x5E0F) + // printf("%04x %02x a %02x b %02x hl %04x\n", pc, curInstr, a, b, (l|(h<<8))); //HALT bug: PC doesnt increase after instruction is parsed! if(!cpuHaltBug) pc++; cpuHaltBug = false; @@ -1617,6 +1640,9 @@ void cpuSetSpeed(bool cgb) void cpuPlayGBS() { + if(!gbsInitRet || !gbsPlayRet) + return; + gbsPlayRet = false; //push back detect pc sp--; memSet8(sp, 0x87); @@ -1645,7 +1671,7 @@ void cpuLoadGBS(uint8_t song) sp--; memSet8(sp, 0x87); sp--; - memSet8(sp, 0x65); + memSet8(sp, 0x64); //set song and init routine a = song; pc = gbsInitAddr; diff --git a/main.c b/main.c index 99afa5e..35125cb 100644 --- a/main.c +++ b/main.c @@ -26,7 +26,7 @@ #define DEBUG_KEY 0 #define DEBUG_LOAD_INFO 1 -static const char *VERSION_STRING = "fixGB Alpha v0.3.2"; +static const char *VERSION_STRING = "fixGB Alpha v0.4"; static void gbEmuDisplayFrame(void); static void gbEmuMainLoop(void); diff --git a/ppu.c b/ppu.c index da63d31..057e980 100644 --- a/ppu.c +++ b/ppu.c @@ -83,7 +83,7 @@ void ppuInit() ppuCgbBgPalPos = 0; ppuCgbObjPalPos = 0; ppuCgbBank = 0; - ppuLycReg = 153; //last (first) line + ppuLycReg = 0; //first line ppuFrameDone = false; ppuVBlank = false; ppuVBlankTriggered = false; @@ -177,7 +177,7 @@ bool ppuCycle() ppuOAMpos++; } //draw point? - if(ppuClock >= 80 && ppuClock < 240) + if(ppuClock >= 92 && ppuClock < 252) { //makes it possible to draw 160x144 in here :) size_t drawPos = (ppuDots*4)+(PPU_Reg[4]*160*4); @@ -193,7 +193,9 @@ ppuIncreasePos: { ppuClock = 0; PPU_Reg[4]++; - if(PPU_Reg[4]==ppuLycReg) + //zelda seems to want a STAT IRQ for line 0 on the line before it which breaks + //other games, TODO figure out why its intro looks correct with PPU_Reg == 153 + if((PPU_Reg[4] == ppuLycReg) || (ppuLycReg == 0 && PPU_Reg[4] == 154)) { if(PPU_Reg[1]&PPU_LINEMATCH_IRQ) { @@ -280,12 +282,17 @@ uint8_t ppuGet8(uint16_t addr) { if(addr == 0xFF68) val = ppuCgbBgPalPos; - else if(addr == 0xFF69) - val = PPU_CGB_BGPAL[ppuCgbBgPalPos&0x3F]; else if(addr == 0xFF6A) val = ppuCgbObjPalPos; - else if(addr == 0xFF6B) - val = PPU_CGB_OBJPAL[ppuCgbObjPalPos&0x3F]; + else if(!(PPU_Reg[0] & PPU_ENABLE) || (ppuMode != 3)) + { + if(addr == 0xFF69) + val = PPU_CGB_BGPAL[ppuCgbBgPalPos&0x3F]; + else if(addr == 0xFF6B) + val = PPU_CGB_OBJPAL[ppuCgbObjPalPos&0x3F]; + } + else + val = 0xFF; } return val; } @@ -327,33 +334,41 @@ void ppuSet8(uint16_t addr, uint8_t val) if(addr == 0xFF40 && !(val&PPU_ENABLE)) { PPU_Reg[4] = 0; - ppuClock = 0; - ppuMode = 2; + //since it resets a bit into the screen + //it wont get through OAM fully + ppuClock = 6; + ppuOAMpos = 0; //Reset check pos + ppuOAM2pos = 0; //Reset array pos + ppuMode = 2; //OAM + ppuHBlank = false; } else if(addr == 0xFF45) - ppuLycReg = ((val == 0 || val > 153) ? 153 : val); - // printf("ppuSet8(%04x, %02x)\n",addr,val); + ppuLycReg = val; + //printf("ppuSet8(%04x, %02x)\n",addr,val); } } else if(addr >= 0xFF68 && addr < 0xFF6C) { if(addr == 0xFF68) ppuCgbBgPalPos = val; - else if(addr == 0xFF69) - { - //printf("BG Write %02x to %02x\n", val, ppuCgbBgPalPos&0x3F); - PPU_CGB_BGPAL[ppuCgbBgPalPos&0x3F] = val; - if(ppuCgbBgPalPos&0x80) //auto-increment - ppuCgbBgPalPos = ((ppuCgbBgPalPos+1)&0x3F)|0x80; - } else if(addr == 0xFF6A) ppuCgbObjPalPos = val; - else if(addr == 0xFF6B) + else if(!(PPU_Reg[0] & PPU_ENABLE) || (ppuMode != 3)) { - //printf("OBJ Write %02x to %02x\n", val, ppuCgbObjPalPos&0x3F); - PPU_CGB_OBJPAL[ppuCgbObjPalPos&0x3F] = val; - if(ppuCgbObjPalPos&0x80) //auto-increment - ppuCgbObjPalPos = ((ppuCgbObjPalPos+1)&0x3F)|0x80; + if(addr == 0xFF69) + { + //printf("BG Write %02x to %02x\n", val, ppuCgbBgPalPos&0x3F); + PPU_CGB_BGPAL[ppuCgbBgPalPos&0x3F] = val; + if(ppuCgbBgPalPos&0x80) //auto-increment + ppuCgbBgPalPos = ((ppuCgbBgPalPos+1)&0x3F)|0x80; + } + else if(addr == 0xFF6B) + { + //printf("OBJ Write %02x to %02x\n", val, ppuCgbObjPalPos&0x3F); + PPU_CGB_OBJPAL[ppuCgbObjPalPos&0x3F] = val; + if(ppuCgbObjPalPos&0x80) //auto-increment + ppuCgbObjPalPos = ((ppuCgbObjPalPos+1)&0x3F)|0x80; + } } } } @@ -428,7 +443,6 @@ static uint8_t ppuDoSpritesDMG(uint8_t color, uint8_t tCol) { uint8_t cSpriteByte3 = PPU_OAM2[(i<<2)+3]; uint8_t tVal = PPU_OAM2[(i<<2)+2]; - uint16_t tPos = tVal*16; uint8_t OAMcYpos = PPU_OAM2[(i<<2)]; uint8_t cmpYPos = OAMcYpos-16; @@ -439,12 +453,15 @@ static uint8_t ppuDoSpritesDMG(uint8_t color, uint8_t tCol) cSpriteAdd = 16; cSpriteY &= 7; } + if(PPU_Reg[0] & PPU_SPRITE_8_16) + tVal &= ~1; //clear low bit since its ALL 8 by 16 (2x the space) if(cSpriteByte3 & PPU_TILE_FLIP_Y) { cSpriteY ^= 7; if(PPU_Reg[0] & PPU_SPRITE_8_16) cSpriteAdd ^= 16; //8 by 16 select } + uint16_t tPos = tVal*16; tPos+=(cSpriteY)*2; ChrRegA = PPU_VRAM[(tPos+cSpriteAdd)&0x1FFF]; @@ -578,7 +595,6 @@ static uint16_t ppuDoSpritesCGB(uint8_t color, uint16_t cgbRGB) uint8_t cSpriteByte3 = PPU_OAM2[(i<<2)+3]; uint16_t tCgbBank = (cSpriteByte3&PPU_TILE_CGB_BANK)?0x2000:0x0; uint8_t tVal = PPU_OAM2[(i<<2)+2]; - uint16_t tPos = tVal*16; uint8_t OAMcYpos = PPU_OAM2[(i<<2)]; uint8_t cmpYPos = OAMcYpos-16; @@ -589,12 +605,15 @@ static uint16_t ppuDoSpritesCGB(uint8_t color, uint16_t cgbRGB) cSpriteAdd = 16; cSpriteY &= 7; } + if(PPU_Reg[0] & PPU_SPRITE_8_16) + tVal &= ~1; //clear low bit since its ALL 8 by 16 (2x the space) if(cSpriteByte3 & PPU_TILE_FLIP_Y) { cSpriteY ^= 7; if(PPU_Reg[0] & PPU_SPRITE_8_16) cSpriteAdd ^= 16; //8 by 16 select } + uint16_t tPos = tVal*16; tPos+=(cSpriteY)*2; ChrRegA = PPU_VRAM[tCgbBank|((tPos+cSpriteAdd)&0x1FFF)];