2017-05-12 03:22:59 +00:00
|
|
|
/*
|
|
|
|
* 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>
|
2020-12-28 05:40:21 +00:00
|
|
|
#include <stdlib.h>
|
2017-05-27 14:04:25 +00:00
|
|
|
#include <string.h>
|
2017-05-12 03:22:59 +00:00
|
|
|
#include "apu.h"
|
|
|
|
#include "audio.h"
|
|
|
|
#include "mem.h"
|
|
|
|
#include "cpu.h"
|
|
|
|
|
2017-05-29 03:49:02 +00:00
|
|
|
//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)
|
2017-05-29 03:19:19 +00:00
|
|
|
|
2017-05-29 03:49:02 +00:00
|
|
|
//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)
|
2017-05-12 03:22:59 +00:00
|
|
|
|
2017-05-12 22:46:44 +00:00
|
|
|
static uint8_t APU_IO_Reg[0x50];
|
2017-05-29 22:18:53 +00:00
|
|
|
#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
|
2017-05-29 21:09:21 +00:00
|
|
|
static int32_t lpVal;
|
|
|
|
static int32_t hpVal;
|
|
|
|
static int16_t *apuOutBuf;
|
2017-05-29 22:18:53 +00:00
|
|
|
#endif
|
2017-05-12 03:22:59 +00:00
|
|
|
static uint32_t apuBufSize;
|
|
|
|
static uint32_t apuBufSizeBytes;
|
|
|
|
static uint32_t curBufPos;
|
|
|
|
static uint32_t apuFrequency;
|
|
|
|
static uint16_t freq1;
|
|
|
|
static uint16_t freq2;
|
2017-05-12 14:20:23 +00:00
|
|
|
static uint16_t wavFreq;
|
2017-05-12 03:22:59 +00:00
|
|
|
static uint16_t noiseFreq;
|
|
|
|
static uint16_t noiseShiftReg;
|
|
|
|
static uint8_t p1LengthCtr, p2LengthCtr, noiseLengthCtr;
|
2017-05-12 22:46:44 +00:00
|
|
|
static uint8_t wavLinearCtr;
|
2017-05-12 14:20:23 +00:00
|
|
|
static uint16_t wavLengthCtr;
|
|
|
|
static uint8_t wavVolShift;
|
2018-10-31 21:33:10 +00:00
|
|
|
static uint16_t modeCurCtr;
|
|
|
|
static uint8_t modePos;
|
2017-05-12 14:20:23 +00:00
|
|
|
static uint16_t p1freqCtr, p2freqCtr, wavFreqCtr, noiseFreqCtr;
|
|
|
|
static uint8_t p1Cycle, p2Cycle, wavCycle;
|
|
|
|
static bool p1haltloop, p2haltloop, wavhaltloop, noisehaltloop;
|
2017-05-12 22:46:44 +00:00
|
|
|
static bool p1dacenable, p2dacenable, wavdacenable, noisedacenable;
|
|
|
|
static bool p1enable, p2enable, wavenable, noiseenable;
|
|
|
|
static bool soundEnabled;
|
2017-05-12 03:22:59 +00:00
|
|
|
static bool noiseMode1;
|
2017-06-08 01:09:10 +00:00
|
|
|
static bool wavEqual;
|
2017-05-12 03:22:59 +00:00
|
|
|
|
|
|
|
static envelope_t p1Env, p2Env, noiseEnv;
|
|
|
|
|
|
|
|
typedef struct _sweep_t {
|
|
|
|
bool enabled;
|
|
|
|
bool negative;
|
2017-05-15 00:37:48 +00:00
|
|
|
bool inNegative;
|
2017-05-12 03:22:59 +00:00
|
|
|
uint8_t period;
|
|
|
|
uint8_t divider;
|
|
|
|
uint8_t shift;
|
2017-05-15 00:37:48 +00:00
|
|
|
uint16_t pfreq;
|
2017-05-12 03:22:59 +00:00
|
|
|
} sweep_t;
|
|
|
|
|
2017-05-12 22:46:44 +00:00
|
|
|
static sweep_t p1Sweep;
|
2017-05-12 03:22:59 +00:00
|
|
|
|
|
|
|
//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,
|
|
|
|
};
|
|
|
|
|
2017-05-26 05:52:21 +00:00
|
|
|
//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,
|
|
|
|
};
|
|
|
|
|
2017-05-12 03:22:59 +00:00
|
|
|
//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
|
2017-05-26 05:52:21 +00:00
|
|
|
//apuFrequency = 526680;
|
|
|
|
//effective frequency for original LCD Video out
|
2017-05-27 01:11:58 +00:00
|
|
|
apuFrequency = 262144;
|
2017-05-12 03:22:59 +00:00
|
|
|
double dt = 1.0/((double)apuFrequency);
|
2017-05-29 22:18:53 +00:00
|
|
|
|
2018-10-31 21:33:10 +00:00
|
|
|
//LP at 20kHz
|
|
|
|
double rc = 1.0/(M_2_PI * 20000.0);
|
2017-05-29 22:18:53 +00:00
|
|
|
#if AUDIO_FLOAT
|
|
|
|
lpVal = dt / (rc + dt);
|
|
|
|
#else
|
2017-05-29 21:09:21 +00:00
|
|
|
//convert to 32bit int for calcs later
|
2017-05-29 22:18:53 +00:00
|
|
|
lpVal = (int32_t)((dt / (rc + dt))*32768.0);
|
|
|
|
#endif
|
2018-10-31 21:33:10 +00:00
|
|
|
//HP at 20Hz for GB/GBA (150Hz for GBC)
|
|
|
|
rc = 1.0/(M_2_PI * 20.0);
|
2017-05-29 22:18:53 +00:00
|
|
|
#if AUDIO_FLOAT
|
|
|
|
hpVal = rc / (rc + dt);
|
|
|
|
#else
|
2017-05-29 21:09:21 +00:00
|
|
|
//convert to 32bit int for calcs later
|
2017-05-29 22:18:53 +00:00
|
|
|
hpVal = (int32_t)((rc / (rc + dt))*32768.0);
|
|
|
|
#endif
|
2018-03-06 19:51:40 +00:00
|
|
|
//keep exactly 1 frame buffer
|
|
|
|
apuBufSize = 70224/16*2;
|
2017-05-29 22:18:53 +00:00
|
|
|
#if AUDIO_FLOAT
|
|
|
|
apuBufSizeBytes = apuBufSize*sizeof(float);
|
|
|
|
apuOutBuf = (float*)malloc(apuBufSizeBytes);
|
|
|
|
printf("Audio: 32-bit Float Output\n");
|
|
|
|
#else
|
2017-05-29 21:09:21 +00:00
|
|
|
apuBufSizeBytes = apuBufSize*sizeof(int16_t);
|
|
|
|
apuOutBuf = (int16_t*)malloc(apuBufSizeBytes);
|
2017-05-29 22:18:53 +00:00
|
|
|
printf("Audio: 16-bit Short Output\n");
|
|
|
|
#endif
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void apuDeinitBufs()
|
|
|
|
{
|
|
|
|
if(apuOutBuf)
|
|
|
|
free(apuOutBuf);
|
|
|
|
apuOutBuf = NULL;
|
|
|
|
}
|
|
|
|
|
2018-10-31 21:33:10 +00:00
|
|
|
#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;
|
|
|
|
|
2017-06-07 20:25:30 +00:00
|
|
|
extern bool gbCgbMode;
|
2017-08-09 00:48:59 +00:00
|
|
|
extern bool gbCgbBootrom;
|
2017-05-12 03:22:59 +00:00
|
|
|
void apuInit()
|
|
|
|
{
|
2017-05-12 22:46:44 +00:00
|
|
|
memset(APU_IO_Reg,0,0x50);
|
2017-06-07 20:25:30 +00:00
|
|
|
if(gbCgbMode) //essentially 50% duty pulse on CGB
|
2017-05-26 05:52:21 +00:00
|
|
|
memcpy(APU_IO_Reg+0x30,startWavSetCGB,0x10);
|
|
|
|
else //relatively random audio pattern on DMG
|
|
|
|
memcpy(APU_IO_Reg+0x30,startWavSetDMG,0x10);
|
2017-05-12 03:22:59 +00:00
|
|
|
memset(apuOutBuf, 0, apuBufSizeBytes);
|
|
|
|
curBufPos = 0;
|
|
|
|
|
2018-10-31 21:33:10 +00:00
|
|
|
modeCurCtr = 0;
|
|
|
|
modePos = 0;
|
|
|
|
|
2017-05-12 22:46:44 +00:00
|
|
|
freq1 = 0; freq2 = 0; wavFreq = 0; noiseFreq = 0;
|
2018-10-31 21:33:10 +00:00
|
|
|
noiseShiftReg = 0;
|
2017-05-12 03:22:59 +00:00
|
|
|
p1LengthCtr = 0; p2LengthCtr = 0;
|
2017-05-12 14:20:23 +00:00
|
|
|
noiseLengthCtr = 0; wavLengthCtr = 0;
|
2017-05-12 22:46:44 +00:00
|
|
|
wavLinearCtr = 0;
|
2017-05-12 14:20:23 +00:00
|
|
|
p1freqCtr = 0; p2freqCtr = 0; wavFreqCtr = 0, noiseFreqCtr = 0;
|
|
|
|
p1Cycle = 0; p2Cycle = 0; wavCycle = 0;
|
2017-05-27 01:11:58 +00:00
|
|
|
wavVolShift = 4; //default
|
2017-05-12 03:22:59 +00:00
|
|
|
|
|
|
|
memset(&p1Env,0,sizeof(envelope_t));
|
|
|
|
memset(&p2Env,0,sizeof(envelope_t));
|
|
|
|
memset(&noiseEnv,0,sizeof(envelope_t));
|
|
|
|
|
|
|
|
memset(&p1Sweep,0,sizeof(sweep_t));
|
|
|
|
|
2017-08-09 00:48:59 +00:00
|
|
|
p1haltloop = false; p2haltloop = false;
|
2017-05-12 14:20:23 +00:00
|
|
|
wavhaltloop = false; noisehaltloop = false;
|
2017-05-12 22:46:44 +00:00
|
|
|
p1enable = false; p2enable = false;
|
|
|
|
wavenable = false; noiseenable = false;
|
|
|
|
p1dacenable = false; p2dacenable = false;
|
|
|
|
wavdacenable = false; noisedacenable = false;
|
2017-05-12 03:22:59 +00:00
|
|
|
noiseMode1 = false;
|
2017-06-08 01:09:10 +00:00
|
|
|
wavEqual = false;
|
2017-08-09 00:48:59 +00:00
|
|
|
if(gbCgbBootrom)
|
|
|
|
soundEnabled = false;
|
|
|
|
else //GB Bootrom
|
|
|
|
{
|
|
|
|
soundEnabled = true;
|
|
|
|
APU_IO_Reg[0x24] = 0x77;
|
|
|
|
APU_IO_Reg[0x25] = 0xF3;
|
|
|
|
}
|
2017-05-12 03:22:59 +00:00
|
|
|
|
2018-10-31 21:33:10 +00:00
|
|
|
lastHPOutLeft = 0, lastHPOutRight = 0, lastLPOutLeft = 0, lastLPOutRight = 0;
|
|
|
|
curP1Out = 0, curP2Out = 0, curWavOut = 0, curNoiseOut = 0;
|
|
|
|
}
|
2017-05-12 03:22:59 +00:00
|
|
|
|
|
|
|
bool apuCycle()
|
|
|
|
{
|
|
|
|
if(curBufPos == apuBufSize)
|
|
|
|
{
|
2018-03-06 19:51:40 +00:00
|
|
|
#ifndef __LIBRETRO__
|
2017-05-12 03:22:59 +00:00
|
|
|
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;
|
|
|
|
}
|
2018-03-06 19:51:40 +00:00
|
|
|
#endif
|
2017-05-12 03:22:59 +00:00
|
|
|
curBufPos = 0;
|
|
|
|
}
|
2018-10-31 21:33:10 +00:00
|
|
|
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);
|
2017-05-29 03:19:19 +00:00
|
|
|
if(p1enable && p1dacenable)
|
2017-05-12 03:22:59 +00:00
|
|
|
{
|
2018-10-31 21:33:10 +00:00
|
|
|
if(p1seq[p1Cycle])
|
2017-05-29 03:19:19 +00:00
|
|
|
curP1Out = p1Env.curVol;
|
|
|
|
else
|
|
|
|
curP1Out = 0;
|
2018-10-31 21:33:10 +00:00
|
|
|
//actually audible output
|
|
|
|
if(freq1 > 0 && freq1 < 0x7FF)
|
|
|
|
{
|
|
|
|
//GB/GBC Behavior
|
|
|
|
//p1Out = (curP1Out<<1)-15;
|
|
|
|
//GBA Behavior
|
|
|
|
p1Out = (curP1Out<<1)-p1Env.curVol;
|
2017-05-27 01:11:58 +00:00
|
|
|
}
|
2017-05-29 03:19:19 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
curP1Out = 0;
|
2018-10-31 21:33:10 +00:00
|
|
|
if(APU_IO_Reg[0x25] & P1_ENABLE_LEFT)
|
|
|
|
p1OutLeft = p1Out;
|
|
|
|
if(APU_IO_Reg[0x25] & P1_ENABLE_RIGHT)
|
|
|
|
p1OutRight = p1Out;
|
2017-05-29 03:19:19 +00:00
|
|
|
if(p2enable && p2dacenable)
|
|
|
|
{
|
2018-10-31 21:33:10 +00:00
|
|
|
if(p2seq[p2Cycle])
|
2017-05-29 03:19:19 +00:00
|
|
|
curP2Out = p2Env.curVol;
|
|
|
|
else
|
|
|
|
curP2Out = 0;
|
2018-10-31 21:33:10 +00:00
|
|
|
//actually audible output
|
|
|
|
if(freq2 > 0 && freq2 < 0x7FF)
|
|
|
|
{
|
|
|
|
//GB/GBC Behavior
|
|
|
|
//p2Out = (curP2Out<<1)-15;
|
|
|
|
//GBA Behavior
|
|
|
|
p2Out = (curP2Out<<1)-p2Env.curVol;
|
2017-05-29 03:19:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
curP2Out = 0;
|
2018-10-31 21:33:10 +00:00
|
|
|
if(APU_IO_Reg[0x25] & P2_ENABLE_LEFT)
|
|
|
|
p2OutLeft = p2Out;
|
|
|
|
if(APU_IO_Reg[0x25] & P2_ENABLE_RIGHT)
|
|
|
|
p2OutRight = p2Out;
|
2017-05-29 03:19:19 +00:00
|
|
|
if(wavenable && wavdacenable)
|
|
|
|
{
|
2018-10-31 21:33:10 +00:00
|
|
|
curWavOut = APU_IO_Reg[0x30+(wavCycle>>1)];
|
2017-05-29 03:19:19 +00:00
|
|
|
if((wavCycle&1)==0)
|
2018-10-31 21:33:10 +00:00
|
|
|
curWavOut >>= 4;
|
2017-05-29 03:19:19 +00:00
|
|
|
else
|
2018-10-31 21:33:10 +00:00
|
|
|
curWavOut &= 0xF;
|
|
|
|
curWavOut >>= wavVolShift;
|
|
|
|
//actually audible output
|
|
|
|
if((wavFreq > 0 && wavFreq < 0x7FF) || wavEqual)
|
|
|
|
wavOut = (curWavOut<<1)-15;
|
2017-05-29 03:19:19 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
curWavOut = 0;
|
2018-10-31 21:33:10 +00:00
|
|
|
if(APU_IO_Reg[0x25] & WAV_ENABLE_LEFT)
|
|
|
|
wavOutLeft = wavOut;
|
|
|
|
if(APU_IO_Reg[0x25] & WAV_ENABLE_RIGHT)
|
|
|
|
wavOutRight = wavOut;
|
2017-05-29 03:19:19 +00:00
|
|
|
if(noiseenable && noisedacenable)
|
|
|
|
{
|
2018-10-31 21:33:10 +00:00
|
|
|
if((noiseShiftReg&1) == 0)
|
2017-05-29 03:19:19 +00:00
|
|
|
curNoiseOut = noiseEnv.curVol;
|
|
|
|
else
|
|
|
|
curNoiseOut = 0;
|
2018-10-31 21:33:10 +00:00
|
|
|
//actually audible output
|
|
|
|
if(noiseFreq > 0)
|
|
|
|
{
|
|
|
|
//GB/GBC Behavior
|
|
|
|
//noiseOut = (curNoiseOut<<1)-15;
|
|
|
|
//GBA Behavior
|
|
|
|
noiseOut = (curNoiseOut<<1)-noiseEnv.curVol;
|
2017-05-27 01:11:58 +00:00
|
|
|
}
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
2017-05-29 03:19:19 +00:00
|
|
|
else
|
|
|
|
curNoiseOut = 0;
|
2018-10-31 21:33:10 +00:00
|
|
|
if(APU_IO_Reg[0x25] & NOISE_ENABLE_LEFT)
|
|
|
|
noiseOutLeft = noiseOut;
|
|
|
|
if(APU_IO_Reg[0x25] & NOISE_ENABLE_RIGHT)
|
|
|
|
noiseOutRight = noiseOut;
|
2017-05-29 22:18:53 +00:00
|
|
|
#if AUDIO_FLOAT
|
|
|
|
//gen output Left
|
2018-10-31 21:33:10 +00:00
|
|
|
float curInLeft = ((float)(p1OutLeft + p2OutLeft + wavOutLeft + noiseOutLeft))*volLevel[apuMasterVolLeft]/85.333333f;
|
2017-05-29 22:18:53 +00:00
|
|
|
float curLPOutLeft = lastLPOutLeft+(lpVal*(curInLeft-lastLPOutLeft));
|
|
|
|
float curHPOutLeft = hpVal*(lastHPOutLeft+lastLPOutLeft-curLPOutLeft);
|
|
|
|
//gen output Right
|
2018-10-31 21:33:10 +00:00
|
|
|
float curInRight = ((float)(p1OutRight + p2OutRight + wavOutRight + noiseOutRight))*volLevel[apuMasterVolRight]/85.333333f;
|
2017-05-29 22:18:53 +00:00
|
|
|
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
|
2017-05-31 20:20:46 +00:00
|
|
|
int32_t curIn, curOut;
|
2017-05-29 03:19:19 +00:00
|
|
|
//gen output Left
|
2018-10-31 21:33:10 +00:00
|
|
|
curIn = (p1OutLeft + p2OutLeft + wavOutLeft + noiseOutLeft)*(apuMasterVolLeft+1)*48;
|
2017-05-31 20:20:46 +00:00
|
|
|
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);
|
2017-05-29 03:19:19 +00:00
|
|
|
//gen output Right
|
2018-10-31 21:33:10 +00:00
|
|
|
curIn = (p1OutRight + p2OutRight + wavOutRight + noiseOutRight)*(apuMasterVolRight+1)*48;
|
2017-05-31 20:20:46 +00:00
|
|
|
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);
|
2017-05-29 22:18:53 +00:00
|
|
|
#endif
|
2017-05-12 03:22:59 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-03-06 19:51:40 +00:00
|
|
|
#ifdef __LIBRETRO__
|
|
|
|
void audioFrameEnd(int samples);
|
|
|
|
void apuFrameEnd()
|
|
|
|
{
|
|
|
|
audioFrameEnd(curBufPos>>1);
|
|
|
|
curBufPos = 0;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-05-12 03:22:59 +00:00
|
|
|
void doEnvelopeLogic(envelope_t *env)
|
|
|
|
{
|
|
|
|
if(env->divider == 0)
|
|
|
|
{
|
|
|
|
if(env->period)
|
|
|
|
{
|
|
|
|
if(env->modeadd)
|
|
|
|
{
|
2017-05-26 05:52:21 +00:00
|
|
|
if(env->curVol < 15)
|
|
|
|
env->curVol++;
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-05-26 05:52:21 +00:00
|
|
|
if(env->curVol > 0)
|
|
|
|
env->curVol--;
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
}
|
2017-05-15 00:37:48 +00:00
|
|
|
//period 0 is actually period 8!
|
|
|
|
env->divider = (env->period-1)&7;
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
env->divider--;
|
|
|
|
}
|
|
|
|
|
2017-05-15 00:37:48 +00:00
|
|
|
void sweepUpdateFreq(sweep_t *sw, uint16_t *freq, bool update)
|
2017-05-12 03:22:59 +00:00
|
|
|
{
|
2017-05-15 00:37:48 +00:00
|
|
|
if(!sw->enabled)
|
|
|
|
return;
|
2017-05-12 22:46:44 +00:00
|
|
|
//printf("%i\n", *freq);
|
2017-05-15 00:37:48 +00:00
|
|
|
uint16_t inFreq = sw->pfreq;
|
2017-05-12 22:46:44 +00:00
|
|
|
uint16_t shiftVal = (inFreq >> sw->shift);
|
2017-05-15 00:37:48 +00:00
|
|
|
|
|
|
|
if(sw->negative)
|
2017-05-12 03:22:59 +00:00
|
|
|
{
|
2017-05-15 00:37:48 +00:00
|
|
|
sw->inNegative = true;
|
|
|
|
inFreq -= shiftVal;
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
2017-05-15 00:37:48 +00:00
|
|
|
else
|
|
|
|
inFreq += shiftVal;
|
|
|
|
|
2017-05-12 22:46:44 +00:00
|
|
|
if(inFreq <= 0x7FF)
|
2017-05-12 03:22:59 +00:00
|
|
|
{
|
2017-05-15 00:37:48 +00:00
|
|
|
if(sw->enabled && sw->shift && sw->period && update)
|
|
|
|
{
|
2017-05-12 03:22:59 +00:00
|
|
|
*freq = inFreq;
|
2017-05-15 00:37:48 +00:00
|
|
|
sw->pfreq = inFreq;
|
|
|
|
}
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
else
|
2017-05-12 22:46:44 +00:00
|
|
|
{
|
|
|
|
//printf("Freq disabled\n");
|
|
|
|
p1enable = false;
|
|
|
|
}
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void doSweepLogic(sweep_t *sw, uint16_t *freq)
|
|
|
|
{
|
2017-05-12 22:46:44 +00:00
|
|
|
if(sw->divider == 0)
|
2017-05-12 03:22:59 +00:00
|
|
|
{
|
2017-05-15 00:37:48 +00:00
|
|
|
//printf("Divider 0\n");
|
2017-05-12 22:46:44 +00:00
|
|
|
if(sw->period)
|
2017-05-12 03:22:59 +00:00
|
|
|
{
|
2017-05-15 00:37:48 +00:00
|
|
|
sweepUpdateFreq(sw, freq, true);
|
2017-05-12 22:46:44 +00:00
|
|
|
//gameboy checks a SECOND time after updating...
|
2017-05-15 00:37:48 +00:00
|
|
|
uint16_t inFreq = sw->pfreq;
|
2017-05-12 22:46:44 +00:00
|
|
|
uint16_t shiftVal = (inFreq >> sw->shift);
|
|
|
|
if(sw->negative)
|
|
|
|
inFreq -= shiftVal;
|
|
|
|
else
|
|
|
|
inFreq += shiftVal;
|
|
|
|
if(inFreq > 0x7FF)
|
|
|
|
{
|
|
|
|
//printf("Freq disabled\n");
|
|
|
|
p1enable = false;
|
|
|
|
}
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
2017-05-15 00:37:48 +00:00
|
|
|
//period 0 is actually period 8!
|
|
|
|
sw->divider = (sw->period-1)&7;
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
else
|
2017-05-12 22:46:44 +00:00
|
|
|
sw->divider--;
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void apuClockA()
|
|
|
|
{
|
2017-05-12 22:46:44 +00:00
|
|
|
//printf("Len clock\n");
|
|
|
|
if(p1LengthCtr && !p1haltloop)
|
2017-05-12 03:22:59 +00:00
|
|
|
{
|
2017-05-12 22:46:44 +00:00
|
|
|
p1LengthCtr--;
|
|
|
|
if(p1LengthCtr == 0)
|
|
|
|
p1enable = false;
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
if(p2LengthCtr && !p2haltloop)
|
2017-05-12 22:46:44 +00:00
|
|
|
{
|
2017-05-12 03:22:59 +00:00
|
|
|
p2LengthCtr--;
|
2017-05-12 22:46:44 +00:00
|
|
|
if(p2LengthCtr == 0)
|
|
|
|
p2enable = false;
|
|
|
|
}
|
2017-05-12 14:20:23 +00:00
|
|
|
if(wavLengthCtr && !wavhaltloop)
|
2017-05-12 22:46:44 +00:00
|
|
|
{
|
2017-05-12 14:20:23 +00:00
|
|
|
wavLengthCtr--;
|
2017-05-12 22:46:44 +00:00
|
|
|
if(wavLengthCtr == 0)
|
|
|
|
wavenable = false;
|
|
|
|
}
|
2017-05-12 03:22:59 +00:00
|
|
|
if(noiseLengthCtr && !noisehaltloop)
|
2017-05-12 22:46:44 +00:00
|
|
|
{
|
2017-05-12 03:22:59 +00:00
|
|
|
noiseLengthCtr--;
|
2017-05-12 22:46:44 +00:00
|
|
|
if(noiseLengthCtr == 0)
|
|
|
|
noiseenable = false;
|
|
|
|
}
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void apuClockB()
|
|
|
|
{
|
|
|
|
if(p1LengthCtr)
|
|
|
|
doEnvelopeLogic(&p1Env);
|
|
|
|
if(p2LengthCtr)
|
|
|
|
doEnvelopeLogic(&p2Env);
|
|
|
|
if(noiseLengthCtr)
|
|
|
|
doEnvelopeLogic(&noiseEnv);
|
|
|
|
}
|
|
|
|
|
2017-05-27 01:11:58 +00:00
|
|
|
void apuClockTimers()
|
2017-05-12 03:22:59 +00:00
|
|
|
{
|
|
|
|
if(modeCurCtr == 0)
|
|
|
|
{
|
|
|
|
modePos++;
|
2017-05-12 22:46:44 +00:00
|
|
|
if(modePos&1)
|
|
|
|
apuClockA();
|
|
|
|
if(modePos == 3 || modePos == 7)
|
|
|
|
{
|
|
|
|
//printf("sweep clock\n");
|
|
|
|
if(p1LengthCtr)
|
|
|
|
doSweepLogic(&p1Sweep, &freq1);
|
|
|
|
}
|
|
|
|
if(modePos >= 8)
|
2017-05-12 03:22:59 +00:00
|
|
|
{
|
|
|
|
apuClockB();
|
|
|
|
modePos = 0;
|
|
|
|
}
|
2017-05-27 01:11:58 +00:00
|
|
|
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;
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
2017-05-27 01:11:58 +00:00
|
|
|
if(wavFreqCtr)
|
|
|
|
wavFreqCtr--;
|
|
|
|
|
|
|
|
if(noiseFreqCtr == 0)
|
|
|
|
{
|
|
|
|
noiseFreqCtr = noiseFreq;
|
2018-10-31 21:33:10 +00:00
|
|
|
uint8_t cmpRes = (noiseShiftReg&1)^((noiseShiftReg>>1)&1);
|
2017-05-27 01:11:58 +00:00
|
|
|
noiseShiftReg >>= 1;
|
2018-10-31 21:33:10 +00:00
|
|
|
noiseShiftReg |= cmpRes << (noiseMode1 ? 6 : 14);
|
2017-05-27 01:11:58 +00:00
|
|
|
}
|
|
|
|
if(noiseFreqCtr)
|
|
|
|
noiseFreqCtr--;
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
|
2017-05-27 01:11:58 +00:00
|
|
|
void apuSetReg8(uint16_t addr, uint8_t val)
|
2017-05-12 03:22:59 +00:00
|
|
|
{
|
2017-05-27 01:11:58 +00:00
|
|
|
uint8_t reg = addr&0xFF;
|
2017-05-12 22:46:44 +00:00
|
|
|
//printf("APU set %02x %02x\n", reg, val);
|
|
|
|
if(reg == 0x26)
|
|
|
|
{
|
2017-05-26 05:52:21 +00:00
|
|
|
bool wasEnabled = soundEnabled;
|
2017-05-12 22:46:44 +00:00
|
|
|
soundEnabled = (val&0x80)!=0;
|
|
|
|
if(!soundEnabled)
|
|
|
|
{
|
|
|
|
// FULL reset of nearly every reg
|
|
|
|
memset(APU_IO_Reg,0,0x30);
|
2017-05-26 05:52:21 +00:00
|
|
|
// except for the wav buffer
|
2017-05-12 22:46:44 +00:00
|
|
|
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;
|
2017-05-27 01:11:58 +00:00
|
|
|
wavVolShift = 4; //default
|
|
|
|
}
|
|
|
|
else
|
2017-05-26 05:52:21 +00:00
|
|
|
{
|
2017-05-27 01:11:58 +00:00
|
|
|
APU_IO_Reg[0x26] = val;
|
|
|
|
//on sound powerup, reset frame sequencer
|
|
|
|
if(!wasEnabled)
|
|
|
|
{
|
|
|
|
modeCurCtr = 8192;
|
|
|
|
modePos = 0;
|
|
|
|
}
|
2017-05-12 22:46:44 +00:00
|
|
|
}
|
2017-05-27 01:11:58 +00:00
|
|
|
return;
|
2017-05-12 22:46:44 +00:00
|
|
|
}
|
2017-05-26 05:52:21 +00:00
|
|
|
//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;
|
2017-06-08 01:09:10 +00:00
|
|
|
//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)) );
|
2017-05-26 05:52:21 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-05-27 01:11:58 +00:00
|
|
|
//dont even bother with the switch if sound is off
|
|
|
|
else if(!soundEnabled)
|
2017-05-12 22:46:44 +00:00
|
|
|
return;
|
2017-05-27 01:11:58 +00:00
|
|
|
bool p1prevhaltloop, p2prevhaltloop,
|
|
|
|
wavprevhaltloop, noiseprevhaltloop;
|
2017-05-12 03:22:59 +00:00
|
|
|
APU_IO_Reg[reg] = val;
|
2017-05-27 01:11:58 +00:00
|
|
|
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)
|
2017-05-26 05:52:21 +00:00
|
|
|
p1enable = false;
|
2017-05-27 01:11:58 +00:00
|
|
|
break;
|
|
|
|
case 0x11:
|
|
|
|
p1seq = pulseSeqs[val>>6];
|
|
|
|
p1LengthCtr = 64-(val&0x3F);
|
|
|
|
break;
|
|
|
|
case 0x12:
|
|
|
|
p1Env.vol = (val>>4)&0xF;
|
|
|
|
p1Env.modeadd = (val&8)!=0;
|
2017-09-08 23:48:34 +00:00
|
|
|
if(p1Env.modeadd && p1Env.period == 0 && (val&7) == 0)
|
|
|
|
{
|
|
|
|
//"Zombie" Mode
|
|
|
|
p1Env.curVol++;
|
|
|
|
p1Env.curVol &= 0xF;
|
|
|
|
}
|
2017-05-27 01:11:58 +00:00
|
|
|
p1dacenable = (p1Env.modeadd || p1Env.vol);
|
|
|
|
if(!p1dacenable)
|
|
|
|
p1enable = false;
|
|
|
|
p1Env.period = val&7;
|
|
|
|
break;
|
|
|
|
case 0x13:
|
|
|
|
freq1 = ((freq1&~0xFF) | val);
|
2017-05-29 21:09:21 +00:00
|
|
|
//printf("P1 new freq %04x\n", freq1);
|
2017-05-27 01:11:58 +00:00
|
|
|
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))
|
2017-05-26 05:52:21 +00:00
|
|
|
{
|
2017-05-27 01:11:58 +00:00
|
|
|
p1LengthCtr--;
|
|
|
|
//disable channel immediately if length
|
|
|
|
//reached 0 from this extra clock
|
|
|
|
if(p1LengthCtr == 0)
|
|
|
|
p1enable = false;
|
2017-05-26 05:52:21 +00:00
|
|
|
}
|
2017-05-27 01:11:58 +00:00
|
|
|
if(val&(1<<7))
|
2017-05-26 05:52:21 +00:00
|
|
|
{
|
2017-05-27 01:11:58 +00:00
|
|
|
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);
|
2017-05-26 05:52:21 +00:00
|
|
|
}
|
2017-05-27 01:11:58 +00:00
|
|
|
//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;
|
2017-09-08 23:48:34 +00:00
|
|
|
if(p2Env.modeadd && p2Env.period == 0 && (val&7) == 0)
|
|
|
|
{
|
|
|
|
//"Zombie" Mode
|
|
|
|
p2Env.curVol++;
|
|
|
|
p2Env.curVol &= 0xF;
|
|
|
|
}
|
2017-05-27 01:11:58 +00:00
|
|
|
p2dacenable = (p2Env.modeadd || p2Env.vol);
|
|
|
|
if(!p2dacenable)
|
|
|
|
p2enable = false;
|
|
|
|
p2Env.period = val&7;
|
|
|
|
break;
|
|
|
|
case 0x18:
|
|
|
|
freq2 = ((freq2&~0xFF) | val);
|
2017-05-29 21:09:21 +00:00
|
|
|
//printf("P2 new freq %04x\n", freq2);
|
2017-05-27 01:11:58 +00:00
|
|
|
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)
|
2017-05-26 05:52:21 +00:00
|
|
|
wavenable = false;
|
2017-05-27 01:11:58 +00:00
|
|
|
break;
|
|
|
|
case 0x1B:
|
|
|
|
wavLengthCtr = 256-val;
|
|
|
|
break;
|
|
|
|
case 0x1C:
|
|
|
|
//printf("wavVolShift %i\n", (val>>5)&3);
|
|
|
|
switch((val>>5)&3)
|
2017-05-26 05:52:21 +00:00
|
|
|
{
|
2017-05-27 01:11:58 +00:00
|
|
|
case 0:
|
|
|
|
wavVolShift=4;
|
|
|
|
break;
|
|
|
|
case 1:
|
|
|
|
wavVolShift=0;
|
|
|
|
break;
|
|
|
|
case 2:
|
|
|
|
wavVolShift=1;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
wavVolShift=2;
|
|
|
|
break;
|
2017-05-26 05:52:21 +00:00
|
|
|
}
|
2017-05-27 01:11:58 +00:00
|
|
|
break;
|
|
|
|
case 0x1D:
|
|
|
|
wavFreq = ((wavFreq&~0xFF) | val);
|
2017-05-29 21:09:21 +00:00
|
|
|
//printf("wav new freq %04x\n", wavFreq);
|
2017-05-27 01:11:58 +00:00
|
|
|
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))
|
2017-05-26 05:52:21 +00:00
|
|
|
{
|
2017-05-27 01:11:58 +00:00
|
|
|
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;
|
2017-05-26 05:52:21 +00:00
|
|
|
}
|
2017-05-27 01:11:58 +00:00
|
|
|
//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;
|
2017-09-08 23:48:34 +00:00
|
|
|
if(noiseEnv.modeadd && noiseEnv.period == 0 && (val&7) == 0)
|
|
|
|
{
|
|
|
|
//"Zombie" Mode
|
|
|
|
noiseEnv.curVol++;
|
|
|
|
noiseEnv.curVol &= 0xF;
|
|
|
|
}
|
2017-05-27 01:11:58 +00:00
|
|
|
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;
|
2018-10-31 21:33:10 +00:00
|
|
|
//trigger sets all shift reg bits
|
|
|
|
noiseShiftReg = 0x7FFF;
|
2017-05-27 01:11:58 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-12 22:46:44 +00:00
|
|
|
//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,
|
|
|
|
};
|
|
|
|
|
2017-05-27 01:11:58 +00:00
|
|
|
uint8_t apuGetReg8(uint16_t addr)
|
2017-05-12 03:22:59 +00:00
|
|
|
{
|
2017-05-27 01:11:58 +00:00
|
|
|
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;
|
2017-05-12 03:22:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t *apuGetBuf()
|
|
|
|
{
|
|
|
|
return (uint8_t*)apuOutBuf;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t apuGetBufSize()
|
|
|
|
{
|
|
|
|
return apuBufSizeBytes;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t apuGetFrequency()
|
|
|
|
{
|
|
|
|
return apuFrequency;
|
|
|
|
}
|