mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-02-24 23:22:25 +01:00
byuu says: Changelog: - lots of code cleanups to processor/r6502 (the switch.cpp file is only halfway done ...) - lots of code cleanups to fc/cpu - removed fc/input - implemented fc/controller hex_usr, you may not like this, but I want to keep the controller port and expansion port interface separate, like I do with the SNES. I realize the NES' is used more for controllers, and the SNES' more for hardware expansions, but ... they're not compatible pinouts and you can't really connect one to the other. Right now, I've only implemented the controller portion. I'll have to get to the peripheral portion later. Also, the gamepad implementation there now may be wrong. It's based off the Super Famicom version obviously. I'm not sure if the Famicom has different behavior with latching $4016 writes, or not. But, it works in Mega Man II, so it's a start. Everyone, be sure to remap your controls, and then set port 1 -> gamepad after loading your first Famicom game with the new WIP.
347 lines
8.1 KiB
C++
347 lines
8.1 KiB
C++
#include <fc/fc.hpp>
|
|
|
|
namespace Famicom {
|
|
|
|
#include "envelope.cpp"
|
|
#include "sweep.cpp"
|
|
#include "pulse.cpp"
|
|
#include "triangle.cpp"
|
|
#include "noise.cpp"
|
|
#include "dmc.cpp"
|
|
#include "serialization.cpp"
|
|
APU apu;
|
|
|
|
APU::APU() {
|
|
for(uint amp : range(32)) {
|
|
if(amp == 0) {
|
|
pulse_dac[amp] = 0;
|
|
} else {
|
|
pulse_dac[amp] = 16384.0 * 95.88 / (8128.0 / amp + 100.0);
|
|
}
|
|
}
|
|
|
|
for(uint dmc_amp : range(128)) {
|
|
for(uint triangle_amp : range(16)) {
|
|
for(uint noise_amp : range(16)) {
|
|
if(dmc_amp == 0 && triangle_amp == 0 && noise_amp == 0) {
|
|
dmc_triangle_noise_dac[dmc_amp][triangle_amp][noise_amp] = 0;
|
|
} else {
|
|
dmc_triangle_noise_dac[dmc_amp][triangle_amp][noise_amp]
|
|
= 16384.0 * 159.79 / (100.0 + 1.0 / (triangle_amp / 8227.0 + noise_amp / 12241.0 + dmc_amp / 22638.0));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
auto APU::Enter() -> void {
|
|
while(true) scheduler.synchronize(), apu.main();
|
|
}
|
|
|
|
auto APU::main() -> void {
|
|
uint pulse_output, triangle_output, noise_output, dmc_output;
|
|
|
|
pulse_output = pulse[0].clock();
|
|
pulse_output += pulse[1].clock();
|
|
triangle_output = triangle.clock();
|
|
noise_output = noise.clock();
|
|
dmc_output = dmc.clock();
|
|
|
|
clock_frame_counter_divider();
|
|
|
|
int output = pulse_dac[pulse_output] + dmc_triangle_noise_dac[dmc_output][triangle_output][noise_output];
|
|
|
|
output = filter.run_hipass_strong(output);
|
|
output += cartridge_sample;
|
|
output = filter.run_hipass_weak(output);
|
|
//output = filter.run_lopass(output);
|
|
output = sclamp<16>(output);
|
|
|
|
stream->sample(output / 32768.0);
|
|
|
|
tick();
|
|
}
|
|
|
|
auto APU::tick() -> void {
|
|
clock += 12;
|
|
if(clock >= 0 && !scheduler.synchronizing()) co_switch(cpu.thread);
|
|
}
|
|
|
|
auto APU::set_irq_line() -> void {
|
|
cpu.apuLine(frame.irq_pending || dmc.irq_pending);
|
|
}
|
|
|
|
auto APU::set_sample(int16 sample) -> void {
|
|
cartridge_sample = sample;
|
|
}
|
|
|
|
auto APU::power() -> void {
|
|
filter.hipass_strong = 0;
|
|
filter.hipass_weak = 0;
|
|
filter.lopass = 0;
|
|
|
|
pulse[0].power();
|
|
pulse[1].power();
|
|
triangle.power();
|
|
noise.power();
|
|
dmc.power();
|
|
}
|
|
|
|
auto APU::reset() -> void {
|
|
create(APU::Enter, 21'477'272);
|
|
stream = Emulator::audio.createStream(1, 21'477'272.0 / 12.0);
|
|
|
|
pulse[0].reset();
|
|
pulse[1].reset();
|
|
triangle.reset();
|
|
noise.reset();
|
|
dmc.reset();
|
|
|
|
frame.irq_pending = 0;
|
|
|
|
frame.mode = 0;
|
|
frame.counter = 0;
|
|
frame.divider = 1;
|
|
|
|
enabled_channels = 0;
|
|
cartridge_sample = 0;
|
|
|
|
set_irq_line();
|
|
}
|
|
|
|
auto APU::readIO(uint16 addr) -> uint8 {
|
|
switch(addr) {
|
|
|
|
case 0x4015: {
|
|
uint8 result = 0x00;
|
|
result |= pulse[0].length_counter ? 0x01 : 0;
|
|
result |= pulse[1].length_counter ? 0x02 : 0;
|
|
result |= triangle.length_counter ? 0x04 : 0;
|
|
result |= noise.length_counter ? 0x08 : 0;
|
|
result |= dmc.length_counter ? 0x10 : 0;
|
|
result |= frame.irq_pending ? 0x40 : 0;
|
|
result |= dmc.irq_pending ? 0x80 : 0;
|
|
|
|
frame.irq_pending = false;
|
|
set_irq_line();
|
|
|
|
return result;
|
|
}
|
|
|
|
}
|
|
|
|
return cpu.mdr();
|
|
}
|
|
|
|
auto APU::writeIO(uint16 addr, uint8 data) -> void {
|
|
const uint n = (addr >> 2) & 1; //pulse#
|
|
|
|
switch(addr) {
|
|
|
|
case 0x4000: case 0x4004: {
|
|
pulse[n].duty = data >> 6;
|
|
pulse[n].envelope.loop_mode = data & 0x20;
|
|
pulse[n].envelope.use_speed_as_volume = data & 0x10;
|
|
pulse[n].envelope.speed = data & 0x0f;
|
|
return;
|
|
}
|
|
|
|
case 0x4001: case 0x4005: {
|
|
pulse[n].sweep.enable = data & 0x80;
|
|
pulse[n].sweep.period = (data & 0x70) >> 4;
|
|
pulse[n].sweep.decrement = data & 0x08;
|
|
pulse[n].sweep.shift = data & 0x07;
|
|
pulse[n].sweep.reload = true;
|
|
return;
|
|
}
|
|
|
|
case 0x4002: case 0x4006: {
|
|
pulse[n].period = (pulse[n].period & 0x0700) | (data << 0);
|
|
pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x0700) | (data << 0);
|
|
return;
|
|
}
|
|
|
|
case 0x4003: case 0x4007: {
|
|
pulse[n].period = (pulse[n].period & 0x00ff) | (data << 8);
|
|
pulse[n].sweep.pulse_period = (pulse[n].sweep.pulse_period & 0x00ff) | (data << 8);
|
|
|
|
pulse[n].duty_counter = 7;
|
|
pulse[n].envelope.reload_decay = true;
|
|
|
|
if(enabled_channels & (1 << n)) {
|
|
pulse[n].length_counter = length_counter_table[(data >> 3) & 0x1f];
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x4008: {
|
|
triangle.halt_length_counter = data & 0x80;
|
|
triangle.linear_length = data & 0x7f;
|
|
return;
|
|
}
|
|
|
|
case 0x400a: {
|
|
triangle.period = (triangle.period & 0x0700) | (data << 0);
|
|
return;
|
|
}
|
|
|
|
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];
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x400c: {
|
|
noise.envelope.loop_mode = data & 0x20;
|
|
noise.envelope.use_speed_as_volume = data & 0x10;
|
|
noise.envelope.speed = data & 0x0f;
|
|
return;
|
|
}
|
|
|
|
case 0x400e: {
|
|
noise.short_mode = data & 0x80;
|
|
noise.period = data & 0x0f;
|
|
return;
|
|
}
|
|
|
|
case 0x400f: {
|
|
noise.envelope.reload_decay = true;
|
|
|
|
if(enabled_channels & (1 << 3)) {
|
|
noise.length_counter = length_counter_table[(data >> 3) & 0x1f];
|
|
}
|
|
return;
|
|
}
|
|
|
|
case 0x4010: {
|
|
dmc.irq_enable = data & 0x80;
|
|
dmc.loop_mode = data & 0x40;
|
|
dmc.period = data & 0x0f;
|
|
|
|
dmc.irq_pending = dmc.irq_pending && dmc.irq_enable && !dmc.loop_mode;
|
|
set_irq_line();
|
|
return;
|
|
}
|
|
|
|
case 0x4011: {
|
|
dmc.dac_latch = data & 0x7f;
|
|
return;
|
|
}
|
|
|
|
case 0x4012: {
|
|
dmc.addr_latch = data;
|
|
return;
|
|
}
|
|
|
|
case 0x4013: {
|
|
dmc.length_latch = data;
|
|
return;
|
|
}
|
|
|
|
case 0x4015: {
|
|
if((data & 0x01) == 0) pulse[0].length_counter = 0;
|
|
if((data & 0x02) == 0) pulse[1].length_counter = 0;
|
|
if((data & 0x04) == 0) triangle.length_counter = 0;
|
|
if((data & 0x08) == 0) noise.length_counter = 0;
|
|
|
|
(data & 0x10) ? dmc.start() : dmc.stop();
|
|
dmc.irq_pending = false;
|
|
|
|
set_irq_line();
|
|
enabled_channels = data & 0x1f;
|
|
return;
|
|
}
|
|
|
|
case 0x4017: {
|
|
frame.mode = data >> 6;
|
|
|
|
frame.counter = 0;
|
|
if(frame.mode & 2) clock_frame_counter();
|
|
if(frame.mode & 1) {
|
|
frame.irq_pending = false;
|
|
set_irq_line();
|
|
}
|
|
frame.divider = FrameCounter::NtscPeriod;
|
|
return;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
auto APU::Filter::run_hipass_strong(int sample) -> int {
|
|
hipass_strong += ((((int64)sample << 16) - (hipass_strong >> 16)) * HiPassStrong) >> 16;
|
|
return sample - (hipass_strong >> 32);
|
|
}
|
|
|
|
auto APU::Filter::run_hipass_weak(int sample) -> int {
|
|
hipass_weak += ((((int64)sample << 16) - (hipass_weak >> 16)) * HiPassWeak) >> 16;
|
|
return sample - (hipass_weak >> 32);
|
|
}
|
|
|
|
auto APU::Filter::run_lopass(int sample) -> int {
|
|
lopass += ((((int64)sample << 16) - (lopass >> 16)) * LoPass) >> 16;
|
|
return (lopass >> 32);
|
|
}
|
|
|
|
auto APU::clock_frame_counter() -> void {
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
auto APU::clock_frame_counter_divider() -> void {
|
|
frame.divider -= 2;
|
|
if(frame.divider <= 0) {
|
|
clock_frame_counter();
|
|
frame.divider += FrameCounter::NtscPeriod;
|
|
}
|
|
}
|
|
|
|
const uint8 APU::length_counter_table[32] = {
|
|
0x0a, 0xfe, 0x14, 0x02, 0x28, 0x04, 0x50, 0x06, 0xa0, 0x08, 0x3c, 0x0a, 0x0e, 0x0c, 0x1a, 0x0e,
|
|
0x0c, 0x10, 0x18, 0x12, 0x30, 0x14, 0x60, 0x16, 0xc0, 0x18, 0x48, 0x1a, 0x10, 0x1c, 0x20, 0x1e,
|
|
};
|
|
|
|
const uint16 APU::ntsc_noise_period_table[16] = {
|
|
4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068,
|
|
};
|
|
|
|
const uint16 APU::pal_noise_period_table[16] = {
|
|
4, 7, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778,
|
|
};
|
|
|
|
const uint16 APU::ntsc_dmc_period_table[16] = {
|
|
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
|
|
};
|
|
|
|
const uint16 APU::pal_dmc_period_table[16] = {
|
|
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
|
|
};
|
|
|
|
}
|