Update to v082r14 release.

byuu says:

Emulates DMC channel (sound effect when Link gets hit in Zelda 1, for
instance), fixes up bugs in rectangle/sweep and triangle channels, adds
DMC/frame APU IRQs, adds proper stalling for DMC ROM reads (should even
be cycle accurate, but has one extra cycle when triggered during OAM
DMA, I think), but forgets the frame IRQ acknowledge clear on $4015 read
(will fix next WIP.) All sound courtesy of Ryphecha.

Made template configuration settings window (empty for now.) Simplified
SNES cheat.cpp code. Some other stuff.

Further developed RSI.
This commit is contained in:
Tim Allen 2011-09-16 21:44:07 +10:00
parent e3c7bbfb63
commit 7619805266
34 changed files with 363 additions and 205 deletions

View File

@ -1,35 +1,50 @@
#ifdef NALL_DSP_INTERNAL_HPP
struct Buffer {
double *sample[2];
double **sample;
uint16_t rdoffset;
uint16_t wroffset;
unsigned channels;
inline double& read(bool channel, signed offset = 0) {
void setChannels(unsigned channels) {
for(unsigned c = 0; c < this->channels; c++) {
if(sample[c]) delete[] sample[c];
}
if(sample) delete[] sample;
this->channels = channels;
if(channels == 0) return;
sample = new double*[channels];
for(unsigned c = 0; c < channels; c++) {
sample[c] = new double[65536]();
}
}
inline double& read(unsigned channel, signed offset = 0) {
return sample[channel][(uint16_t)(rdoffset + offset)];
}
inline double& write(bool channel, signed offset = 0) {
inline double& write(unsigned channel, signed offset = 0) {
return sample[channel][(uint16_t)(wroffset + offset)];
}
inline void clear() {
for(unsigned n = 0; n < 65536; n++) {
sample[0][n] = 0;
sample[1][n] = 0;
for(unsigned c = 0; c < channels; c++) {
for(unsigned n = 0; n < 65536; n++) {
sample[c][n] = 0;
}
}
rdoffset = 0;
wroffset = 0;
}
Buffer() {
sample[0] = new double[65536];
sample[1] = new double[65536];
channels = 0;
}
~Buffer() {
delete[] sample[0];
delete[] sample[1];
setChannels(0);
}
};

View File

@ -15,6 +15,7 @@ struct DSP {
Average,
};
inline void setChannels(unsigned channels);
inline void setPrecision(unsigned precision);
inline void setFrequency(double frequency); //inputFrequency
inline void setVolume(double volume);
@ -23,9 +24,9 @@ struct DSP {
inline void setResampler(Resampler resampler);
inline void setResamplerFrequency(double frequency); //outputFrequency
inline void sample(signed lchannel, signed rchannel);
inline void sample(signed channel[]);
inline bool pending();
inline void read(signed &lchannel, signed &rchannel);
inline void read(signed channel[]);
inline void clear();
inline DSP();
@ -33,6 +34,7 @@ struct DSP {
protected:
struct Settings {
unsigned channels;
unsigned precision;
double frequency;
double volume;
@ -50,7 +52,7 @@ protected:
} resampler;
inline void resamplerRun();
inline void resamplerWrite(double lchannel, double rchannel);
inline void resamplerWrite(double channel[]);
inline void resamplePoint();
inline void resampleLinear();
@ -70,9 +72,10 @@ protected:
#include "settings.hpp"
void DSP::sample(signed lchannel, signed rchannel) {
buffer.write(0) = (double)lchannel / settings.intensity;
buffer.write(1) = (double)rchannel / settings.intensity;
void DSP::sample(signed channel[]) {
for(unsigned c = 0; c < settings.channels; c++) {
buffer.write(c) = (double)channel[c] / settings.intensity;
}
buffer.wroffset++;
resamplerRun();
}
@ -81,12 +84,13 @@ bool DSP::pending() {
return output.rdoffset != output.wroffset;
}
void DSP::read(signed &lchannel, signed &rchannel) {
void DSP::read(signed channel[]) {
adjustVolume();
adjustBalance();
lchannel = clamp(settings.precision, output.read(0) * settings.intensity);
rchannel = clamp(settings.precision, output.read(1) * settings.intensity);
for(unsigned c = 0; c < settings.channels; c++) {
channel[c] = clamp(settings.precision, output.read(0) * settings.intensity);
}
output.rdoffset++;
}
@ -101,9 +105,10 @@ void DSP::resamplerRun() {
}
}
void DSP::resamplerWrite(double lchannel, double rchannel) {
output.write(0) = lchannel;
output.write(1) = rchannel;
void DSP::resamplerWrite(double channel[]) {
for(unsigned c = 0; c < settings.channels; c++) {
output.write(c) = channel[c];
}
output.wroffset++;
}
@ -115,11 +120,13 @@ void DSP::resamplerWrite(double lchannel, double rchannel) {
#include "resample/average.hpp"
void DSP::adjustVolume() {
output.read(0) *= settings.volume;
output.read(1) *= settings.volume;
for(unsigned c = 0; c < settings.channels; c++) {
output.read(c) *= settings.volume;
}
}
void DSP::adjustBalance() {
if(settings.channels != 2) return; //TODO: support > 2 channels
if(settings.balance < 0.0) output.read(1) *= 1.0 + settings.balance;
if(settings.balance > 0.0) output.read(0) *= 1.0 - settings.balance;
}
@ -137,6 +144,7 @@ void DSP::clear() {
}
DSP::DSP() {
setChannels(2);
setPrecision(16);
setFrequency(44100.0);
setVolume(1.0);

View File

@ -9,17 +9,20 @@ void DSP::resampleAverage() {
double scalar = 1.0;
if(resampler.fraction > resampler.step) scalar = 1.0 - (resampler.fraction - resampler.step);
output.write(0) += buffer.read(0) * scalar;
output.write(1) += buffer.read(1) * scalar;
for(unsigned c = 0; c < settings.channels; c++) {
output.write(c) += buffer.read(c) * scalar;
}
if(resampler.fraction >= resampler.step) {
output.write(0) /= resampler.step;
output.write(1) /= resampler.step;
for(unsigned c = 0; c < settings.channels; c++) {
output.write(c) /= resampler.step;
}
output.wroffset++;
resampler.fraction -= resampler.step;
output.write(0) = buffer.read(0) * resampler.fraction;
output.write(1) = buffer.read(1) * resampler.fraction;
for(unsigned c = 0; c < settings.channels; c++) {
output.write(c) = buffer.read(c) * resampler.fraction;
}
}
buffer.rdoffset++;

View File

@ -2,9 +2,9 @@
void DSP::resampleCosine() {
while(resampler.fraction <= 1.0) {
double channel[2];
double channel[settings.channels];
for(unsigned n = 0; n < 2; n++) {
for(unsigned n = 0; n < settings.channels; n++) {
double a = buffer.read(n, -1);
double b = buffer.read(n, -0);
@ -14,7 +14,7 @@ void DSP::resampleCosine() {
channel[n] = a * (1.0 - mu) + b * mu;
}
resamplerWrite(channel[0], channel[1]);
resamplerWrite(channel);
resampler.fraction += resampler.step;
}

View File

@ -2,9 +2,9 @@
void DSP::resampleCubic() {
while(resampler.fraction <= 1.0) {
double channel[2];
double channel[settings.channels];
for(unsigned n = 0; n < 2; n++) {
for(unsigned n = 0; n < settings.channels; n++) {
double a = buffer.read(n, -3);
double b = buffer.read(n, -2);
double c = buffer.read(n, -1);
@ -20,7 +20,7 @@ void DSP::resampleCubic() {
channel[n] = A * (mu * 3) + B * (mu * 2) + C * mu + D;
}
resamplerWrite(channel[0], channel[1]);
resamplerWrite(channel);
resampler.fraction += resampler.step;
}

View File

@ -2,9 +2,9 @@
void DSP::resampleHermite() {
while(resampler.fraction <= 1.0) {
double channel[2];
double channel[settings.channels];
for(unsigned n = 0; n < 2; n++) {
for(unsigned n = 0; n < settings.channels; n++) {
double a = buffer.read(n, -3);
double b = buffer.read(n, -2);
double c = buffer.read(n, -1);
@ -32,7 +32,7 @@ void DSP::resampleHermite() {
channel[n] = (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
}
resamplerWrite(channel[0], channel[1]);
resamplerWrite(channel);
resampler.fraction += resampler.step;
}

View File

@ -2,9 +2,9 @@
void DSP::resampleLinear() {
while(resampler.fraction <= 1.0) {
double channel[2];
double channel[settings.channels];
for(unsigned n = 0; n < 2; n++) {
for(unsigned n = 0; n < settings.channels; n++) {
double a = buffer.read(n, -1);
double b = buffer.read(n, -0);
@ -13,7 +13,7 @@ void DSP::resampleLinear() {
channel[n] = a * (1.0 - mu) + b * mu;
}
resamplerWrite(channel[0], channel[1]);
resamplerWrite(channel);
resampler.fraction += resampler.step;
}

View File

@ -2,9 +2,9 @@
void DSP::resamplePoint() {
while(resampler.fraction <= 1.0) {
double channel[2];
double channel[settings.channels];
for(unsigned n = 0; n < 2; n++) {
for(unsigned n = 0; n < settings.channels; n++) {
double a = buffer.read(n, -1);
double b = buffer.read(n, -0);
@ -13,7 +13,7 @@ void DSP::resamplePoint() {
channel[n] = mu < 0.5 ? a : b;
}
resamplerWrite(channel[0], channel[1]);
resamplerWrite(channel);
resampler.fraction += resampler.step;
}

View File

@ -1,5 +1,12 @@
#ifdef NALL_DSP_INTERNAL_HPP
void DSP::setChannels(unsigned channels) {
assert(channels > 0);
buffer.setChannels(channels);
output.setChannels(channels);
settings.channels = channels;
}
void DSP::setPrecision(unsigned precision) {
settings.precision = precision;
settings.intensity = 1 << (settings.precision - 1);

View File

@ -27,6 +27,7 @@ namespace nall {
inline operator bool() const { return valid; }
inline const T& operator()() const { if(!valid) throw; return value; }
inline optional<T>& operator=(const optional<T> &source) { valid = source.valid; value = source.value; return *this; }
inline optional() : valid(false) {}
inline optional(bool valid, const T &value) : valid(valid), value(value) {}
};

View File

@ -53,7 +53,21 @@ void APU::tick() {
if(clock >= 0) co_switch(cpu.thread);
}
void APU::set_irq_line() {
cpu.set_irq_apu_line(frame.irq_pending || dmc.irq_pending);
}
void APU::power() {
for(unsigned n = 0; n < 2; n++) {
rectangle[n].sweep.shift = 0;
rectangle[n].sweep.decrement = 0;
rectangle[n].sweep.period = 0;
rectangle[n].sweep.counter = 1;
rectangle[n].sweep.enable = 0;
rectangle[n].sweep.reload = 0;
rectangle[n].sweep.rectangle_period = 0;
}
reset();
}
@ -70,14 +84,6 @@ void APU::reset() {
rectangle[n].envelope.decay_counter = 0;
rectangle[n].envelope.decay_volume = 0;
rectangle[n].sweep.shift = 0;
rectangle[n].sweep.decrement = 0;
rectangle[n].sweep.period = 0;
rectangle[n].sweep.counter = 0;
rectangle[n].sweep.enable = 0;
rectangle[n].sweep.reload = 0;
rectangle[n].sweep.rectangle_period = 0;
rectangle[n].duty = 0;
rectangle[n].duty_counter = 0;
rectangle[n].period = 0;
@ -108,27 +114,44 @@ void APU::reset() {
noise.short_mode = 0;
noise.lfsr = 1;
dmc.irq = 0;
dmc.loop = 0;
dmc.length_counter = 0;
dmc.irq_pending = 0;
dmc.period = 0;
dmc.counter = 0;
dmc.addr = 0xc000;
dmc.length = 1;
dmc.period_counter = ntsc_dmc_period_table[0];
dmc.irq_enable = 0;
dmc.loop_mode = 0;
dmc.dac_latch = 0;
dmc.addr_latch = 0;
dmc.length_latch = 0;
dmc.read_addr = 0;
dmc.dma_delay_counter = 0;
dmc.bit_counter = 0;
dmc.have_dma_buffer = 0;
dmc.dma_buffer = 0;
dmc.have_sample = 0;
dmc.sample = 0;
frame.irq_pending = 0;
frame.mode = 0;
frame.counter = 0;
frame.divider = 1;
enabled_channels = 0;
//cpu.set_alu_irq_line(frame.irq_pending || dmc.irq_pending);
}
uint8 APU::read(uint16 addr) {
if(addr == 0x4015) {
uint8 result = 0x00;
result |= rectangle[0].length_counter ? 1 : 0;
result |= rectangle[1].length_counter ? 2 : 0;
result |= triangle.length_counter ? 4 : 0;
result |= noise.length_counter ? 8 : 0;
result |= rectangle[0].length_counter ? 0x01 : 0;
result |= rectangle[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;
return result;
}
@ -148,7 +171,7 @@ void APU::write(uint16 addr, uint8 data) {
case 0x4001: case 0x4005:
rectangle[r].sweep.enable = data & 0x80;
rectangle[r].sweep.period = (data >> 4) & 7;
rectangle[r].sweep.period = (data & 0x70) >> 4;
rectangle[r].sweep.decrement = data & 0x08;
rectangle[r].sweep.shift = data & 0x07;
rectangle[r].sweep.reload = true;
@ -216,20 +239,24 @@ void APU::write(uint16 addr, uint8 data) {
break;
case 0x4010:
dmc.irq = data & 0x80;
dmc.loop = data & 0x40;
dmc.period = ntsc_dmc_period_table[data & 0x0f];
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();
break;
case 0x4011:
dmc.counter = data & 0x7f;
dmc.dac_latch = data & 0x7f;
break;
case 0x4012:
dmc.addr = 0xc000 | (data << 6);
dmc.addr_latch = data;
break;
case 0x4013:
dmc.length = (data << 4) | 1;
dmc.length_latch = data;
break;
case 0x4015:
@ -237,6 +264,12 @@ void APU::write(uint16 addr, uint8 data) {
if((data & 0x02) == 0) rectangle[1].length_counter = 0;
if((data & 0x04) == 0) triangle.length_counter = 0;
if((data & 0x08) == 0) noise.length_counter = 0;
if((data & 0x10) == 0) dmc.length_counter = 0;
if((data & 0x10) && dmc.length_counter == 0) dmc.start();
dmc.irq_pending = false;
set_irq_line();
enabled_channels = data & 0x1f;
break;
@ -245,20 +278,34 @@ void APU::write(uint16 addr, uint8 data) {
frame.counter = 0;
if(frame.mode & 2) clock_frame_counter();
frame.divider = 14915;
if(frame.mode & 1) {
frame.irq_pending = false;
set_irq_line();
}
frame.divider = FrameCounter::NtscPeriod;
break;
}
}
void APU::Sweep::clock() {
bool APU::Sweep::check_period() {
if(rectangle_period > 0x7ff) return false;
if(decrement == 0) {
if((rectangle_period + (rectangle_period >> shift)) & 0x800) return false;
}
return true;
}
void APU::Sweep::clock(unsigned channel) {
if(--counter == 0) {
counter = period + 1;
if(enable & shift && rectangle_period > 8) {
if(enable && shift && rectangle_period > 8) {
signed delta = rectangle_period >> shift;
if(decrement) {
//first rectangle decrements by one extra
rectangle_period -= delta + (channel == 0);
rectangle_period -= delta;
if(channel == 0) rectangle_period--;
} else if((rectangle_period + delta) < 0x800) {
rectangle_period += delta;
}
@ -272,7 +319,7 @@ void APU::Sweep::clock() {
}
unsigned APU::Envelope::volume() const {
return use_speed_as_volume ? speed :decay_volume;
return use_speed_as_volume ? speed : decay_volume;
}
void APU::Envelope::clock() {
@ -295,24 +342,13 @@ void APU::Rectangle::clock_length() {
}
}
bool APU::Rectangle::check_period() {
const unsigned raw_period = sweep.rectangle_period;
if(raw_period < 0x008 || raw_period > 0x7ff) return false;
if(sweep.decrement == 0) {
if((raw_period + (raw_period >> sweep.shift)) & 0x800) return false;
}
return true;
}
uint8 APU::Rectangle::clock() {
if(check_period() == false) return 0;
if(sweep.check_period() == false) return 0;
if(length_counter == 0) return 0;
static const unsigned duty_table[] = { 1, 2, 4, 6 };
uint8 result = (duty_counter < duty_table[duty]) ? envelope.volume() : 0;
if(sweep.rectangle_period < 0x008) result = 0;
if(--period_counter == 0) {
period_counter = (sweep.rectangle_period + 1) * 2;
@ -340,11 +376,11 @@ void APU::Triangle::clock_linear_length() {
uint8 APU::Triangle::clock() {
uint8 result = step_counter & 0x0f;
if(step_counter & 0x10) result ^= 0x0f;
if((step_counter & 0x10) == 0) result ^= 0x0f;
if(length_counter == 0 || linear_length_counter == 0) return result;
if(--period_counter == 0) {
step_counter = (step_counter + 1) & 0x1f;
step_counter++;
period_counter = period + 1;
}
@ -378,8 +414,63 @@ uint8 APU::Noise::clock() {
return result;
}
void APU::DMC::start() {
read_addr = 0x4000 + (addr_latch << 6);
length_counter = (length_latch << 4) + 1;
}
uint8 APU::DMC::clock() {
uint8 result = counter;
uint8 result = dac_latch;
if(dma_delay_counter > 0) {
dma_delay_counter--;
if(dma_delay_counter == 1) {
cpu.set_rdy_addr({ true, uint16(0x8000 | read_addr) });
} else if(dma_delay_counter == 0) {
cpu.set_rdy_line(1);
cpu.set_rdy_addr({ false, 0u });
dma_buffer = cpu.mdr();
have_dma_buffer = true;
length_counter--;
read_addr++;
if(length_counter == 0) {
if(loop_mode) {
start();
} else if(irq_enable) {
irq_pending = true;
apu.set_irq_line();
}
}
}
}
if(--period_counter == 0) {
if(have_sample) {
signed delta = (((sample >> bit_counter) & 1) << 2) - 2;
unsigned data = dac_latch + delta;
if((data & 0x80) == 0) dac_latch = data;
}
if(++bit_counter == 0) {
if(have_dma_buffer) {
have_sample = true;
sample = dma_buffer;
have_dma_buffer = false;
} else {
have_sample = false;
}
}
period_counter = ntsc_dmc_period_table[period];
}
if(length_counter > 0 && have_dma_buffer == false && dma_delay_counter == 0) {
cpu.set_rdy_line(0);
dma_delay_counter = 4;
}
return result;
}
@ -389,9 +480,9 @@ void APU::clock_frame_counter() {
if(frame.counter & 1) {
rectangle[0].clock_length();
rectangle[0].sweep.clock();
rectangle[0].sweep.clock(0);
rectangle[1].clock_length();
rectangle[1].sweep.clock();
rectangle[1].sweep.clock(1);
triangle.clock_length();
noise.clock_length();
}
@ -402,7 +493,11 @@ void APU::clock_frame_counter() {
noise.envelope.clock();
if(frame.counter == 0) {
if(frame.mode & 2) frame.divider += 14195;
if(frame.mode & 2) frame.divider += FrameCounter::NtscPeriod;
if(frame.mode == 0) {
frame.irq_pending = true;
set_irq_line();
}
}
}
@ -410,14 +505,11 @@ void APU::clock_frame_counter_divider() {
frame.divider -= 2;
if(frame.divider <= 0) {
clock_frame_counter();
frame.divider += 14195;
frame.divider += FrameCounter::NtscPeriod;
}
}
APU::APU() {
rectangle[0].sweep.channel = 0;
rectangle[1].sweep.channel = 1;
for(unsigned amp = 0; amp < 32; amp++) {
if(amp == 0) {
rectangle_dac[amp] = 0;

View File

@ -2,6 +2,7 @@ struct APU : Processor {
static void Main();
void main();
void tick();
void set_irq_line();
void power();
void reset();
@ -25,8 +26,6 @@ struct APU : Processor {
};
struct Sweep {
bool channel;
uint8 shift;
bool decrement;
uint3 period;
@ -35,7 +34,8 @@ struct APU : Processor {
bool reload;
unsigned rectangle_period;
void clock();
bool check_period();
void clock(unsigned channel);
};
struct Rectangle {
@ -64,7 +64,7 @@ struct APU : Processor {
uint11 period;
unsigned period_counter;
uint8 step_counter;
uint5 step_counter;
uint8 linear_length_counter;
bool reload_linear;
@ -89,18 +89,38 @@ struct APU : Processor {
} noise;
struct DMC {
bool irq;
bool loop;
unsigned period;
unsigned length_counter;
bool irq_pending;
uint8 counter;
uint16 addr;
uint16 length;
uint4 period;
unsigned period_counter;
bool irq_enable;
bool loop_mode;
uint8 dac_latch;
uint8 addr_latch;
uint8 length_latch;
uint15 read_addr;
unsigned dma_delay_counter;
uint3 bit_counter;
bool have_dma_buffer;
uint8 dma_buffer;
bool have_sample;
uint8 sample;
void start();
uint8 clock();
} dmc;
struct FrameCounter {
enum : unsigned { NtscPeriod = 14915 }; //~(21.477MHz / 6 / 240hz)
bool irq_pending;
uint2 mode;
uint2 counter;
signed divider;

View File

@ -35,6 +35,7 @@ void interrupt();
void interrupt_test();
void set_nmi_line(bool);
void set_irq_line(bool);
void set_irq_apu_line(bool);
//opcodes.cpp
void opf_asl();

View File

@ -17,7 +17,7 @@ L abs.h = op_read(vector++);
}
void CPU::interrupt_test() {
status.interrupt_pending = (status.irq_line & ~regs.p.i) | status.nmi_pending;
status.interrupt_pending = ((status.irq_line | status.irq_apu_line) & ~regs.p.i) | status.nmi_pending;
}
void CPU::set_nmi_line(bool line) {
@ -30,3 +30,8 @@ void CPU::set_irq_line(bool line) {
//level-sensitive
status.irq_line = line;
}
void CPU::set_irq_apu_line(bool line) {
//level-sensitive
status.irq_apu_line = line;
}

View File

@ -70,6 +70,13 @@ void CPU::reset() {
status.nmi_pending = false;
status.nmi_line = 0;
status.irq_line = 0;
status.irq_apu_line = 0;
status.rdy_line = 1;
status.rdy_addr = { false, 0x0000 };
status.oam_dma_pending = false;
status.oam_dma_page = 0x00;
status.controller_latch = false;
status.controller_port0 = 0;
@ -80,6 +87,14 @@ uint8 CPU::mdr() const {
return regs.mdr;
}
void CPU::set_rdy_line(bool line) {
status.rdy_line = line;
}
void CPU::set_rdy_addr(optional<uint16> addr) {
status.rdy_addr = addr;
}
uint8 CPU::ram_read(uint16 addr) {
return ram[addr & 0x07ff];
}
@ -104,7 +119,8 @@ uint8 CPU::read(uint16 addr) {
void CPU::write(uint16 addr, uint8 data) {
if(addr == 0x4014) {
return oam_dma(data << 8);
status.oam_dma_page = data;
status.oam_dma_pending = true;
}
if(addr == 0x4016) {
@ -118,10 +134,9 @@ void CPU::write(uint16 addr, uint8 data) {
return apu.write(addr, data);
}
void CPU::oam_dma(uint16 addr) {
op_readpc();
void CPU::oam_dma() {
for(unsigned n = 0; n < 256; n++) {
uint8 data = op_read(addr + n);
uint8 data = op_read((status.oam_dma_page << 8) + n);
op_write(0x2004, data);
}
}

View File

@ -8,6 +8,13 @@ struct CPU : Processor {
bool nmi_pending;
bool nmi_line;
bool irq_line;
bool irq_apu_line;
bool rdy_line;
optional<uint16> rdy_addr;
bool oam_dma_pending;
uint8 oam_dma_page;
bool controller_latch;
unsigned controller_port0;
@ -22,6 +29,8 @@ struct CPU : Processor {
void reset();
uint8 mdr() const;
void set_rdy_line(bool);
void set_rdy_addr(optional<uint16>);
uint8 ram_read(uint16 addr);
void ram_write(uint16 addr, uint8 data);
@ -29,7 +38,7 @@ struct CPU : Processor {
uint8 read(uint16 addr);
void write(uint16 addr, uint8 data);
void oam_dma(uint16 addr);
void oam_dma();
bool trace;
};

View File

@ -1,4 +1,15 @@
uint8 CPU::op_read(uint16 addr) {
if(status.oam_dma_pending) {
status.oam_dma_pending = false;
op_read(addr);
oam_dma();
}
while(status.rdy_line == 0) {
regs.mdr = bus.read(status.rdy_addr ? status.rdy_addr() : addr);
add_clocks(12);
}
regs.mdr = bus.read(addr);
add_clocks(12);
return regs.mdr;

View File

@ -4,7 +4,7 @@
namespace NES {
namespace Info {
static const char Name[] = "bnes";
static const char Version[] = "000.09";
static const char Version[] = "000.10";
}
}

View File

@ -28,13 +28,13 @@ void Audio::sample(int16 left, int16 right) {
}
}
void Audio::coprocessor_sample(int16 left, int16 right) {
dspaudio.sample(left, right);
void Audio::coprocessor_sample(int16 lsample, int16 rsample) {
signed samples[] = { lsample, rsample };
dspaudio.sample(samples);
while(dspaudio.pending()) {
signed left, right;
dspaudio.read(left, right);
dspaudio.read(samples);
cop_buffer[cop_wroffset] = ((uint16)left << 0) + ((uint16)right << 16);
cop_buffer[cop_wroffset] = ((uint16)samples[0] << 0) + ((uint16)samples[1] << 16);
cop_wroffset = (cop_wroffset + 1) & buffer_mask;
cop_length = (cop_length + 1) & buffer_mask;
flush();

View File

@ -65,51 +65,31 @@ Cheat::~Cheat() {
delete[] override;
}
//===============
//encode / decode
//===============
bool Cheat::decode(const char *s, unsigned &addr, unsigned &data, Type &type) {
string t = s;
bool Cheat::decode(const string &code, unsigned &addr, unsigned &data) {
string t = code;
t.lower();
#define ischr(n) ((n >= '0' && n <= '9') || (n >= 'a' && n <= 'f'))
if(strlen(t) == 8 || (strlen(t) == 9 && t[6] == ':')) {
//strip ':'
if(strlen(t) == 9 && t[6] == ':') t = { substr(t, 0, 6), substr(t, 7) };
//validate input
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false;
//Pro Action Replay
if(strlen(t) == 9 && t[6] == ':') t = { substr(t, 0, 6), substr(t, 7) }; //strip ':'
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false; //validate input
unsigned r = hex(t);
type = Type::ProActionReplay;
unsigned r = hex((const char*)t);
addr = r >> 8;
data = r & 0xff;
return true;
} else if(strlen(t) == 9 && t[4] == '-') {
//strip '-'
t = { substr(t, 0, 4), substr(t, 5) };
//validate input
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false;
type = Type::GameGenie;
//Game Genie
t = { substr(t, 0, 4), substr(t, 5) }; //strip '-'
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false; //validate input
t.transform("df4709156bc8a23e", "0123456789abcdef");
unsigned r = hex((const char*)t);
//8421 8421 8421 8421 8421 8421
//abcd efgh ijkl mnop qrst uvwx
//ijkl qrst opab cduv wxef ghmn
addr = (!!(r & 0x002000) << 23) | (!!(r & 0x001000) << 22)
| (!!(r & 0x000800) << 21) | (!!(r & 0x000400) << 20)
| (!!(r & 0x000020) << 19) | (!!(r & 0x000010) << 18)
| (!!(r & 0x000008) << 17) | (!!(r & 0x000004) << 16)
| (!!(r & 0x800000) << 15) | (!!(r & 0x400000) << 14)
| (!!(r & 0x200000) << 13) | (!!(r & 0x100000) << 12)
| (!!(r & 0x000002) << 11) | (!!(r & 0x000001) << 10)
| (!!(r & 0x008000) << 9) | (!!(r & 0x004000) << 8)
| (!!(r & 0x080000) << 7) | (!!(r & 0x040000) << 6)
| (!!(r & 0x020000) << 5) | (!!(r & 0x010000) << 4)
| (!!(r & 0x000200) << 3) | (!!(r & 0x000100) << 2)
| (!!(r & 0x000080) << 1) | (!!(r & 0x000040) << 0);
unsigned r = hex(t);
static unsigned bits[] = { 13, 12, 11, 10, 5, 4, 3, 2, 23, 22, 21, 20, 1, 0, 15, 14, 19, 18, 17, 16, 9, 8, 7, 6 };
addr = 0;
for(unsigned n = 0; n < 24; n++) addr |= r & (1 << bits[n]) ? 0x800000 >> n : 0;
data = r >> 24;
return true;
} else {
@ -119,38 +99,6 @@ bool Cheat::decode(const char *s, unsigned &addr, unsigned &data, Type &type) {
#undef ischr
}
bool Cheat::encode(string &s, unsigned addr, unsigned data, Type type) {
char t[16];
if(type == Type::ProActionReplay) {
s = string(hex<6>(addr), hex<2>(data));
return true;
} else if(type == Type::GameGenie) {
unsigned r = addr;
addr = (!!(r & 0x008000) << 23) | (!!(r & 0x004000) << 22)
| (!!(r & 0x002000) << 21) | (!!(r & 0x001000) << 20)
| (!!(r & 0x000080) << 19) | (!!(r & 0x000040) << 18)
| (!!(r & 0x000020) << 17) | (!!(r & 0x000010) << 16)
| (!!(r & 0x000200) << 15) | (!!(r & 0x000100) << 14)
| (!!(r & 0x800000) << 13) | (!!(r & 0x400000) << 12)
| (!!(r & 0x200000) << 11) | (!!(r & 0x100000) << 10)
| (!!(r & 0x000008) << 9) | (!!(r & 0x000004) << 8)
| (!!(r & 0x000002) << 7) | (!!(r & 0x000001) << 6)
| (!!(r & 0x080000) << 5) | (!!(r & 0x040000) << 4)
| (!!(r & 0x020000) << 3) | (!!(r & 0x010000) << 2)
| (!!(r & 0x000800) << 1) | (!!(r & 0x000400) << 0);
s = string(hex<2>(data), hex<2>(addr >> 16), "-", hex<4>(addr & 0xffff));
s.transform("0123456789abcdef", "df4709156bc8a23e");
return true;
} else {
return false;
}
}
//========
//internal
//========
unsigned Cheat::mirror(unsigned addr) const {
//$00-3f|80-bf:0000-1fff -> $7e:0000-1fff
if((addr & 0x40e000) == 0x000000) return (0x7e0000 + (addr & 0x1fff));

View File

@ -5,7 +5,6 @@ struct CheatCode {
class Cheat : public linear_vector<CheatCode> {
public:
enum class Type : unsigned { ProActionReplay, GameGenie };
uint8 *override;
bool enabled() const;
@ -17,8 +16,7 @@ public:
Cheat();
~Cheat();
static bool decode(const char*, unsigned&, unsigned&, Type&);
static bool encode(string&, unsigned, unsigned, Type);
static bool decode(const string&, unsigned&, unsigned&);
private:
bool system_enabled;

View File

@ -61,8 +61,7 @@ void Interface::setCheats(const lstring &list) {
codelist.split("+", code);
foreach(part, codelist) {
unsigned addr, data;
Cheat::Type type;
if(Cheat::decode(part, addr, data, type)) {
if(Cheat::decode(part, addr, data)) {
cheat.append({ addr, data });
}
}

View File

@ -4,7 +4,7 @@
namespace SNES {
namespace Info {
static const char Name[] = "bsnes";
static const char Version[] = "082.13";
static const char Version[] = "082.14";
static const unsigned SerializerVersion = 22;
}
}

View File

@ -4,7 +4,7 @@ include $(gameboy)/Makefile
name := batch
ui_objects := ui-main ui-config ui-interface ui-utility
ui_objects += ui-general ui-tools
ui_objects += ui-general ui-settings ui-tools
ui_objects += phoenix ruby
ui_objects += $(if $(call streq,$(platform),win),resource)
@ -72,6 +72,7 @@ obj/ui-config.o: $(ui)/config/config.cpp $(call rwildcard,$(ui)/)
obj/ui-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/)
obj/ui-utility.o: $(ui)/utility/utility.cpp $(call rwildcard,$(ui)/)
obj/ui-general.o: $(ui)/general/general.cpp $(call rwildcard,$(ui)/)
obj/ui-settings.o: $(ui)/settings/settings.cpp $(call rwildcard,$(ui)/)
obj/ui-tools.o: $(ui)/tools/tools.cpp $(call rwildcard,$(ui)/)
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)

View File

@ -23,6 +23,7 @@ using namespace ruby;
#include "interface/interface.hpp"
#include "utility/utility.hpp"
#include "general/general.hpp"
#include "settings/settings.hpp"
#include "tools/tools.hpp"
struct Application {

View File

@ -30,6 +30,7 @@ MainWindow::MainWindow() {
settingsSynchronizeAudio.setText("Synchronize Audio");
settingsSynchronizeAudio.setChecked();
settingsMuteAudio.setText("Mute Audio");
settingsConfiguration.setText("Configuration ...");
toolsMenu.setText("Tools");
toolsStateSave.setText("Save State");
@ -78,6 +79,8 @@ MainWindow::MainWindow() {
settingsMenu.append(settingsSynchronizeVideo);
settingsMenu.append(settingsSynchronizeAudio);
settingsMenu.append(settingsMuteAudio);
settingsMenu.append(settingsSeparator);
settingsMenu.append(settingsConfiguration);
append(toolsMenu);
toolsMenu.append(toolsStateSave);
@ -155,6 +158,8 @@ MainWindow::MainWindow() {
dspaudio.setVolume(settingsMuteAudio.checked() ? 0.0 : 1.0);
};
settingsConfiguration.onTick = [&] { settingsWindow->setVisible(); };
toolsStateSave1.onTick = [&] { interface->saveState({ interface->baseName, "-1.bst" }); };
toolsStateSave2.onTick = [&] { interface->saveState({ interface->baseName, "-2.bst" }); };
toolsStateSave3.onTick = [&] { interface->saveState({ interface->baseName, "-3.bst" }); };

View File

@ -28,6 +28,8 @@ struct MainWindow : Window {
CheckItem settingsSynchronizeVideo;
CheckItem settingsSynchronizeAudio;
CheckItem settingsMuteAudio;
Separator settingsSeparator;
Item settingsConfiguration;
Menu toolsMenu;
Menu toolsStateSave;

View File

@ -53,11 +53,11 @@ void InterfaceGameBoy::videoRefresh(const uint8_t *data) {
}
void InterfaceGameBoy::audioSample(int16_t csample, int16_t lsample, int16_t rsample) {
dspaudio.sample(lsample, rsample);
signed samples[] = { lsample, rsample };
dspaudio.sample(samples);
while(dspaudio.pending()) {
signed lsample, rsample;
dspaudio.read(lsample, rsample);
audio.sample(lsample, rsample);
dspaudio.read(samples);
audio.sample(samples[0], samples[1]);
}
}

View File

@ -36,11 +36,11 @@ void InterfaceNES::videoRefresh(const uint16_t *data) {
}
void InterfaceNES::audioSample(int16_t sample) {
dspaudio.sample(sample, sample);
signed samples[] = { sample };
dspaudio.sample(samples);
while(dspaudio.pending()) {
signed lsample, rsample;
dspaudio.read(lsample, rsample);
audio.sample(lsample, rsample);
dspaudio.read(samples);
audio.sample(samples[0], samples[0]); //NES audio output is monaural; ruby only takes stereo audio
}
}

View File

@ -69,11 +69,11 @@ void InterfaceSNES::videoRefresh(const uint16_t *data, bool hires, bool interlac
}
void InterfaceSNES::audioSample(int16_t lsample, int16_t rsample) {
dspaudio.sample(lsample, rsample);
signed samples[] = { lsample, rsample };
dspaudio.sample(samples);
while(dspaudio.pending()) {
signed lsample, rsample;
dspaudio.read(lsample, rsample);
audio.sample(lsample, rsample);
dspaudio.read(samples);
audio.sample(samples[0], samples[1]);
}
}

View File

@ -41,6 +41,7 @@ Application::Application(int argc, char **argv) : quit(false) {
mainWindow = new MainWindow;
fileBrowser = new FileBrowser;
settingsWindow = new SettingsWindow;
cheatEditor = new CheatEditor;
stateManager = new StateManager;
utility->setMode(Interface::Mode::None);
@ -82,6 +83,7 @@ Application::Application(int argc, char **argv) : quit(false) {
Application::~Application() {
delete stateManager;
delete cheatEditor;
delete settingsWindow;
delete fileBrowser;
delete mainWindow;
delete utility;

7
bsnes/ui/settings/settings.cpp Executable file
View File

@ -0,0 +1,7 @@
#include "../base.hpp"
SettingsWindow *settingsWindow = 0;
SettingsWindow::SettingsWindow() {
setTitle("Configuration Settings");
setGeometry({ 128, 128, 640, 360 });
}

5
bsnes/ui/settings/settings.hpp Executable file
View File

@ -0,0 +1,5 @@
struct SettingsWindow : Window {
SettingsWindow();
};
extern SettingsWindow *settingsWindow;

View File

@ -18,18 +18,21 @@ void Utility::setMode(Interface::Mode mode) {
else if(mode == Interface::Mode::NES) {
mainWindow->setTitle({ notdir(interface->baseName), " - ", NES::Info::Name, " v", NES::Info::Version });
mainWindow->nesMenu.setVisible(true);
dspaudio.setChannels(1);
dspaudio.setFrequency(315.0 / 88.8 * 6000000.0 / 12.0);
}
else if(mode == Interface::Mode::SNES) {
mainWindow->setTitle({ notdir(interface->baseName), " - ", SNES::Info::Name, " v", SNES::Info::Version });
mainWindow->snesMenu.setVisible(true);
dspaudio.setChannels(2);
dspaudio.setFrequency(32040.0);
}
else if(mode == Interface::Mode::GameBoy) {
mainWindow->setTitle({ notdir(interface->baseName), " - ", GameBoy::Info::Name, " v", GameBoy::Info::Version });
mainWindow->gameBoyMenu.setVisible(true);
dspaudio.setChannels(2);
dspaudio.setFrequency(4194304.0);
}