aliaspider recently noted that by adding "if(period == 0) return 0;" to the top of my APU::Triangle::clock() function, that the buzzing went away completely, even with a simple hermite audio filter (many, many times less CPU intensive.)
Could anyone please look at my triangle emulation code and let me know if that is a proper fix (eg how the hardware really works), or if the game is really supposed to create this buzzing sound without proper audio resampling?
(Note that I use a variable-length integer class. So a uint5 type, when the value is 31, and you add one, will wrap to zero automatically. Much like your traditional uint8 type does now from 255->0. This is why there aren't as many bit-masks in the below code.)
Many thanks in advance!
Code: Select all
struct Triangle {
unsigned length_counter;
uint8 linear_length;
bool halt_length_counter;
uint11 period;
unsigned period_counter;
uint5 step_counter;
uint8 linear_length_counter;
bool reload_linear;
void clock_length();
void clock_linear_length();
uint8 clock();
void power();
void reset();
} triangle;
Code: Select all
void APU::Triangle::clock_length() {
if(halt_length_counter == 0) {
if(length_counter > 0) length_counter--;
}
}
void APU::Triangle::clock_linear_length() {
if(reload_linear) {
linear_length_counter = linear_length;
} else if(linear_length_counter) {
linear_length_counter--;
}
if(halt_length_counter == 0) reload_linear = false;
}
uint8 APU::Triangle::clock() {
uint8 result = step_counter & 0x0f;
if((step_counter & 0x10) == 0) result ^= 0x0f;
if(length_counter == 0 || linear_length_counter == 0) return result;
if(--period_counter == 0) {
step_counter++;
period_counter = period + 1;
}
return result;
}
void APU::Triangle::power() {
reset();
}
void APU::Triangle::reset() {
length_counter = 0;
linear_length = 0;
halt_length_counter = 0;
period = 0;
period_counter = 1;
step_counter = 0;
linear_length_counter = 0;
reload_linear = 0;
}
Code: Select all
void APU::write(uint16 addr, uint8 data) {
const unsigned n = (addr >> 2) & 1; //pulse#
switch(addr) {
...
case 0x4008:
triangle.halt_length_counter = data & 0x80;
triangle.linear_length = data & 0x7f;
break;
case 0x400a:
triangle.period = (triangle.period & 0x0700) | (data << 0);
break;
case 0x400b:
triangle.period = (triangle.period & 0x00ff) | (data << 8);
triangle.reload_linear = true;
if(enabled_channels & (1 << 2)) {
triangle.length_counter = length_counter_table[(data >> 3) & 0x1f];
}
break;
case 0x4015:
...
if((data & 0x04) == 0) triangle.length_counter = 0;
break;
}
}
void APU::clock_frame_counter() {
frame.counter++;
if(frame.counter & 1) {
pulse[0].clock_length();
pulse[0].sweep.clock(0);
pulse[1].clock_length();
pulse[1].sweep.clock(1);
triangle.clock_length();
noise.clock_length();
}
pulse[0].envelope.clock();
pulse[1].envelope.clock();
triangle.clock_linear_length();
noise.envelope.clock();
if(frame.counter == 0) {
if(frame.mode & 2) frame.divider += FrameCounter::NtscPeriod;
if(frame.mode == 0) {
frame.irq_pending = true;
set_irq_line();
}
}
}