mirror of https://github.com/bsnes-emu/bsnes.git
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:
parent
e3c7bbfb63
commit
7619805266
|
@ -1,35 +1,50 @@
|
||||||
#ifdef NALL_DSP_INTERNAL_HPP
|
#ifdef NALL_DSP_INTERNAL_HPP
|
||||||
|
|
||||||
struct Buffer {
|
struct Buffer {
|
||||||
double *sample[2];
|
double **sample;
|
||||||
uint16_t rdoffset;
|
uint16_t rdoffset;
|
||||||
uint16_t wroffset;
|
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)];
|
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)];
|
return sample[channel][(uint16_t)(wroffset + offset)];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void clear() {
|
inline void clear() {
|
||||||
for(unsigned n = 0; n < 65536; n++) {
|
for(unsigned c = 0; c < channels; c++) {
|
||||||
sample[0][n] = 0;
|
for(unsigned n = 0; n < 65536; n++) {
|
||||||
sample[1][n] = 0;
|
sample[c][n] = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
rdoffset = 0;
|
rdoffset = 0;
|
||||||
wroffset = 0;
|
wroffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer() {
|
Buffer() {
|
||||||
sample[0] = new double[65536];
|
channels = 0;
|
||||||
sample[1] = new double[65536];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
~Buffer() {
|
~Buffer() {
|
||||||
delete[] sample[0];
|
setChannels(0);
|
||||||
delete[] sample[1];
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@ struct DSP {
|
||||||
Average,
|
Average,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline void setChannels(unsigned channels);
|
||||||
inline void setPrecision(unsigned precision);
|
inline void setPrecision(unsigned precision);
|
||||||
inline void setFrequency(double frequency); //inputFrequency
|
inline void setFrequency(double frequency); //inputFrequency
|
||||||
inline void setVolume(double volume);
|
inline void setVolume(double volume);
|
||||||
|
@ -23,9 +24,9 @@ struct DSP {
|
||||||
inline void setResampler(Resampler resampler);
|
inline void setResampler(Resampler resampler);
|
||||||
inline void setResamplerFrequency(double frequency); //outputFrequency
|
inline void setResamplerFrequency(double frequency); //outputFrequency
|
||||||
|
|
||||||
inline void sample(signed lchannel, signed rchannel);
|
inline void sample(signed channel[]);
|
||||||
inline bool pending();
|
inline bool pending();
|
||||||
inline void read(signed &lchannel, signed &rchannel);
|
inline void read(signed channel[]);
|
||||||
|
|
||||||
inline void clear();
|
inline void clear();
|
||||||
inline DSP();
|
inline DSP();
|
||||||
|
@ -33,6 +34,7 @@ struct DSP {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
struct Settings {
|
struct Settings {
|
||||||
|
unsigned channels;
|
||||||
unsigned precision;
|
unsigned precision;
|
||||||
double frequency;
|
double frequency;
|
||||||
double volume;
|
double volume;
|
||||||
|
@ -50,7 +52,7 @@ protected:
|
||||||
} resampler;
|
} resampler;
|
||||||
|
|
||||||
inline void resamplerRun();
|
inline void resamplerRun();
|
||||||
inline void resamplerWrite(double lchannel, double rchannel);
|
inline void resamplerWrite(double channel[]);
|
||||||
|
|
||||||
inline void resamplePoint();
|
inline void resamplePoint();
|
||||||
inline void resampleLinear();
|
inline void resampleLinear();
|
||||||
|
@ -70,9 +72,10 @@ protected:
|
||||||
|
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
void DSP::sample(signed lchannel, signed rchannel) {
|
void DSP::sample(signed channel[]) {
|
||||||
buffer.write(0) = (double)lchannel / settings.intensity;
|
for(unsigned c = 0; c < settings.channels; c++) {
|
||||||
buffer.write(1) = (double)rchannel / settings.intensity;
|
buffer.write(c) = (double)channel[c] / settings.intensity;
|
||||||
|
}
|
||||||
buffer.wroffset++;
|
buffer.wroffset++;
|
||||||
resamplerRun();
|
resamplerRun();
|
||||||
}
|
}
|
||||||
|
@ -81,12 +84,13 @@ bool DSP::pending() {
|
||||||
return output.rdoffset != output.wroffset;
|
return output.rdoffset != output.wroffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DSP::read(signed &lchannel, signed &rchannel) {
|
void DSP::read(signed channel[]) {
|
||||||
adjustVolume();
|
adjustVolume();
|
||||||
adjustBalance();
|
adjustBalance();
|
||||||
|
|
||||||
lchannel = clamp(settings.precision, output.read(0) * settings.intensity);
|
for(unsigned c = 0; c < settings.channels; c++) {
|
||||||
rchannel = clamp(settings.precision, output.read(1) * settings.intensity);
|
channel[c] = clamp(settings.precision, output.read(0) * settings.intensity);
|
||||||
|
}
|
||||||
output.rdoffset++;
|
output.rdoffset++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,9 +105,10 @@ void DSP::resamplerRun() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DSP::resamplerWrite(double lchannel, double rchannel) {
|
void DSP::resamplerWrite(double channel[]) {
|
||||||
output.write(0) = lchannel;
|
for(unsigned c = 0; c < settings.channels; c++) {
|
||||||
output.write(1) = rchannel;
|
output.write(c) = channel[c];
|
||||||
|
}
|
||||||
output.wroffset++;
|
output.wroffset++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,11 +120,13 @@ void DSP::resamplerWrite(double lchannel, double rchannel) {
|
||||||
#include "resample/average.hpp"
|
#include "resample/average.hpp"
|
||||||
|
|
||||||
void DSP::adjustVolume() {
|
void DSP::adjustVolume() {
|
||||||
output.read(0) *= settings.volume;
|
for(unsigned c = 0; c < settings.channels; c++) {
|
||||||
output.read(1) *= settings.volume;
|
output.read(c) *= settings.volume;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DSP::adjustBalance() {
|
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(1) *= 1.0 + settings.balance;
|
||||||
if(settings.balance > 0.0) output.read(0) *= 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() {
|
DSP::DSP() {
|
||||||
|
setChannels(2);
|
||||||
setPrecision(16);
|
setPrecision(16);
|
||||||
setFrequency(44100.0);
|
setFrequency(44100.0);
|
||||||
setVolume(1.0);
|
setVolume(1.0);
|
||||||
|
|
|
@ -9,17 +9,20 @@ void DSP::resampleAverage() {
|
||||||
double scalar = 1.0;
|
double scalar = 1.0;
|
||||||
if(resampler.fraction > resampler.step) scalar = 1.0 - (resampler.fraction - resampler.step);
|
if(resampler.fraction > resampler.step) scalar = 1.0 - (resampler.fraction - resampler.step);
|
||||||
|
|
||||||
output.write(0) += buffer.read(0) * scalar;
|
for(unsigned c = 0; c < settings.channels; c++) {
|
||||||
output.write(1) += buffer.read(1) * scalar;
|
output.write(c) += buffer.read(c) * scalar;
|
||||||
|
}
|
||||||
|
|
||||||
if(resampler.fraction >= resampler.step) {
|
if(resampler.fraction >= resampler.step) {
|
||||||
output.write(0) /= resampler.step;
|
for(unsigned c = 0; c < settings.channels; c++) {
|
||||||
output.write(1) /= resampler.step;
|
output.write(c) /= resampler.step;
|
||||||
|
}
|
||||||
output.wroffset++;
|
output.wroffset++;
|
||||||
|
|
||||||
resampler.fraction -= resampler.step;
|
resampler.fraction -= resampler.step;
|
||||||
output.write(0) = buffer.read(0) * resampler.fraction;
|
for(unsigned c = 0; c < settings.channels; c++) {
|
||||||
output.write(1) = buffer.read(1) * resampler.fraction;
|
output.write(c) = buffer.read(c) * resampler.fraction;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.rdoffset++;
|
buffer.rdoffset++;
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
void DSP::resampleCosine() {
|
void DSP::resampleCosine() {
|
||||||
while(resampler.fraction <= 1.0) {
|
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 a = buffer.read(n, -1);
|
||||||
double b = buffer.read(n, -0);
|
double b = buffer.read(n, -0);
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ void DSP::resampleCosine() {
|
||||||
channel[n] = a * (1.0 - mu) + b * mu;
|
channel[n] = a * (1.0 - mu) + b * mu;
|
||||||
}
|
}
|
||||||
|
|
||||||
resamplerWrite(channel[0], channel[1]);
|
resamplerWrite(channel);
|
||||||
resampler.fraction += resampler.step;
|
resampler.fraction += resampler.step;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
void DSP::resampleCubic() {
|
void DSP::resampleCubic() {
|
||||||
while(resampler.fraction <= 1.0) {
|
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 a = buffer.read(n, -3);
|
||||||
double b = buffer.read(n, -2);
|
double b = buffer.read(n, -2);
|
||||||
double c = buffer.read(n, -1);
|
double c = buffer.read(n, -1);
|
||||||
|
@ -20,7 +20,7 @@ void DSP::resampleCubic() {
|
||||||
channel[n] = A * (mu * 3) + B * (mu * 2) + C * mu + D;
|
channel[n] = A * (mu * 3) + B * (mu * 2) + C * mu + D;
|
||||||
}
|
}
|
||||||
|
|
||||||
resamplerWrite(channel[0], channel[1]);
|
resamplerWrite(channel);
|
||||||
resampler.fraction += resampler.step;
|
resampler.fraction += resampler.step;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
void DSP::resampleHermite() {
|
void DSP::resampleHermite() {
|
||||||
while(resampler.fraction <= 1.0) {
|
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 a = buffer.read(n, -3);
|
||||||
double b = buffer.read(n, -2);
|
double b = buffer.read(n, -2);
|
||||||
double c = buffer.read(n, -1);
|
double c = buffer.read(n, -1);
|
||||||
|
@ -32,7 +32,7 @@ void DSP::resampleHermite() {
|
||||||
channel[n] = (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
|
channel[n] = (a0 * b) + (a1 * m0) + (a2 * m1) + (a3 * c);
|
||||||
}
|
}
|
||||||
|
|
||||||
resamplerWrite(channel[0], channel[1]);
|
resamplerWrite(channel);
|
||||||
resampler.fraction += resampler.step;
|
resampler.fraction += resampler.step;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
void DSP::resampleLinear() {
|
void DSP::resampleLinear() {
|
||||||
while(resampler.fraction <= 1.0) {
|
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 a = buffer.read(n, -1);
|
||||||
double b = buffer.read(n, -0);
|
double b = buffer.read(n, -0);
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ void DSP::resampleLinear() {
|
||||||
channel[n] = a * (1.0 - mu) + b * mu;
|
channel[n] = a * (1.0 - mu) + b * mu;
|
||||||
}
|
}
|
||||||
|
|
||||||
resamplerWrite(channel[0], channel[1]);
|
resamplerWrite(channel);
|
||||||
resampler.fraction += resampler.step;
|
resampler.fraction += resampler.step;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,9 @@
|
||||||
|
|
||||||
void DSP::resamplePoint() {
|
void DSP::resamplePoint() {
|
||||||
while(resampler.fraction <= 1.0) {
|
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 a = buffer.read(n, -1);
|
||||||
double b = buffer.read(n, -0);
|
double b = buffer.read(n, -0);
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ void DSP::resamplePoint() {
|
||||||
channel[n] = mu < 0.5 ? a : b;
|
channel[n] = mu < 0.5 ? a : b;
|
||||||
}
|
}
|
||||||
|
|
||||||
resamplerWrite(channel[0], channel[1]);
|
resamplerWrite(channel);
|
||||||
resampler.fraction += resampler.step;
|
resampler.fraction += resampler.step;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
#ifdef NALL_DSP_INTERNAL_HPP
|
#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) {
|
void DSP::setPrecision(unsigned precision) {
|
||||||
settings.precision = precision;
|
settings.precision = precision;
|
||||||
settings.intensity = 1 << (settings.precision - 1);
|
settings.intensity = 1 << (settings.precision - 1);
|
||||||
|
|
|
@ -27,6 +27,7 @@ namespace nall {
|
||||||
inline operator bool() const { return valid; }
|
inline operator bool() const { return valid; }
|
||||||
inline const T& operator()() const { if(!valid) throw; return value; }
|
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<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) {}
|
inline optional(bool valid, const T &value) : valid(valid), value(value) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,21 @@ void APU::tick() {
|
||||||
if(clock >= 0) co_switch(cpu.thread);
|
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() {
|
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();
|
reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,14 +84,6 @@ void APU::reset() {
|
||||||
rectangle[n].envelope.decay_counter = 0;
|
rectangle[n].envelope.decay_counter = 0;
|
||||||
rectangle[n].envelope.decay_volume = 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 = 0;
|
||||||
rectangle[n].duty_counter = 0;
|
rectangle[n].duty_counter = 0;
|
||||||
rectangle[n].period = 0;
|
rectangle[n].period = 0;
|
||||||
|
@ -108,27 +114,44 @@ void APU::reset() {
|
||||||
noise.short_mode = 0;
|
noise.short_mode = 0;
|
||||||
noise.lfsr = 1;
|
noise.lfsr = 1;
|
||||||
|
|
||||||
dmc.irq = 0;
|
dmc.length_counter = 0;
|
||||||
dmc.loop = 0;
|
dmc.irq_pending = 0;
|
||||||
|
|
||||||
dmc.period = 0;
|
dmc.period = 0;
|
||||||
dmc.counter = 0;
|
dmc.period_counter = ntsc_dmc_period_table[0];
|
||||||
dmc.addr = 0xc000;
|
dmc.irq_enable = 0;
|
||||||
dmc.length = 1;
|
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.mode = 0;
|
||||||
frame.counter = 0;
|
frame.counter = 0;
|
||||||
frame.divider = 1;
|
frame.divider = 1;
|
||||||
|
|
||||||
enabled_channels = 0;
|
enabled_channels = 0;
|
||||||
|
//cpu.set_alu_irq_line(frame.irq_pending || dmc.irq_pending);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8 APU::read(uint16 addr) {
|
uint8 APU::read(uint16 addr) {
|
||||||
if(addr == 0x4015) {
|
if(addr == 0x4015) {
|
||||||
uint8 result = 0x00;
|
uint8 result = 0x00;
|
||||||
result |= rectangle[0].length_counter ? 1 : 0;
|
result |= rectangle[0].length_counter ? 0x01 : 0;
|
||||||
result |= rectangle[1].length_counter ? 2 : 0;
|
result |= rectangle[1].length_counter ? 0x02 : 0;
|
||||||
result |= triangle.length_counter ? 4 : 0;
|
result |= triangle.length_counter ? 0x04 : 0;
|
||||||
result |= noise.length_counter ? 8 : 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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +171,7 @@ void APU::write(uint16 addr, uint8 data) {
|
||||||
|
|
||||||
case 0x4001: case 0x4005:
|
case 0x4001: case 0x4005:
|
||||||
rectangle[r].sweep.enable = data & 0x80;
|
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.decrement = data & 0x08;
|
||||||
rectangle[r].sweep.shift = data & 0x07;
|
rectangle[r].sweep.shift = data & 0x07;
|
||||||
rectangle[r].sweep.reload = true;
|
rectangle[r].sweep.reload = true;
|
||||||
|
@ -216,20 +239,24 @@ void APU::write(uint16 addr, uint8 data) {
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x4010:
|
case 0x4010:
|
||||||
dmc.irq = data & 0x80;
|
dmc.irq_enable = data & 0x80;
|
||||||
dmc.loop = data & 0x40;
|
dmc.loop_mode = data & 0x40;
|
||||||
dmc.period = ntsc_dmc_period_table[data & 0x0f];
|
dmc.period = data & 0x0f;
|
||||||
|
|
||||||
|
dmc.irq_pending = dmc.irq_pending && dmc.irq_enable && !dmc.loop_mode;
|
||||||
|
set_irq_line();
|
||||||
|
break;
|
||||||
|
|
||||||
case 0x4011:
|
case 0x4011:
|
||||||
dmc.counter = data & 0x7f;
|
dmc.dac_latch = data & 0x7f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x4012:
|
case 0x4012:
|
||||||
dmc.addr = 0xc000 | (data << 6);
|
dmc.addr_latch = data;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x4013:
|
case 0x4013:
|
||||||
dmc.length = (data << 4) | 1;
|
dmc.length_latch = data;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 0x4015:
|
case 0x4015:
|
||||||
|
@ -237,6 +264,12 @@ void APU::write(uint16 addr, uint8 data) {
|
||||||
if((data & 0x02) == 0) rectangle[1].length_counter = 0;
|
if((data & 0x02) == 0) rectangle[1].length_counter = 0;
|
||||||
if((data & 0x04) == 0) triangle.length_counter = 0;
|
if((data & 0x04) == 0) triangle.length_counter = 0;
|
||||||
if((data & 0x08) == 0) noise.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;
|
enabled_channels = data & 0x1f;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
@ -245,20 +278,34 @@ void APU::write(uint16 addr, uint8 data) {
|
||||||
|
|
||||||
frame.counter = 0;
|
frame.counter = 0;
|
||||||
if(frame.mode & 2) clock_frame_counter();
|
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;
|
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) {
|
if(--counter == 0) {
|
||||||
counter = period + 1;
|
counter = period + 1;
|
||||||
if(enable & shift && rectangle_period > 8) {
|
if(enable && shift && rectangle_period > 8) {
|
||||||
signed delta = rectangle_period >> shift;
|
signed delta = rectangle_period >> shift;
|
||||||
|
|
||||||
if(decrement) {
|
if(decrement) {
|
||||||
//first rectangle decrements by one extra
|
rectangle_period -= delta;
|
||||||
rectangle_period -= delta + (channel == 0);
|
if(channel == 0) rectangle_period--;
|
||||||
} else if((rectangle_period + delta) < 0x800) {
|
} else if((rectangle_period + delta) < 0x800) {
|
||||||
rectangle_period += delta;
|
rectangle_period += delta;
|
||||||
}
|
}
|
||||||
|
@ -272,7 +319,7 @@ void APU::Sweep::clock() {
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned APU::Envelope::volume() const {
|
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() {
|
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() {
|
uint8 APU::Rectangle::clock() {
|
||||||
if(check_period() == false) return 0;
|
if(sweep.check_period() == false) return 0;
|
||||||
if(length_counter == 0) return 0;
|
if(length_counter == 0) return 0;
|
||||||
|
|
||||||
static const unsigned duty_table[] = { 1, 2, 4, 6 };
|
static const unsigned duty_table[] = { 1, 2, 4, 6 };
|
||||||
uint8 result = (duty_counter < duty_table[duty]) ? envelope.volume() : 0;
|
uint8 result = (duty_counter < duty_table[duty]) ? envelope.volume() : 0;
|
||||||
|
if(sweep.rectangle_period < 0x008) result = 0;
|
||||||
|
|
||||||
if(--period_counter == 0) {
|
if(--period_counter == 0) {
|
||||||
period_counter = (sweep.rectangle_period + 1) * 2;
|
period_counter = (sweep.rectangle_period + 1) * 2;
|
||||||
|
@ -340,11 +376,11 @@ void APU::Triangle::clock_linear_length() {
|
||||||
|
|
||||||
uint8 APU::Triangle::clock() {
|
uint8 APU::Triangle::clock() {
|
||||||
uint8 result = step_counter & 0x0f;
|
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(length_counter == 0 || linear_length_counter == 0) return result;
|
||||||
|
|
||||||
if(--period_counter == 0) {
|
if(--period_counter == 0) {
|
||||||
step_counter = (step_counter + 1) & 0x1f;
|
step_counter++;
|
||||||
period_counter = period + 1;
|
period_counter = period + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,8 +414,63 @@ uint8 APU::Noise::clock() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void APU::DMC::start() {
|
||||||
|
read_addr = 0x4000 + (addr_latch << 6);
|
||||||
|
length_counter = (length_latch << 4) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
uint8 APU::DMC::clock() {
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -389,9 +480,9 @@ void APU::clock_frame_counter() {
|
||||||
|
|
||||||
if(frame.counter & 1) {
|
if(frame.counter & 1) {
|
||||||
rectangle[0].clock_length();
|
rectangle[0].clock_length();
|
||||||
rectangle[0].sweep.clock();
|
rectangle[0].sweep.clock(0);
|
||||||
rectangle[1].clock_length();
|
rectangle[1].clock_length();
|
||||||
rectangle[1].sweep.clock();
|
rectangle[1].sweep.clock(1);
|
||||||
triangle.clock_length();
|
triangle.clock_length();
|
||||||
noise.clock_length();
|
noise.clock_length();
|
||||||
}
|
}
|
||||||
|
@ -402,7 +493,11 @@ void APU::clock_frame_counter() {
|
||||||
noise.envelope.clock();
|
noise.envelope.clock();
|
||||||
|
|
||||||
if(frame.counter == 0) {
|
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;
|
frame.divider -= 2;
|
||||||
if(frame.divider <= 0) {
|
if(frame.divider <= 0) {
|
||||||
clock_frame_counter();
|
clock_frame_counter();
|
||||||
frame.divider += 14195;
|
frame.divider += FrameCounter::NtscPeriod;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
APU::APU() {
|
APU::APU() {
|
||||||
rectangle[0].sweep.channel = 0;
|
|
||||||
rectangle[1].sweep.channel = 1;
|
|
||||||
|
|
||||||
for(unsigned amp = 0; amp < 32; amp++) {
|
for(unsigned amp = 0; amp < 32; amp++) {
|
||||||
if(amp == 0) {
|
if(amp == 0) {
|
||||||
rectangle_dac[amp] = 0;
|
rectangle_dac[amp] = 0;
|
||||||
|
|
|
@ -2,6 +2,7 @@ struct APU : Processor {
|
||||||
static void Main();
|
static void Main();
|
||||||
void main();
|
void main();
|
||||||
void tick();
|
void tick();
|
||||||
|
void set_irq_line();
|
||||||
|
|
||||||
void power();
|
void power();
|
||||||
void reset();
|
void reset();
|
||||||
|
@ -25,8 +26,6 @@ struct APU : Processor {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Sweep {
|
struct Sweep {
|
||||||
bool channel;
|
|
||||||
|
|
||||||
uint8 shift;
|
uint8 shift;
|
||||||
bool decrement;
|
bool decrement;
|
||||||
uint3 period;
|
uint3 period;
|
||||||
|
@ -35,7 +34,8 @@ struct APU : Processor {
|
||||||
bool reload;
|
bool reload;
|
||||||
unsigned rectangle_period;
|
unsigned rectangle_period;
|
||||||
|
|
||||||
void clock();
|
bool check_period();
|
||||||
|
void clock(unsigned channel);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Rectangle {
|
struct Rectangle {
|
||||||
|
@ -64,7 +64,7 @@ struct APU : Processor {
|
||||||
uint11 period;
|
uint11 period;
|
||||||
unsigned period_counter;
|
unsigned period_counter;
|
||||||
|
|
||||||
uint8 step_counter;
|
uint5 step_counter;
|
||||||
uint8 linear_length_counter;
|
uint8 linear_length_counter;
|
||||||
bool reload_linear;
|
bool reload_linear;
|
||||||
|
|
||||||
|
@ -89,18 +89,38 @@ struct APU : Processor {
|
||||||
} noise;
|
} noise;
|
||||||
|
|
||||||
struct DMC {
|
struct DMC {
|
||||||
bool irq;
|
unsigned length_counter;
|
||||||
bool loop;
|
bool irq_pending;
|
||||||
unsigned period;
|
|
||||||
|
|
||||||
uint8 counter;
|
uint4 period;
|
||||||
uint16 addr;
|
unsigned period_counter;
|
||||||
uint16 length;
|
|
||||||
|
|
||||||
|
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();
|
uint8 clock();
|
||||||
} dmc;
|
} dmc;
|
||||||
|
|
||||||
struct FrameCounter {
|
struct FrameCounter {
|
||||||
|
enum : unsigned { NtscPeriod = 14915 }; //~(21.477MHz / 6 / 240hz)
|
||||||
|
|
||||||
|
bool irq_pending;
|
||||||
|
|
||||||
uint2 mode;
|
uint2 mode;
|
||||||
uint2 counter;
|
uint2 counter;
|
||||||
signed divider;
|
signed divider;
|
||||||
|
|
|
@ -35,6 +35,7 @@ void interrupt();
|
||||||
void interrupt_test();
|
void interrupt_test();
|
||||||
void set_nmi_line(bool);
|
void set_nmi_line(bool);
|
||||||
void set_irq_line(bool);
|
void set_irq_line(bool);
|
||||||
|
void set_irq_apu_line(bool);
|
||||||
|
|
||||||
//opcodes.cpp
|
//opcodes.cpp
|
||||||
void opf_asl();
|
void opf_asl();
|
||||||
|
|
|
@ -17,7 +17,7 @@ L abs.h = op_read(vector++);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::interrupt_test() {
|
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) {
|
void CPU::set_nmi_line(bool line) {
|
||||||
|
@ -30,3 +30,8 @@ void CPU::set_irq_line(bool line) {
|
||||||
//level-sensitive
|
//level-sensitive
|
||||||
status.irq_line = line;
|
status.irq_line = line;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CPU::set_irq_apu_line(bool line) {
|
||||||
|
//level-sensitive
|
||||||
|
status.irq_apu_line = line;
|
||||||
|
}
|
||||||
|
|
|
@ -70,6 +70,13 @@ void CPU::reset() {
|
||||||
status.nmi_pending = false;
|
status.nmi_pending = false;
|
||||||
status.nmi_line = 0;
|
status.nmi_line = 0;
|
||||||
status.irq_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_latch = false;
|
||||||
status.controller_port0 = 0;
|
status.controller_port0 = 0;
|
||||||
|
@ -80,6 +87,14 @@ uint8 CPU::mdr() const {
|
||||||
return regs.mdr;
|
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) {
|
uint8 CPU::ram_read(uint16 addr) {
|
||||||
return ram[addr & 0x07ff];
|
return ram[addr & 0x07ff];
|
||||||
}
|
}
|
||||||
|
@ -104,7 +119,8 @@ uint8 CPU::read(uint16 addr) {
|
||||||
|
|
||||||
void CPU::write(uint16 addr, uint8 data) {
|
void CPU::write(uint16 addr, uint8 data) {
|
||||||
if(addr == 0x4014) {
|
if(addr == 0x4014) {
|
||||||
return oam_dma(data << 8);
|
status.oam_dma_page = data;
|
||||||
|
status.oam_dma_pending = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(addr == 0x4016) {
|
if(addr == 0x4016) {
|
||||||
|
@ -118,10 +134,9 @@ void CPU::write(uint16 addr, uint8 data) {
|
||||||
return apu.write(addr, data);
|
return apu.write(addr, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CPU::oam_dma(uint16 addr) {
|
void CPU::oam_dma() {
|
||||||
op_readpc();
|
|
||||||
for(unsigned n = 0; n < 256; n++) {
|
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);
|
op_write(0x2004, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,13 @@ struct CPU : Processor {
|
||||||
bool nmi_pending;
|
bool nmi_pending;
|
||||||
bool nmi_line;
|
bool nmi_line;
|
||||||
bool irq_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;
|
bool controller_latch;
|
||||||
unsigned controller_port0;
|
unsigned controller_port0;
|
||||||
|
@ -22,6 +29,8 @@ struct CPU : Processor {
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
uint8 mdr() const;
|
uint8 mdr() const;
|
||||||
|
void set_rdy_line(bool);
|
||||||
|
void set_rdy_addr(optional<uint16>);
|
||||||
|
|
||||||
uint8 ram_read(uint16 addr);
|
uint8 ram_read(uint16 addr);
|
||||||
void ram_write(uint16 addr, uint8 data);
|
void ram_write(uint16 addr, uint8 data);
|
||||||
|
@ -29,7 +38,7 @@ struct CPU : Processor {
|
||||||
uint8 read(uint16 addr);
|
uint8 read(uint16 addr);
|
||||||
void write(uint16 addr, uint8 data);
|
void write(uint16 addr, uint8 data);
|
||||||
|
|
||||||
void oam_dma(uint16 addr);
|
void oam_dma();
|
||||||
|
|
||||||
bool trace;
|
bool trace;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,4 +1,15 @@
|
||||||
uint8 CPU::op_read(uint16 addr) {
|
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);
|
regs.mdr = bus.read(addr);
|
||||||
add_clocks(12);
|
add_clocks(12);
|
||||||
return regs.mdr;
|
return regs.mdr;
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
namespace NES {
|
namespace NES {
|
||||||
namespace Info {
|
namespace Info {
|
||||||
static const char Name[] = "bnes";
|
static const char Name[] = "bnes";
|
||||||
static const char Version[] = "000.09";
|
static const char Version[] = "000.10";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,13 +28,13 @@ void Audio::sample(int16 left, int16 right) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Audio::coprocessor_sample(int16 left, int16 right) {
|
void Audio::coprocessor_sample(int16 lsample, int16 rsample) {
|
||||||
dspaudio.sample(left, right);
|
signed samples[] = { lsample, rsample };
|
||||||
|
dspaudio.sample(samples);
|
||||||
while(dspaudio.pending()) {
|
while(dspaudio.pending()) {
|
||||||
signed left, right;
|
dspaudio.read(samples);
|
||||||
dspaudio.read(left, right);
|
|
||||||
|
|
||||||
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_wroffset = (cop_wroffset + 1) & buffer_mask;
|
||||||
cop_length = (cop_length + 1) & buffer_mask;
|
cop_length = (cop_length + 1) & buffer_mask;
|
||||||
flush();
|
flush();
|
||||||
|
|
|
@ -65,51 +65,31 @@ Cheat::~Cheat() {
|
||||||
delete[] override;
|
delete[] override;
|
||||||
}
|
}
|
||||||
|
|
||||||
//===============
|
bool Cheat::decode(const string &code, unsigned &addr, unsigned &data) {
|
||||||
//encode / decode
|
string t = code;
|
||||||
//===============
|
|
||||||
|
|
||||||
bool Cheat::decode(const char *s, unsigned &addr, unsigned &data, Type &type) {
|
|
||||||
string t = s;
|
|
||||||
t.lower();
|
t.lower();
|
||||||
|
|
||||||
#define ischr(n) ((n >= '0' && n <= '9') || (n >= 'a' && n <= 'f'))
|
#define ischr(n) ((n >= '0' && n <= '9') || (n >= 'a' && n <= 'f'))
|
||||||
|
|
||||||
if(strlen(t) == 8 || (strlen(t) == 9 && t[6] == ':')) {
|
if(strlen(t) == 8 || (strlen(t) == 9 && t[6] == ':')) {
|
||||||
//strip ':'
|
//Pro Action Replay
|
||||||
if(strlen(t) == 9 && t[6] == ':') t = { substr(t, 0, 6), substr(t, 7) };
|
if(strlen(t) == 9 && t[6] == ':') t = { substr(t, 0, 6), substr(t, 7) }; //strip ':'
|
||||||
//validate input
|
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false; //validate input
|
||||||
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false;
|
unsigned r = hex(t);
|
||||||
|
|
||||||
type = Type::ProActionReplay;
|
|
||||||
unsigned r = hex((const char*)t);
|
|
||||||
addr = r >> 8;
|
addr = r >> 8;
|
||||||
data = r & 0xff;
|
data = r & 0xff;
|
||||||
return true;
|
return true;
|
||||||
} else if(strlen(t) == 9 && t[4] == '-') {
|
} else if(strlen(t) == 9 && t[4] == '-') {
|
||||||
//strip '-'
|
//Game Genie
|
||||||
t = { substr(t, 0, 4), substr(t, 5) };
|
t = { substr(t, 0, 4), substr(t, 5) }; //strip '-'
|
||||||
//validate input
|
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false; //validate input
|
||||||
for(unsigned i = 0; i < 8; i++) if(!ischr(t[i])) return false;
|
|
||||||
|
|
||||||
type = Type::GameGenie;
|
|
||||||
t.transform("df4709156bc8a23e", "0123456789abcdef");
|
t.transform("df4709156bc8a23e", "0123456789abcdef");
|
||||||
unsigned r = hex((const char*)t);
|
unsigned r = hex(t);
|
||||||
//8421 8421 8421 8421 8421 8421
|
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 };
|
||||||
//abcd efgh ijkl mnop qrst uvwx
|
|
||||||
//ijkl qrst opab cduv wxef ghmn
|
addr = 0;
|
||||||
addr = (!!(r & 0x002000) << 23) | (!!(r & 0x001000) << 22)
|
for(unsigned n = 0; n < 24; n++) addr |= r & (1 << bits[n]) ? 0x800000 >> n : 0;
|
||||||
| (!!(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);
|
|
||||||
data = r >> 24;
|
data = r >> 24;
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
|
@ -119,38 +99,6 @@ bool Cheat::decode(const char *s, unsigned &addr, unsigned &data, Type &type) {
|
||||||
#undef ischr
|
#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 {
|
unsigned Cheat::mirror(unsigned addr) const {
|
||||||
//$00-3f|80-bf:0000-1fff -> $7e:0000-1fff
|
//$00-3f|80-bf:0000-1fff -> $7e:0000-1fff
|
||||||
if((addr & 0x40e000) == 0x000000) return (0x7e0000 + (addr & 0x1fff));
|
if((addr & 0x40e000) == 0x000000) return (0x7e0000 + (addr & 0x1fff));
|
||||||
|
|
|
@ -5,7 +5,6 @@ struct CheatCode {
|
||||||
|
|
||||||
class Cheat : public linear_vector<CheatCode> {
|
class Cheat : public linear_vector<CheatCode> {
|
||||||
public:
|
public:
|
||||||
enum class Type : unsigned { ProActionReplay, GameGenie };
|
|
||||||
uint8 *override;
|
uint8 *override;
|
||||||
|
|
||||||
bool enabled() const;
|
bool enabled() const;
|
||||||
|
@ -17,8 +16,7 @@ public:
|
||||||
Cheat();
|
Cheat();
|
||||||
~Cheat();
|
~Cheat();
|
||||||
|
|
||||||
static bool decode(const char*, unsigned&, unsigned&, Type&);
|
static bool decode(const string&, unsigned&, unsigned&);
|
||||||
static bool encode(string&, unsigned, unsigned, Type);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool system_enabled;
|
bool system_enabled;
|
||||||
|
|
|
@ -61,8 +61,7 @@ void Interface::setCheats(const lstring &list) {
|
||||||
codelist.split("+", code);
|
codelist.split("+", code);
|
||||||
foreach(part, codelist) {
|
foreach(part, codelist) {
|
||||||
unsigned addr, data;
|
unsigned addr, data;
|
||||||
Cheat::Type type;
|
if(Cheat::decode(part, addr, data)) {
|
||||||
if(Cheat::decode(part, addr, data, type)) {
|
|
||||||
cheat.append({ addr, data });
|
cheat.append({ addr, data });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
namespace SNES {
|
namespace SNES {
|
||||||
namespace Info {
|
namespace Info {
|
||||||
static const char Name[] = "bsnes";
|
static const char Name[] = "bsnes";
|
||||||
static const char Version[] = "082.13";
|
static const char Version[] = "082.14";
|
||||||
static const unsigned SerializerVersion = 22;
|
static const unsigned SerializerVersion = 22;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ include $(gameboy)/Makefile
|
||||||
name := batch
|
name := batch
|
||||||
|
|
||||||
ui_objects := ui-main ui-config ui-interface ui-utility
|
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 += phoenix ruby
|
||||||
ui_objects += $(if $(call streq,$(platform),win),resource)
|
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-interface.o: $(ui)/interface/interface.cpp $(call rwildcard,$(ui)/)
|
||||||
obj/ui-utility.o: $(ui)/utility/utility.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-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/ui-tools.o: $(ui)/tools/tools.cpp $(call rwildcard,$(ui)/)
|
||||||
|
|
||||||
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)
|
obj/ruby.o: ruby/ruby.cpp $(call rwildcard,ruby/*)
|
||||||
|
|
|
@ -23,6 +23,7 @@ using namespace ruby;
|
||||||
#include "interface/interface.hpp"
|
#include "interface/interface.hpp"
|
||||||
#include "utility/utility.hpp"
|
#include "utility/utility.hpp"
|
||||||
#include "general/general.hpp"
|
#include "general/general.hpp"
|
||||||
|
#include "settings/settings.hpp"
|
||||||
#include "tools/tools.hpp"
|
#include "tools/tools.hpp"
|
||||||
|
|
||||||
struct Application {
|
struct Application {
|
||||||
|
|
|
@ -30,6 +30,7 @@ MainWindow::MainWindow() {
|
||||||
settingsSynchronizeAudio.setText("Synchronize Audio");
|
settingsSynchronizeAudio.setText("Synchronize Audio");
|
||||||
settingsSynchronizeAudio.setChecked();
|
settingsSynchronizeAudio.setChecked();
|
||||||
settingsMuteAudio.setText("Mute Audio");
|
settingsMuteAudio.setText("Mute Audio");
|
||||||
|
settingsConfiguration.setText("Configuration ...");
|
||||||
|
|
||||||
toolsMenu.setText("Tools");
|
toolsMenu.setText("Tools");
|
||||||
toolsStateSave.setText("Save State");
|
toolsStateSave.setText("Save State");
|
||||||
|
@ -78,6 +79,8 @@ MainWindow::MainWindow() {
|
||||||
settingsMenu.append(settingsSynchronizeVideo);
|
settingsMenu.append(settingsSynchronizeVideo);
|
||||||
settingsMenu.append(settingsSynchronizeAudio);
|
settingsMenu.append(settingsSynchronizeAudio);
|
||||||
settingsMenu.append(settingsMuteAudio);
|
settingsMenu.append(settingsMuteAudio);
|
||||||
|
settingsMenu.append(settingsSeparator);
|
||||||
|
settingsMenu.append(settingsConfiguration);
|
||||||
|
|
||||||
append(toolsMenu);
|
append(toolsMenu);
|
||||||
toolsMenu.append(toolsStateSave);
|
toolsMenu.append(toolsStateSave);
|
||||||
|
@ -155,6 +158,8 @@ MainWindow::MainWindow() {
|
||||||
dspaudio.setVolume(settingsMuteAudio.checked() ? 0.0 : 1.0);
|
dspaudio.setVolume(settingsMuteAudio.checked() ? 0.0 : 1.0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
settingsConfiguration.onTick = [&] { settingsWindow->setVisible(); };
|
||||||
|
|
||||||
toolsStateSave1.onTick = [&] { interface->saveState({ interface->baseName, "-1.bst" }); };
|
toolsStateSave1.onTick = [&] { interface->saveState({ interface->baseName, "-1.bst" }); };
|
||||||
toolsStateSave2.onTick = [&] { interface->saveState({ interface->baseName, "-2.bst" }); };
|
toolsStateSave2.onTick = [&] { interface->saveState({ interface->baseName, "-2.bst" }); };
|
||||||
toolsStateSave3.onTick = [&] { interface->saveState({ interface->baseName, "-3.bst" }); };
|
toolsStateSave3.onTick = [&] { interface->saveState({ interface->baseName, "-3.bst" }); };
|
||||||
|
|
|
@ -28,6 +28,8 @@ struct MainWindow : Window {
|
||||||
CheckItem settingsSynchronizeVideo;
|
CheckItem settingsSynchronizeVideo;
|
||||||
CheckItem settingsSynchronizeAudio;
|
CheckItem settingsSynchronizeAudio;
|
||||||
CheckItem settingsMuteAudio;
|
CheckItem settingsMuteAudio;
|
||||||
|
Separator settingsSeparator;
|
||||||
|
Item settingsConfiguration;
|
||||||
|
|
||||||
Menu toolsMenu;
|
Menu toolsMenu;
|
||||||
Menu toolsStateSave;
|
Menu toolsStateSave;
|
||||||
|
|
|
@ -53,11 +53,11 @@ void InterfaceGameBoy::videoRefresh(const uint8_t *data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InterfaceGameBoy::audioSample(int16_t csample, int16_t lsample, int16_t rsample) {
|
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()) {
|
while(dspaudio.pending()) {
|
||||||
signed lsample, rsample;
|
dspaudio.read(samples);
|
||||||
dspaudio.read(lsample, rsample);
|
audio.sample(samples[0], samples[1]);
|
||||||
audio.sample(lsample, rsample);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,11 +36,11 @@ void InterfaceNES::videoRefresh(const uint16_t *data) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void InterfaceNES::audioSample(int16_t sample) {
|
void InterfaceNES::audioSample(int16_t sample) {
|
||||||
dspaudio.sample(sample, sample);
|
signed samples[] = { sample };
|
||||||
|
dspaudio.sample(samples);
|
||||||
while(dspaudio.pending()) {
|
while(dspaudio.pending()) {
|
||||||
signed lsample, rsample;
|
dspaudio.read(samples);
|
||||||
dspaudio.read(lsample, rsample);
|
audio.sample(samples[0], samples[0]); //NES audio output is monaural; ruby only takes stereo audio
|
||||||
audio.sample(lsample, rsample);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -69,11 +69,11 @@ void InterfaceSNES::videoRefresh(const uint16_t *data, bool hires, bool interlac
|
||||||
}
|
}
|
||||||
|
|
||||||
void InterfaceSNES::audioSample(int16_t lsample, int16_t rsample) {
|
void InterfaceSNES::audioSample(int16_t lsample, int16_t rsample) {
|
||||||
dspaudio.sample(lsample, rsample);
|
signed samples[] = { lsample, rsample };
|
||||||
|
dspaudio.sample(samples);
|
||||||
while(dspaudio.pending()) {
|
while(dspaudio.pending()) {
|
||||||
signed lsample, rsample;
|
dspaudio.read(samples);
|
||||||
dspaudio.read(lsample, rsample);
|
audio.sample(samples[0], samples[1]);
|
||||||
audio.sample(lsample, rsample);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ Application::Application(int argc, char **argv) : quit(false) {
|
||||||
|
|
||||||
mainWindow = new MainWindow;
|
mainWindow = new MainWindow;
|
||||||
fileBrowser = new FileBrowser;
|
fileBrowser = new FileBrowser;
|
||||||
|
settingsWindow = new SettingsWindow;
|
||||||
cheatEditor = new CheatEditor;
|
cheatEditor = new CheatEditor;
|
||||||
stateManager = new StateManager;
|
stateManager = new StateManager;
|
||||||
utility->setMode(Interface::Mode::None);
|
utility->setMode(Interface::Mode::None);
|
||||||
|
@ -82,6 +83,7 @@ Application::Application(int argc, char **argv) : quit(false) {
|
||||||
Application::~Application() {
|
Application::~Application() {
|
||||||
delete stateManager;
|
delete stateManager;
|
||||||
delete cheatEditor;
|
delete cheatEditor;
|
||||||
|
delete settingsWindow;
|
||||||
delete fileBrowser;
|
delete fileBrowser;
|
||||||
delete mainWindow;
|
delete mainWindow;
|
||||||
delete utility;
|
delete utility;
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
#include "../base.hpp"
|
||||||
|
SettingsWindow *settingsWindow = 0;
|
||||||
|
|
||||||
|
SettingsWindow::SettingsWindow() {
|
||||||
|
setTitle("Configuration Settings");
|
||||||
|
setGeometry({ 128, 128, 640, 360 });
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
struct SettingsWindow : Window {
|
||||||
|
SettingsWindow();
|
||||||
|
};
|
||||||
|
|
||||||
|
extern SettingsWindow *settingsWindow;
|
|
@ -18,18 +18,21 @@ void Utility::setMode(Interface::Mode mode) {
|
||||||
else if(mode == Interface::Mode::NES) {
|
else if(mode == Interface::Mode::NES) {
|
||||||
mainWindow->setTitle({ notdir(interface->baseName), " - ", NES::Info::Name, " v", NES::Info::Version });
|
mainWindow->setTitle({ notdir(interface->baseName), " - ", NES::Info::Name, " v", NES::Info::Version });
|
||||||
mainWindow->nesMenu.setVisible(true);
|
mainWindow->nesMenu.setVisible(true);
|
||||||
|
dspaudio.setChannels(1);
|
||||||
dspaudio.setFrequency(315.0 / 88.8 * 6000000.0 / 12.0);
|
dspaudio.setFrequency(315.0 / 88.8 * 6000000.0 / 12.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if(mode == Interface::Mode::SNES) {
|
else if(mode == Interface::Mode::SNES) {
|
||||||
mainWindow->setTitle({ notdir(interface->baseName), " - ", SNES::Info::Name, " v", SNES::Info::Version });
|
mainWindow->setTitle({ notdir(interface->baseName), " - ", SNES::Info::Name, " v", SNES::Info::Version });
|
||||||
mainWindow->snesMenu.setVisible(true);
|
mainWindow->snesMenu.setVisible(true);
|
||||||
|
dspaudio.setChannels(2);
|
||||||
dspaudio.setFrequency(32040.0);
|
dspaudio.setFrequency(32040.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
else if(mode == Interface::Mode::GameBoy) {
|
else if(mode == Interface::Mode::GameBoy) {
|
||||||
mainWindow->setTitle({ notdir(interface->baseName), " - ", GameBoy::Info::Name, " v", GameBoy::Info::Version });
|
mainWindow->setTitle({ notdir(interface->baseName), " - ", GameBoy::Info::Name, " v", GameBoy::Info::Version });
|
||||||
mainWindow->gameBoyMenu.setVisible(true);
|
mainWindow->gameBoyMenu.setVisible(true);
|
||||||
|
dspaudio.setChannels(2);
|
||||||
dspaudio.setFrequency(4194304.0);
|
dspaudio.setFrequency(4194304.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue