I've been playing with trying to fix this on and off for a long time, but it's just really getting on my nerves and it seriously needs to get fixed. So, there are multiple issues. Right now I'm most interested in fixing the most annoying one. Sometimes my square channels continue to output a high tone when they should be silent. It's pretty rare in a game, but I've got a chiptune NSF that I converted to a NES by zanzan that demonstrates it right at the beginning, and continues for the first 20 to 25 seconds of the song. I've attached a ZIP of the current win32 build (works in WINE too) of my emulator along with that NES ROM to play it so you can see what I mean. After the first part of the song, the rest of it sounds pretty much correct compared to other emulators.
This is my APU code, some of it's a little sloppy but it's readable. I've been wrestling with this for at least a year to no avail. If anybody can spot the problem, it would be amazing. I just can't figure it out. It's probably an easy bug to spot for some of you guys. My second issue is I just can't get my damn sweeps working properly no matter what I do, but one thing at a time! I can deal with sweeps after I fix this square channel bug.
Attachment:
This is my APU code, some of it's a little sloppy but it's readable. I've been wrestling with this for at least a year to no avail. If anybody can spot the problem, it would be amazing. I just can't figure it out. It's probably an easy bug to spot for some of you guys. My second issue is I just can't get my damn sweeps working properly no matter what I do, but one thing at a time! I can deal with sweeps after I fix this square channel bug.
Code:
/* MoarNES source - APU.c
This open-source software is (c)2010-2013 Mike Chambers, and is released
under the terms of the GNU GPL v2 license.
This is my work-in-progess APU emulation code. Sweep functionality
isn't working yet.
*/
#include "config.h"
#include <stdio.h>
#include <malloc.h>
#include "moarnes.h"
uint8_t length_lookup[2][16] = { //the first bracket value should be bit 3, second bracket bits 7 to 4
{ 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x3C, 0x0E, 0x1A, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x48, 0x10, 0x20 },
{ 0xFE, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E}
};
uint32_t noise_lookup[16] = {
0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0,
0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4
};
int8_t triangle_step[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
};
uint32_t dmc_period[16] = {
0x1AC, 0x17C, 0x154, 0x140, 0x11E, 0x0FE, 0x0E2, 0x0D6,
0x0BE, 0x0A0, 0x08E, 0x080, 0x06A, 0x054, 0x048, 0x036
};
uint8_t square_duty[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 }
};
extern uint64_t clockticks6502;
SDL_AudioSpec audio;
uint32_t buffersize, cursamplerate;
uint8_t buf[48000];
uint64_t seqtickgap = 0xFFFFFFFF, seqticknext = 0xFFFFFFFF;
uint64_t sampleticks, cursampleticks, fastsampleticks, nextsamplecycle = 0xFFFFFFFF, nextseqcycle = 0xFFFFFFFF;
uint8_t seqstep = 0, seqmode = 4, interruptAPU = 0;
struct square_s square[2];
struct triangle_s triangle;
struct noise_s noise;
struct dmc_s dmc;
uint8_t allowsweeps = 0; //sweep code is completely buggy right now, and really breaks a lot of stuff. allow sweeps if you want, but be prepared for extreme fail.
int8_t channels[5] = { 0, 0, 0, 0, 0}; //square 1, square 2, triangle, noise, DMC
FILE *wavefile = NULL;
uint8_t readAPU(uint16_t addr) {
uint8_t outbyte;
if (addr == 0x4015) {
if (square[0].enabled && square[0].lengthcounter) outbyte = 1;
else outbyte = 0;
if (square[1].enabled && square[1].lengthcounter) outbyte |= 2;
if (triangle.enabled && triangle.lengthcounter) outbyte |= 4;
if (noise.enabled && noise.lengthcounter) outbyte |= 8;
return(outbyte);
}
return(0);
}
void writeAPU(uint16_t addr, uint8_t value) {
switch (addr) {
case 0x4000: //square 1
square[0].duty = value >> 6;
if (value & 0x20) square[0].lengthhalt = 1;
else square[0].lengthhalt = 0;
if (value & 0x10) square[0].constant = 1;
else square[0].constant = 0;
square[0].envelope = value & 0x0F;
break;
case 0x4001:
if (value & 0x80) { square[0].sweepenable = 1; square[0].sweepresult = square[0].period; }
else { square[0].sweepenable = 0; square[0].sweepresult = square[0].period; }
square[0].sweepperiod = ((value >> 4) & 7) + 1;
if (value & 0x08) square[0].negate = 1;
else square[0].negate = 0;
square[0].sweep = value & 7;
square[0].sweepreload = 1;
break;
case 0x4002:
square[0].period &= 0xFF00;
square[0].period += (uint32_t)value + 1;
square[0].sweepresult = square[0].period;
break;
case 0x4003:
square[0].lengthindex = value >> 3;
square[0].period &= 0xFF;
square[0].period += (uint32_t)(value & 7) << 8;
square[0].dutypos = 0;
squarereload(0);
square[0].wrotebit4 = 1;
square[0].sweepresult = square[0].period;
break;
case 0x4004: //square 2
square[1].duty = value >> 6;
if (value & 0x20) square[1].lengthhalt = 1;
else square[1].lengthhalt = 0;
if (value & 0x10) square[1].constant = 1;
else square[1].constant = 0;
square[1].envelope = value & 0x0F;
break;
case 0x4005:
if (value & 0x80) { square[1].sweepenable = 1; square[1].sweepresult = square[0].period; }
else { square[1].sweepenable = 0; square[1].sweepresult = square[0].period; }
square[1].sweepperiod = ((value >> 4) & 7) + 1;
if (value & 0x08) square[1].negate = 1;
else square[0].negate = 0;
square[1].sweep = value & 7;
square[1].sweepreload = 1;
break;
case 0x4006:
square[1].period &= 0xFF00;
square[1].period += (uint32_t)value;
square[1].sweepresult = square[1].period;
break;
case 0x4007:
square[1].lengthindex = value >> 3;
square[1].period &= 0xFF;
square[1].period += (uint32_t)(value & 7) << 8;
square[1].dutypos = 0;
squarereload(1);
square[1].wrotebit4 = 1;
square[1].sweepresult = square[1].period;
break;
case 0x4008: //triangle
if (value & 0x80) triangle.lengthhalt = 1;
else triangle.lengthhalt = 0;
triangle.linearreload = value & 0x7F;
break;
case 0x400A:
triangle.period &= 0xFF00;
triangle.period += value + 1;
break;
case 0x400B:
triangle.lengthindex = value >> 3;
triangle.period &= 0xFF;
triangle.period += (uint32_t)(value & 7) << 8;
//if (triangle.lengthhalt == 0)
triangle.lengthcounter = length_lookup[triangle.lengthindex & 1][triangle.lengthindex >> 1];
break;
case 0x400C: //noise
if (value & 0x20) noise.lengthhalt = 1;
else noise.lengthhalt = 0;
if (value & 0x10) noise.constant = 1;
else noise.constant = 0;
noise.envreload = value & 0x0F;
noise.envelope = noise.envreload;
break;
case 0x400E:
noise.mode = value >> 7;
noise.period = noise_lookup[value & 0x0F];
break;
case 0x400F:
noise.lengthindex = value >> 3;
noise.lengthcounter = length_lookup[noise.lengthindex & 1][noise.lengthindex >> 1];
noise.wrotebit4 = 1;
break;
case 0x4010: //DMC
if (value & 0x40) dmc.loopmode = 1;
else dmc.loopmode = 0;
dmc.period = dmc_period[value & 0x0F] >> 3;
break;
case 0x4011:
channels[4] = value & 0x7F;
break;
case 0x4012:
dmc.addressreg = value;
dmc.address = ((uint16_t)value << 6) | 0xC000;
break;
case 0x4013:
dmc.lengthreg = value;
dmc.bytesremain = ((uint16_t)value << 4) + 1;
break;
case 0x4015:
if (value & 1) {
square[0].enabled = 1;
} else {
square[0].enabled = 0;
square[0].lengthcounter = 0;
}
if (value & 2) {
square[1].enabled = 1;
} else {
square[1].enabled = 0;
square[1].lengthcounter = 0;
}
if (value & 4) {
triangle.enabled = 1;
} else {
triangle.enabled = 0;
triangle.lengthcounter = 0;
}
if (value & 8) noise.enabled = 1;
else noise.enabled = 0;
if (value & 16) { dmc.enabled = 1; dmc.lasttick = clockticks6502; }
else dmc.enabled = 0;
break;
case 0x4017:
if (value & 0x80) seqmode = 5;
else seqmode = 4;
break;
}
if (square[0].enabled == 0) square[0].lengthcounter = 0;
if (square[1].enabled == 0) square[1].lengthcounter = 0;
if (triangle.enabled == 0) {
triangle.lengthcounter = 0;
triangle.linearcounter = 0;
}
if (noise.enabled == 0) noise.lengthcounter = 0;
}
void squarereload(uint8_t channel) {
square[channel].lengthcounter = length_lookup[square[channel].lengthindex & 1][square[channel].lengthindex >> 1];
}
void envclock() {
if (square[0].constant == 0) {
if (square[0].wrotebit4) {
square[0].envelope = 15;
square[0].wrotebit4 = 0;
} else {
if (square[0].envelope > 0) square[0].envelope--;
}
if ((square[0].envelope == 0) && square[0].envloop) {
square[0].envelope = 15;
}
}
if (square[1].constant == 0) {
if (square[1].wrotebit4) {
square[1].envelope = 15;
square[1].wrotebit4 = 0;
} else {
if (square[1].envelope > 0) square[1].envelope--;
}
if ((square[1].envelope == 0) && square[1].envloop) {
square[1].envelope = 15;
}
}
if (noise.constant == 0) {
if (noise.wrotebit4) {
noise.envelope = 15;
noise.wrotebit4 = 0;
} else {
if (noise.envelope > 0) noise.envelope--;
}
if ((noise.envelope == 0) && noise.lengthhalt) {
noise.envelope = 15;
}
}
}
void linearclock() {
if (triangle.lengthhalt == 0) {
if (triangle.linearcounter > 0) triangle.linearcounter--;
else triangle.linearcounter = triangle.linearreload;
} else {
triangle.linearcounter = triangle.linearreload;
}
}
void lengthclock() {
if (square[0].lengthhalt == 0) {
if (square[0].lengthcounter > 0) square[0].lengthcounter--;
}
if (square[1].lengthhalt == 0) {
if (square[1].lengthcounter > 0) square[1].lengthcounter--;
}
if (triangle.lengthhalt == 0) {
if (triangle.lengthcounter > 0) triangle.lengthcounter--;
}
if (noise.lengthhalt == 0) {
if (noise.lengthcounter > 0) noise.lengthcounter--;
}
}
void sweepclock() {
int32_t s, wl, d[4];
uint8_t chan;
for (chan=0; chan<2; chan++) {
if ((--square[chan].sweep == 0) || square[chan].sweepreload) square[chan].sweep = square[chan].sweepperiod;
if ((square[chan].period >= 8) && square[chan].sweepenable && square[chan].sweep) {
wl = square[chan].period;
s = wl >> square[chan].sweep;
d[0] = s; d[1] = s; d[2] = ~s; d[3] = -s;
wl += d[square[chan].negate*2 + chan];
if (wl < 0x800) square[chan].sweepresult = wl;
else square[chan].sweepresult = 0;
} else square[chan].sweepresult = 0;
if (!allowsweeps) square[chan].sweepresult = square[chan].period;
}
}
void setinterruptAPU() {
}
void frameseqAPU() {
if (clockticks6502 < seqticknext) return;
else seqticknext += seqtickgap;
if (seqmode == 4) {
switch (seqstep) {
case 0:
envclock();
linearclock();
break;
case 1:
lengthclock();
sweepclock();
envclock();
linearclock();
break;
case 2:
envclock();
linearclock();
break;
case 3:
setinterruptAPU();
lengthclock();
sweepclock();
envclock();
linearclock();
break;
}
seqstep = (seqstep + 1) % seqmode;
} else if (seqmode == 5) {
switch (seqstep) {
case 0:
lengthclock();
sweepclock();
envclock();
linearclock();
break;
case 1:
envclock();
linearclock();
break;
case 2:
lengthclock();
sweepclock();
envclock();
linearclock();
break;
case 3:
envclock();
linearclock();
break;
case 4:
break;
}
seqstep = (seqstep + 1) % seqmode;
}
}
void mixerinput(uint8_t channel, uint8_t value) {
channels[channel] = value;
}
int32_t bufferpos = 0, buffersync = 0, audiosync = 0, unpausebufpos;
int8_t last = 0;
int8_t mixeroutAPU() {
double outbyte;
double squareout, tndout;
squareout = 0;
if (square[0].enabled && (square[0].period > 7) && (square[0].lengthcounter > 0)) squareout += channels[0];
if (square[1].enabled && (square[1].period > 7) && (square[1].lengthcounter > 0)) squareout += channels[1];
squareout *= 0.00752;
tndout = 0;
if (triangle.enabled && (triangle.period > 7) && (triangle.lengthcounter > 0) && (triangle.linearcounter > 0)) tndout += channels[2];
tndout = tndout * 0.00851 + 0.00494 * channels[3] + 0.00335 * channels[4];
outbyte = (int16_t)(127 * squareout);
outbyte += (int16_t)(127 * tndout);
return(outbyte);
}
void buffersampleAPU() {
if ((uint32_t)bufferpos < buffersize) {
buf[bufferpos++] = mixeroutAPU();
} else {
SDL_PauseAudio(0);
}
}
void tickchannelsAPU() {
int16_t tmpbit, feedback;
uint32_t squaretarget;
if (clockticks6502 >= nextsamplecycle) {
buffersampleAPU();
nextsamplecycle += fastsampleticks;
}
if ((uint32_t)bufferpos >= buffersize) { //if sample buffer is full, postpone channel ticks
square[0].lasttick = square[1].lasttick = triangle.lasttick = noise.lasttick = dmc.lasttick = clockticks6502;
return;
}
if (clockticks6502 >= nextseqcycle) {
frameseqAPU();
nextseqcycle += seqtickgap;
}
if (square[0].sweepenable) squaretarget = square[0].sweepresult;
else squaretarget = square[0].period;
if ((clockticks6502 - square[0].lasttick) >= (squaretarget << 1)) {
if (square[0].enabled && (squaretarget > 7) && (square[0].lengthcounter > 0)) {
channels[0] = square[0].envelope * square_duty[square[0].duty][square[0].dutypos];
square[0].dutypos = (square[0].dutypos + 1) & 7;
} else channels[0] = 0;
square[0].lasttick = clockticks6502 - ((clockticks6502 - square[0].lasttick) - (squaretarget << 1));
}
if (square[1].sweepenable) squaretarget = square[1].sweepresult;
else squaretarget = square[1].period;
if ((clockticks6502 - square[1].lasttick) >= (squaretarget << 1)) {
if (square[1].enabled && (squaretarget > 7) && (square[1].lengthcounter > 0)) {
channels[1] = square[1].envelope * square_duty[square[1].duty][square[1].dutypos];
square[1].dutypos = (square[1].dutypos + 1) & 7;
} else channels[1] = 0;
square[1].lasttick = clockticks6502 - ((clockticks6502 - square[1].lasttick) - (squaretarget << 1));
}
if ((clockticks6502 - triangle.lasttick) >= triangle.period) {
if (triangle.enabled && (triangle.period > 7) && (triangle.lengthcounter > 0) && (triangle.linearcounter > 0)) {
channels[2] = triangle_step[triangle.seqstep] - 8;
triangle.seqstep = (triangle.seqstep + 1) & 31;
} else channels[2] = 0;
triangle.lasttick = clockticks6502 - ((clockticks6502 - triangle.lasttick) - triangle.period);
}
if ((clockticks6502 - noise.lasttick) >= noise.period) {
if (noise.enabled) {
if (noise.mode) {
if (noise.shift & 0x40) tmpbit = 1;
else tmpbit = 0;
} else {
if (noise.shift & 0x02) tmpbit = 1;
else tmpbit = 0;
}
feedback = (noise.shift & 1) ^ tmpbit;
noise.shift = (noise.shift >> 1) | (feedback << 13);
if ((noise.lengthcounter > 0) && ((noise.shift & 1) == 0) && (noise.period > 7)) {
channels[3] = (int8_t)noise.envelope;
} else channels[3] = 0;
} else channels[3] = 0;
noise.lasttick = clockticks6502 - ((clockticks6502 - noise.lasttick) - noise.period);
}
if ((clockticks6502 - dmc.lasttick) >= (dmc.period << 3)) {
if (dmc.enabled) {
if (dmc.sampleempty) {
dmc.sampleempty = 0;
dmc.bufferbit = 0;
dmc.samplebuffer = read6502(dmc.address++);
if (dmc.address < 0x8000) dmc.address = 0x8000;
dmc.bytesremain--;
if (dmc.bytesremain == 0) {
if (!dmc.loopmode) {
dmc.enabled = 0;
} else {
dmc.address = (dmc.addressreg << 6) | 0xC000;
dmc.bytesremain = (dmc.lengthreg << 4) + 1;
}
}
}
if (!dmc.sampleempty) {
if ((dmc.samplebuffer >> (dmc.bufferbit & 7)) & 1) {
if (channels[4] <= 0x7D) channels[4] += 2;
} else {
if (channels[4] >= 2) channels[4] -= 2;
}
if (++dmc.bufferbit == 8) {
dmc.sampleempty = 1;
}
}
}
dmc.lasttick = clockticks6502 - ((clockticks6502 - dmc.lasttick) - (dmc.period << 3));
}
}
double sampleratio = 1.00;
void rebufferAPU(void *udata, uint8_t *stream, int16_t len) {
memcpy(stream, &buf[0], len);
memmove(&buf[0], &buf[len], buffersize - len);
bufferpos -= len;
if (bufferpos <= 0) bufferpos = 0;
}
bool initAPU(uint8_t CPUmode, uint32_t samplerate) { //for CPUmode, 0 = NTSC, non-zero = PAL
uint32_t buflen;
if (CPUmode) {
seqtickgap = CPU_PAL / 240;
sampleticks = CPU_PAL / samplerate;
} else {
seqtickgap = CPU_NTSC / 240;
sampleticks = CPU_NTSC / samplerate;
}
cursampleticks = sampleticks;
fastsampleticks = (uint64_t)((double)sampleticks * 1.05); //5% faster sample generation if we need to play catch-up
seqticknext = clockticks6502 + seqtickgap;
seqmode = 4;
seqstep = 0;
triangle.seqstep = 0;
noise.shift = 1;
buflen = (uint32_t)((float)(samplerate / 1000) * 100); //100 milliseconds is generally a safe latency period
audio.freq = samplerate;
audio.samples = (uint16_t)buflen;
audio.channels = 1;
audio.format = AUDIO_S8;
audio.callback = (void *)rebufferAPU;
audio.userdata = NULL;
if (SDL_OpenAudio(&audio, NULL) < 0) return(false);
buffersize = buflen;
bufferpos = 0;
cursamplerate = samplerate;
SDL_PauseAudio(1);
return(true);
}
void resetAPU() {
SDL_PauseAudio(1);
bufferpos = 0;
}
void killAPU() {
SDL_CloseAudio();
}
This open-source software is (c)2010-2013 Mike Chambers, and is released
under the terms of the GNU GPL v2 license.
This is my work-in-progess APU emulation code. Sweep functionality
isn't working yet.
*/
#include "config.h"
#include <stdio.h>
#include <malloc.h>
#include "moarnes.h"
uint8_t length_lookup[2][16] = { //the first bracket value should be bit 3, second bracket bits 7 to 4
{ 0x0A, 0x14, 0x28, 0x50, 0xA0, 0x3C, 0x0E, 0x1A, 0x0C, 0x18, 0x30, 0x60, 0xC0, 0x48, 0x10, 0x20 },
{ 0xFE, 0x02, 0x04, 0x06, 0x08, 0x0A, 0x0C, 0x0E, 0x10, 0x12, 0x14, 0x16, 0x18, 0x1A, 0x1C, 0x1E}
};
uint32_t noise_lookup[16] = {
0x004, 0x008, 0x010, 0x020, 0x040, 0x060, 0x080, 0x0A0,
0x0CA, 0x0FE, 0x17C, 0x1FC, 0x2FA, 0x3F8, 0x7F2, 0xFE4
};
int8_t triangle_step[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
};
uint32_t dmc_period[16] = {
0x1AC, 0x17C, 0x154, 0x140, 0x11E, 0x0FE, 0x0E2, 0x0D6,
0x0BE, 0x0A0, 0x08E, 0x080, 0x06A, 0x054, 0x048, 0x036
};
uint8_t square_duty[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 }
};
extern uint64_t clockticks6502;
SDL_AudioSpec audio;
uint32_t buffersize, cursamplerate;
uint8_t buf[48000];
uint64_t seqtickgap = 0xFFFFFFFF, seqticknext = 0xFFFFFFFF;
uint64_t sampleticks, cursampleticks, fastsampleticks, nextsamplecycle = 0xFFFFFFFF, nextseqcycle = 0xFFFFFFFF;
uint8_t seqstep = 0, seqmode = 4, interruptAPU = 0;
struct square_s square[2];
struct triangle_s triangle;
struct noise_s noise;
struct dmc_s dmc;
uint8_t allowsweeps = 0; //sweep code is completely buggy right now, and really breaks a lot of stuff. allow sweeps if you want, but be prepared for extreme fail.
int8_t channels[5] = { 0, 0, 0, 0, 0}; //square 1, square 2, triangle, noise, DMC
FILE *wavefile = NULL;
uint8_t readAPU(uint16_t addr) {
uint8_t outbyte;
if (addr == 0x4015) {
if (square[0].enabled && square[0].lengthcounter) outbyte = 1;
else outbyte = 0;
if (square[1].enabled && square[1].lengthcounter) outbyte |= 2;
if (triangle.enabled && triangle.lengthcounter) outbyte |= 4;
if (noise.enabled && noise.lengthcounter) outbyte |= 8;
return(outbyte);
}
return(0);
}
void writeAPU(uint16_t addr, uint8_t value) {
switch (addr) {
case 0x4000: //square 1
square[0].duty = value >> 6;
if (value & 0x20) square[0].lengthhalt = 1;
else square[0].lengthhalt = 0;
if (value & 0x10) square[0].constant = 1;
else square[0].constant = 0;
square[0].envelope = value & 0x0F;
break;
case 0x4001:
if (value & 0x80) { square[0].sweepenable = 1; square[0].sweepresult = square[0].period; }
else { square[0].sweepenable = 0; square[0].sweepresult = square[0].period; }
square[0].sweepperiod = ((value >> 4) & 7) + 1;
if (value & 0x08) square[0].negate = 1;
else square[0].negate = 0;
square[0].sweep = value & 7;
square[0].sweepreload = 1;
break;
case 0x4002:
square[0].period &= 0xFF00;
square[0].period += (uint32_t)value + 1;
square[0].sweepresult = square[0].period;
break;
case 0x4003:
square[0].lengthindex = value >> 3;
square[0].period &= 0xFF;
square[0].period += (uint32_t)(value & 7) << 8;
square[0].dutypos = 0;
squarereload(0);
square[0].wrotebit4 = 1;
square[0].sweepresult = square[0].period;
break;
case 0x4004: //square 2
square[1].duty = value >> 6;
if (value & 0x20) square[1].lengthhalt = 1;
else square[1].lengthhalt = 0;
if (value & 0x10) square[1].constant = 1;
else square[1].constant = 0;
square[1].envelope = value & 0x0F;
break;
case 0x4005:
if (value & 0x80) { square[1].sweepenable = 1; square[1].sweepresult = square[0].period; }
else { square[1].sweepenable = 0; square[1].sweepresult = square[0].period; }
square[1].sweepperiod = ((value >> 4) & 7) + 1;
if (value & 0x08) square[1].negate = 1;
else square[0].negate = 0;
square[1].sweep = value & 7;
square[1].sweepreload = 1;
break;
case 0x4006:
square[1].period &= 0xFF00;
square[1].period += (uint32_t)value;
square[1].sweepresult = square[1].period;
break;
case 0x4007:
square[1].lengthindex = value >> 3;
square[1].period &= 0xFF;
square[1].period += (uint32_t)(value & 7) << 8;
square[1].dutypos = 0;
squarereload(1);
square[1].wrotebit4 = 1;
square[1].sweepresult = square[1].period;
break;
case 0x4008: //triangle
if (value & 0x80) triangle.lengthhalt = 1;
else triangle.lengthhalt = 0;
triangle.linearreload = value & 0x7F;
break;
case 0x400A:
triangle.period &= 0xFF00;
triangle.period += value + 1;
break;
case 0x400B:
triangle.lengthindex = value >> 3;
triangle.period &= 0xFF;
triangle.period += (uint32_t)(value & 7) << 8;
//if (triangle.lengthhalt == 0)
triangle.lengthcounter = length_lookup[triangle.lengthindex & 1][triangle.lengthindex >> 1];
break;
case 0x400C: //noise
if (value & 0x20) noise.lengthhalt = 1;
else noise.lengthhalt = 0;
if (value & 0x10) noise.constant = 1;
else noise.constant = 0;
noise.envreload = value & 0x0F;
noise.envelope = noise.envreload;
break;
case 0x400E:
noise.mode = value >> 7;
noise.period = noise_lookup[value & 0x0F];
break;
case 0x400F:
noise.lengthindex = value >> 3;
noise.lengthcounter = length_lookup[noise.lengthindex & 1][noise.lengthindex >> 1];
noise.wrotebit4 = 1;
break;
case 0x4010: //DMC
if (value & 0x40) dmc.loopmode = 1;
else dmc.loopmode = 0;
dmc.period = dmc_period[value & 0x0F] >> 3;
break;
case 0x4011:
channels[4] = value & 0x7F;
break;
case 0x4012:
dmc.addressreg = value;
dmc.address = ((uint16_t)value << 6) | 0xC000;
break;
case 0x4013:
dmc.lengthreg = value;
dmc.bytesremain = ((uint16_t)value << 4) + 1;
break;
case 0x4015:
if (value & 1) {
square[0].enabled = 1;
} else {
square[0].enabled = 0;
square[0].lengthcounter = 0;
}
if (value & 2) {
square[1].enabled = 1;
} else {
square[1].enabled = 0;
square[1].lengthcounter = 0;
}
if (value & 4) {
triangle.enabled = 1;
} else {
triangle.enabled = 0;
triangle.lengthcounter = 0;
}
if (value & 8) noise.enabled = 1;
else noise.enabled = 0;
if (value & 16) { dmc.enabled = 1; dmc.lasttick = clockticks6502; }
else dmc.enabled = 0;
break;
case 0x4017:
if (value & 0x80) seqmode = 5;
else seqmode = 4;
break;
}
if (square[0].enabled == 0) square[0].lengthcounter = 0;
if (square[1].enabled == 0) square[1].lengthcounter = 0;
if (triangle.enabled == 0) {
triangle.lengthcounter = 0;
triangle.linearcounter = 0;
}
if (noise.enabled == 0) noise.lengthcounter = 0;
}
void squarereload(uint8_t channel) {
square[channel].lengthcounter = length_lookup[square[channel].lengthindex & 1][square[channel].lengthindex >> 1];
}
void envclock() {
if (square[0].constant == 0) {
if (square[0].wrotebit4) {
square[0].envelope = 15;
square[0].wrotebit4 = 0;
} else {
if (square[0].envelope > 0) square[0].envelope--;
}
if ((square[0].envelope == 0) && square[0].envloop) {
square[0].envelope = 15;
}
}
if (square[1].constant == 0) {
if (square[1].wrotebit4) {
square[1].envelope = 15;
square[1].wrotebit4 = 0;
} else {
if (square[1].envelope > 0) square[1].envelope--;
}
if ((square[1].envelope == 0) && square[1].envloop) {
square[1].envelope = 15;
}
}
if (noise.constant == 0) {
if (noise.wrotebit4) {
noise.envelope = 15;
noise.wrotebit4 = 0;
} else {
if (noise.envelope > 0) noise.envelope--;
}
if ((noise.envelope == 0) && noise.lengthhalt) {
noise.envelope = 15;
}
}
}
void linearclock() {
if (triangle.lengthhalt == 0) {
if (triangle.linearcounter > 0) triangle.linearcounter--;
else triangle.linearcounter = triangle.linearreload;
} else {
triangle.linearcounter = triangle.linearreload;
}
}
void lengthclock() {
if (square[0].lengthhalt == 0) {
if (square[0].lengthcounter > 0) square[0].lengthcounter--;
}
if (square[1].lengthhalt == 0) {
if (square[1].lengthcounter > 0) square[1].lengthcounter--;
}
if (triangle.lengthhalt == 0) {
if (triangle.lengthcounter > 0) triangle.lengthcounter--;
}
if (noise.lengthhalt == 0) {
if (noise.lengthcounter > 0) noise.lengthcounter--;
}
}
void sweepclock() {
int32_t s, wl, d[4];
uint8_t chan;
for (chan=0; chan<2; chan++) {
if ((--square[chan].sweep == 0) || square[chan].sweepreload) square[chan].sweep = square[chan].sweepperiod;
if ((square[chan].period >= 8) && square[chan].sweepenable && square[chan].sweep) {
wl = square[chan].period;
s = wl >> square[chan].sweep;
d[0] = s; d[1] = s; d[2] = ~s; d[3] = -s;
wl += d[square[chan].negate*2 + chan];
if (wl < 0x800) square[chan].sweepresult = wl;
else square[chan].sweepresult = 0;
} else square[chan].sweepresult = 0;
if (!allowsweeps) square[chan].sweepresult = square[chan].period;
}
}
void setinterruptAPU() {
}
void frameseqAPU() {
if (clockticks6502 < seqticknext) return;
else seqticknext += seqtickgap;
if (seqmode == 4) {
switch (seqstep) {
case 0:
envclock();
linearclock();
break;
case 1:
lengthclock();
sweepclock();
envclock();
linearclock();
break;
case 2:
envclock();
linearclock();
break;
case 3:
setinterruptAPU();
lengthclock();
sweepclock();
envclock();
linearclock();
break;
}
seqstep = (seqstep + 1) % seqmode;
} else if (seqmode == 5) {
switch (seqstep) {
case 0:
lengthclock();
sweepclock();
envclock();
linearclock();
break;
case 1:
envclock();
linearclock();
break;
case 2:
lengthclock();
sweepclock();
envclock();
linearclock();
break;
case 3:
envclock();
linearclock();
break;
case 4:
break;
}
seqstep = (seqstep + 1) % seqmode;
}
}
void mixerinput(uint8_t channel, uint8_t value) {
channels[channel] = value;
}
int32_t bufferpos = 0, buffersync = 0, audiosync = 0, unpausebufpos;
int8_t last = 0;
int8_t mixeroutAPU() {
double outbyte;
double squareout, tndout;
squareout = 0;
if (square[0].enabled && (square[0].period > 7) && (square[0].lengthcounter > 0)) squareout += channels[0];
if (square[1].enabled && (square[1].period > 7) && (square[1].lengthcounter > 0)) squareout += channels[1];
squareout *= 0.00752;
tndout = 0;
if (triangle.enabled && (triangle.period > 7) && (triangle.lengthcounter > 0) && (triangle.linearcounter > 0)) tndout += channels[2];
tndout = tndout * 0.00851 + 0.00494 * channels[3] + 0.00335 * channels[4];
outbyte = (int16_t)(127 * squareout);
outbyte += (int16_t)(127 * tndout);
return(outbyte);
}
void buffersampleAPU() {
if ((uint32_t)bufferpos < buffersize) {
buf[bufferpos++] = mixeroutAPU();
} else {
SDL_PauseAudio(0);
}
}
void tickchannelsAPU() {
int16_t tmpbit, feedback;
uint32_t squaretarget;
if (clockticks6502 >= nextsamplecycle) {
buffersampleAPU();
nextsamplecycle += fastsampleticks;
}
if ((uint32_t)bufferpos >= buffersize) { //if sample buffer is full, postpone channel ticks
square[0].lasttick = square[1].lasttick = triangle.lasttick = noise.lasttick = dmc.lasttick = clockticks6502;
return;
}
if (clockticks6502 >= nextseqcycle) {
frameseqAPU();
nextseqcycle += seqtickgap;
}
if (square[0].sweepenable) squaretarget = square[0].sweepresult;
else squaretarget = square[0].period;
if ((clockticks6502 - square[0].lasttick) >= (squaretarget << 1)) {
if (square[0].enabled && (squaretarget > 7) && (square[0].lengthcounter > 0)) {
channels[0] = square[0].envelope * square_duty[square[0].duty][square[0].dutypos];
square[0].dutypos = (square[0].dutypos + 1) & 7;
} else channels[0] = 0;
square[0].lasttick = clockticks6502 - ((clockticks6502 - square[0].lasttick) - (squaretarget << 1));
}
if (square[1].sweepenable) squaretarget = square[1].sweepresult;
else squaretarget = square[1].period;
if ((clockticks6502 - square[1].lasttick) >= (squaretarget << 1)) {
if (square[1].enabled && (squaretarget > 7) && (square[1].lengthcounter > 0)) {
channels[1] = square[1].envelope * square_duty[square[1].duty][square[1].dutypos];
square[1].dutypos = (square[1].dutypos + 1) & 7;
} else channels[1] = 0;
square[1].lasttick = clockticks6502 - ((clockticks6502 - square[1].lasttick) - (squaretarget << 1));
}
if ((clockticks6502 - triangle.lasttick) >= triangle.period) {
if (triangle.enabled && (triangle.period > 7) && (triangle.lengthcounter > 0) && (triangle.linearcounter > 0)) {
channels[2] = triangle_step[triangle.seqstep] - 8;
triangle.seqstep = (triangle.seqstep + 1) & 31;
} else channels[2] = 0;
triangle.lasttick = clockticks6502 - ((clockticks6502 - triangle.lasttick) - triangle.period);
}
if ((clockticks6502 - noise.lasttick) >= noise.period) {
if (noise.enabled) {
if (noise.mode) {
if (noise.shift & 0x40) tmpbit = 1;
else tmpbit = 0;
} else {
if (noise.shift & 0x02) tmpbit = 1;
else tmpbit = 0;
}
feedback = (noise.shift & 1) ^ tmpbit;
noise.shift = (noise.shift >> 1) | (feedback << 13);
if ((noise.lengthcounter > 0) && ((noise.shift & 1) == 0) && (noise.period > 7)) {
channels[3] = (int8_t)noise.envelope;
} else channels[3] = 0;
} else channels[3] = 0;
noise.lasttick = clockticks6502 - ((clockticks6502 - noise.lasttick) - noise.period);
}
if ((clockticks6502 - dmc.lasttick) >= (dmc.period << 3)) {
if (dmc.enabled) {
if (dmc.sampleempty) {
dmc.sampleempty = 0;
dmc.bufferbit = 0;
dmc.samplebuffer = read6502(dmc.address++);
if (dmc.address < 0x8000) dmc.address = 0x8000;
dmc.bytesremain--;
if (dmc.bytesremain == 0) {
if (!dmc.loopmode) {
dmc.enabled = 0;
} else {
dmc.address = (dmc.addressreg << 6) | 0xC000;
dmc.bytesremain = (dmc.lengthreg << 4) + 1;
}
}
}
if (!dmc.sampleempty) {
if ((dmc.samplebuffer >> (dmc.bufferbit & 7)) & 1) {
if (channels[4] <= 0x7D) channels[4] += 2;
} else {
if (channels[4] >= 2) channels[4] -= 2;
}
if (++dmc.bufferbit == 8) {
dmc.sampleempty = 1;
}
}
}
dmc.lasttick = clockticks6502 - ((clockticks6502 - dmc.lasttick) - (dmc.period << 3));
}
}
double sampleratio = 1.00;
void rebufferAPU(void *udata, uint8_t *stream, int16_t len) {
memcpy(stream, &buf[0], len);
memmove(&buf[0], &buf[len], buffersize - len);
bufferpos -= len;
if (bufferpos <= 0) bufferpos = 0;
}
bool initAPU(uint8_t CPUmode, uint32_t samplerate) { //for CPUmode, 0 = NTSC, non-zero = PAL
uint32_t buflen;
if (CPUmode) {
seqtickgap = CPU_PAL / 240;
sampleticks = CPU_PAL / samplerate;
} else {
seqtickgap = CPU_NTSC / 240;
sampleticks = CPU_NTSC / samplerate;
}
cursampleticks = sampleticks;
fastsampleticks = (uint64_t)((double)sampleticks * 1.05); //5% faster sample generation if we need to play catch-up
seqticknext = clockticks6502 + seqtickgap;
seqmode = 4;
seqstep = 0;
triangle.seqstep = 0;
noise.shift = 1;
buflen = (uint32_t)((float)(samplerate / 1000) * 100); //100 milliseconds is generally a safe latency period
audio.freq = samplerate;
audio.samples = (uint16_t)buflen;
audio.channels = 1;
audio.format = AUDIO_S8;
audio.callback = (void *)rebufferAPU;
audio.userdata = NULL;
if (SDL_OpenAudio(&audio, NULL) < 0) return(false);
buffersize = buflen;
bufferpos = 0;
cursamplerate = samplerate;
SDL_PauseAudio(1);
return(true);
}
void resetAPU() {
SDL_PauseAudio(1);
bufferpos = 0;
}
void killAPU() {
SDL_CloseAudio();
}