diff --git a/bsnes/nall/dsp/buffer.hpp b/bsnes/nall/dsp/buffer.hpp index c1b85b8d..4386d0e9 100755 --- a/bsnes/nall/dsp/buffer.hpp +++ b/bsnes/nall/dsp/buffer.hpp @@ -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); } }; diff --git a/bsnes/nall/dsp/core.hpp b/bsnes/nall/dsp/core.hpp index bec46d93..3761cb2a 100755 --- a/bsnes/nall/dsp/core.hpp +++ b/bsnes/nall/dsp/core.hpp @@ -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); diff --git a/bsnes/nall/dsp/resample/average.hpp b/bsnes/nall/dsp/resample/average.hpp index ca97050e..c5cdbca3 100755 --- a/bsnes/nall/dsp/resample/average.hpp +++ b/bsnes/nall/dsp/resample/average.hpp @@ -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++; diff --git a/bsnes/nall/dsp/resample/cosine.hpp b/bsnes/nall/dsp/resample/cosine.hpp index 56fcf8fe..5405b7f3 100755 --- a/bsnes/nall/dsp/resample/cosine.hpp +++ b/bsnes/nall/dsp/resample/cosine.hpp @@ -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; } diff --git a/bsnes/nall/dsp/resample/cubic.hpp b/bsnes/nall/dsp/resample/cubic.hpp index f975f6aa..71e3766f 100755 --- a/bsnes/nall/dsp/resample/cubic.hpp +++ b/bsnes/nall/dsp/resample/cubic.hpp @@ -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; } diff --git a/bsnes/nall/dsp/resample/hermite.hpp b/bsnes/nall/dsp/resample/hermite.hpp index a042a55b..6eed087d 100755 --- a/bsnes/nall/dsp/resample/hermite.hpp +++ b/bsnes/nall/dsp/resample/hermite.hpp @@ -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; } diff --git a/bsnes/nall/dsp/resample/linear.hpp b/bsnes/nall/dsp/resample/linear.hpp index f4e48f57..3dbda6a0 100755 --- a/bsnes/nall/dsp/resample/linear.hpp +++ b/bsnes/nall/dsp/resample/linear.hpp @@ -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; } diff --git a/bsnes/nall/dsp/resample/point.hpp b/bsnes/nall/dsp/resample/point.hpp index 51d35935..b1cc7dae 100755 --- a/bsnes/nall/dsp/resample/point.hpp +++ b/bsnes/nall/dsp/resample/point.hpp @@ -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; } diff --git a/bsnes/nall/dsp/settings.hpp b/bsnes/nall/dsp/settings.hpp index c7313d3d..dc422e39 100755 --- a/bsnes/nall/dsp/settings.hpp +++ b/bsnes/nall/dsp/settings.hpp @@ -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); diff --git a/bsnes/nall/utility.hpp b/bsnes/nall/utility.hpp index 1f5699e5..374b5469 100755 --- a/bsnes/nall/utility.hpp +++ b/bsnes/nall/utility.hpp @@ -27,6 +27,7 @@ namespace nall { inline operator bool() const { return valid; } inline const T& operator()() const { if(!valid) throw; return value; } inline optional& operator=(const optional &source) { valid = source.valid; value = source.value; return *this; } + inline optional() : valid(false) {} inline optional(bool valid, const T &value) : valid(valid), value(value) {} }; diff --git a/bsnes/nes/apu/apu.cpp b/bsnes/nes/apu/apu.cpp index e3c7ab43..29932714 100755 --- a/bsnes/nes/apu/apu.cpp +++ b/bsnes/nes/apu/apu.cpp @@ -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; diff --git a/bsnes/nes/apu/apu.hpp b/bsnes/nes/apu/apu.hpp index 29ca723f..7e7cafd0 100755 --- a/bsnes/nes/apu/apu.hpp +++ b/bsnes/nes/apu/apu.hpp @@ -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; diff --git a/bsnes/nes/cpu/core/core.hpp b/bsnes/nes/cpu/core/core.hpp index d8f3527d..0a07d4c4 100755 --- a/bsnes/nes/cpu/core/core.hpp +++ b/bsnes/nes/cpu/core/core.hpp @@ -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(); diff --git a/bsnes/nes/cpu/core/interrupt.cpp b/bsnes/nes/cpu/core/interrupt.cpp index 0c094705..a3a76828 100755 --- a/bsnes/nes/cpu/core/interrupt.cpp +++ b/bsnes/nes/cpu/core/interrupt.cpp @@ -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; +} diff --git a/bsnes/nes/cpu/cpu.cpp b/bsnes/nes/cpu/cpu.cpp index df50108a..a5b4f390 100755 --- a/bsnes/nes/cpu/cpu.cpp +++ b/bsnes/nes/cpu/cpu.cpp @@ -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 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); } } diff --git a/bsnes/nes/cpu/cpu.hpp b/bsnes/nes/cpu/cpu.hpp index 09973e85..6cab71c5 100755 --- a/bsnes/nes/cpu/cpu.hpp +++ b/bsnes/nes/cpu/cpu.hpp @@ -8,6 +8,13 @@ struct CPU : Processor { bool nmi_pending; bool nmi_line; bool irq_line; + bool irq_apu_line; + + bool rdy_line; + optional 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); 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; }; diff --git a/bsnes/nes/cpu/memory/memory.cpp b/bsnes/nes/cpu/memory/memory.cpp index 38c26c51..1078e5ae 100755 --- a/bsnes/nes/cpu/memory/memory.cpp +++ b/bsnes/nes/cpu/memory/memory.cpp @@ -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; diff --git a/bsnes/nes/nes.hpp b/bsnes/nes/nes.hpp index 07148dc7..c8e81349 100755 --- a/bsnes/nes/nes.hpp +++ b/bsnes/nes/nes.hpp @@ -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"; } } diff --git a/bsnes/snes/audio/audio.cpp b/bsnes/snes/audio/audio.cpp index 1e0d9886..6ad5f2c8 100755 --- a/bsnes/snes/audio/audio.cpp +++ b/bsnes/snes/audio/audio.cpp @@ -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(); diff --git a/bsnes/snes/cheat/cheat.cpp b/bsnes/snes/cheat/cheat.cpp index 06e79d08..70942017 100755 --- a/bsnes/snes/cheat/cheat.cpp +++ b/bsnes/snes/cheat/cheat.cpp @@ -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)); diff --git a/bsnes/snes/cheat/cheat.hpp b/bsnes/snes/cheat/cheat.hpp index d2c2c17a..458c484c 100755 --- a/bsnes/snes/cheat/cheat.hpp +++ b/bsnes/snes/cheat/cheat.hpp @@ -5,7 +5,6 @@ struct CheatCode { class Cheat : public linear_vector { 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; diff --git a/bsnes/snes/interface/interface.cpp b/bsnes/snes/interface/interface.cpp index b120a5c9..be109f0a 100755 --- a/bsnes/snes/interface/interface.cpp +++ b/bsnes/snes/interface/interface.cpp @@ -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 }); } } diff --git a/bsnes/snes/snes.hpp b/bsnes/snes/snes.hpp index 24f4e2ac..7e87c37a 100755 --- a/bsnes/snes/snes.hpp +++ b/bsnes/snes/snes.hpp @@ -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; } } diff --git a/bsnes/ui/Makefile b/bsnes/ui/Makefile index cbaa3fac..f9354534 100755 --- a/bsnes/ui/Makefile +++ b/bsnes/ui/Makefile @@ -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/*) diff --git a/bsnes/ui/base.hpp b/bsnes/ui/base.hpp index a0a7e652..64dca409 100755 --- a/bsnes/ui/base.hpp +++ b/bsnes/ui/base.hpp @@ -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 { diff --git a/bsnes/ui/general/main-window.cpp b/bsnes/ui/general/main-window.cpp index 7ebf1759..853ceb99 100755 --- a/bsnes/ui/general/main-window.cpp +++ b/bsnes/ui/general/main-window.cpp @@ -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" }); }; diff --git a/bsnes/ui/general/main-window.hpp b/bsnes/ui/general/main-window.hpp index 7c13432e..aae23f66 100755 --- a/bsnes/ui/general/main-window.hpp +++ b/bsnes/ui/general/main-window.hpp @@ -28,6 +28,8 @@ struct MainWindow : Window { CheckItem settingsSynchronizeVideo; CheckItem settingsSynchronizeAudio; CheckItem settingsMuteAudio; + Separator settingsSeparator; + Item settingsConfiguration; Menu toolsMenu; Menu toolsStateSave; diff --git a/bsnes/ui/interface/gameboy.cpp b/bsnes/ui/interface/gameboy.cpp index b18bf2c8..e51872fa 100755 --- a/bsnes/ui/interface/gameboy.cpp +++ b/bsnes/ui/interface/gameboy.cpp @@ -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]); } } diff --git a/bsnes/ui/interface/nes.cpp b/bsnes/ui/interface/nes.cpp index 8971655c..27f7489e 100755 --- a/bsnes/ui/interface/nes.cpp +++ b/bsnes/ui/interface/nes.cpp @@ -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 } } diff --git a/bsnes/ui/interface/snes.cpp b/bsnes/ui/interface/snes.cpp index 85732a35..16958553 100755 --- a/bsnes/ui/interface/snes.cpp +++ b/bsnes/ui/interface/snes.cpp @@ -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]); } } diff --git a/bsnes/ui/main.cpp b/bsnes/ui/main.cpp index f5c380b8..a8dd6ebd 100755 --- a/bsnes/ui/main.cpp +++ b/bsnes/ui/main.cpp @@ -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; diff --git a/bsnes/ui/settings/settings.cpp b/bsnes/ui/settings/settings.cpp new file mode 100755 index 00000000..ffd8b535 --- /dev/null +++ b/bsnes/ui/settings/settings.cpp @@ -0,0 +1,7 @@ +#include "../base.hpp" +SettingsWindow *settingsWindow = 0; + +SettingsWindow::SettingsWindow() { + setTitle("Configuration Settings"); + setGeometry({ 128, 128, 640, 360 }); +} diff --git a/bsnes/ui/settings/settings.hpp b/bsnes/ui/settings/settings.hpp new file mode 100755 index 00000000..0ed081f8 --- /dev/null +++ b/bsnes/ui/settings/settings.hpp @@ -0,0 +1,5 @@ +struct SettingsWindow : Window { + SettingsWindow(); +}; + +extern SettingsWindow *settingsWindow; diff --git a/bsnes/ui/utility/utility.cpp b/bsnes/ui/utility/utility.cpp index 402860d2..e5733bd0 100755 --- a/bsnes/ui/utility/utility.cpp +++ b/bsnes/ui/utility/utility.cpp @@ -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); }