diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj index d1ec2205ae..6f2bb02f78 100644 --- a/BizHawk.Emulation/BizHawk.Emulation.csproj +++ b/BizHawk.Emulation/BizHawk.Emulation.csproj @@ -210,6 +210,7 @@ + diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/BisqAPU.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/BisqAPU.cs new file mode 100644 index 0000000000..3d38c7dd47 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/BisqAPU.cs @@ -0,0 +1,655 @@ +using System; +using System.IO; +using System.Collections.Generic; + +using BizHawk.Emulation.Sound; + +//http://wiki.nesdev.com/w/index.php/APU_Mixer_Emulation +//http://wiki.nesdev.com/w/index.php/APU +//http://wiki.nesdev.com/w/index.php/APU_Pulse +//sequencer ref: http://wiki.nesdev.com/w/index.php/APU_Frame_Counter + +//TODO - refactor length counter to be separate component + +namespace BizHawk.Emulation.Consoles.Nintendo +{ + partial class NES + { + public class BisqAPU : ISoundProvider + { + class RegBitSetStore + { + public RegBitSetStore(int numUints) + { + data = new uint[numUints]; + } + public uint[] data; + } + + class RegBitSet + { + RegBitSetStore store; + int mask; + int bitno, nbits, dim, index; + public RegBitSet(RegBitSetStore store, int bitno, int nbits, int dim = 1, int index = 0) + { + this.store = store; + this.bitno = bitno; + this.nbits = nbits; + this.dim = dim; + this.index = index; + mask = ((1 << (nbits / 2)) << (nbits - (nbits / 2))) - 1; + } + + public static implicit operator int(RegBitSet rhs) { return (int)rhs.get(); } + public static implicit operator uint(RegBitSet rhs) { return rhs.get(); } + public static explicit operator bool(RegBitSet rhs) { return rhs.get() != 0; } + + public uint Value { get { return get(); } set { set(value); } } + + public uint this[int offset] + { + get + { + return get(offset); + } + set + { + set(value, offset); + } + } + + public uint get(int offset = 0) + { + return (uint)((store.data[index + offset] >> bitno) & mask); + } + + public void set(uint val, int offset = 0) + { + long temp = (store.data[index + offset] & ~(mask << bitno)) + | ((nbits > 1 ? val & mask : ((val != 0) ? 1 : 0)) << bitno); + store.data[index + offset] = (uint)temp; + } + + public static uint operator ^(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() ^ rhs)); } + public static uint operator |(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() | rhs)); } + public static uint operator &(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() & rhs)); } + public static uint operator +(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() + rhs)); } + public static uint operator -(RegBitSet lhs, uint rhs) { return ((uint)(lhs.get() - rhs)); } + }; + + class SignedRegBitSet + { + RegBitSetStore store; + int mask; + int bitno, nbits, dim, index; + public SignedRegBitSet(RegBitSetStore store, int bitno, int nbits, int dim = 1, int index = 0) + { + this.store = store; + this.bitno = bitno; + this.nbits = nbits; + this.dim = dim; + this.index = index; + mask = ((1 << (nbits / 2)) << (nbits - (nbits / 2))) - 1; + } + + public static implicit operator int(SignedRegBitSet rhs) { return rhs.get(); } + public static implicit operator uint(SignedRegBitSet rhs) { return (uint)rhs.get(); } + public static explicit operator bool(SignedRegBitSet rhs) { return rhs.get() != 0; } + + public int Value { get { return get(); } set { set(value); } } + + public int this[int offset] + { + get + { + return get(offset); + } + set + { + set(value, offset); + } + } + + public int get(int offset = 0) + { + return (int)((store.data[index + offset] >> bitno) & mask); + } + + public void set(int val, int offset = 0) + { + long temp = ((int)store.data[index + offset] & (int)~(mask << bitno)) + | ((nbits > 1 ? val & mask : ((val != 0) ? 1 : 0)) << bitno); + store.data[index + offset] = (uint)temp; + } + + public static int operator ^(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() ^ rhs)); } + public static int operator |(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() | rhs)); } + public static int operator &(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() & rhs)); } + public static int operator +(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() + rhs)); } + public static int operator -(SignedRegBitSet lhs, int rhs) { return ((int)(lhs.get() - rhs)); } + + }; + + + public static bool CFG_USE_METASPU = true; + + class APUdata + { + public APUdata() + { + bs = new RegBitSetStore(5); + ChannelsEnabled = new RegBitSet(bs, 15, 1, 5, 0); + frame_delay = new RegBitSet(bs, 0, 15, 5, 0); + frame = new RegBitSet(bs, 0, 8, 5, 1); + FiveCycleDivider = new RegBitSet(bs, 8, 1, 5, 1); + IRQdisable = new RegBitSet(bs, 9, 1, 5, 1); + DMC_CycleCost = new RegBitSet(bs, 10, 3, 5, 1); + IRQ_delay = new RegBitSet(bs, 0, 15, 5, 2); + } + public RegBitSetStore bs; + public RegBitSet ChannelsEnabled; + public RegBitSet frame_delay; + public RegBitSet frame; + public RegBitSet FiveCycleDivider; + public RegBitSet IRQdisable; + public RegBitSet DMC_CycleCost; + public RegBitSet IRQ_delay; + } + APUdata data = new APUdata(); + + class APUchannel + { + public APUchannel() + { + bs = new RegBitSetStore(11); + + reg0 = new RegBitSet(bs,0, 8, 11, 0); + DutyCycle = new RegBitSet(bs,6, 2, 11, 0); + EnvDecayDisable = new RegBitSet(bs,4, 1, 11, 0); + EnvDecayRate = new RegBitSet(bs,0, 4, 11, 0); + EnvDecayLoopEnable = new RegBitSet(bs,5, 1, 11, 0); + FixedVolume = new RegBitSet(bs,0, 4, 11, 0); + LengthCounterDisable = new RegBitSet(bs,5, 1, 11, 0); + LinearCounterInit = new RegBitSet(bs,0, 7, 11, 0); + LinearCounterDisable = new RegBitSet(bs,7, 1, 11, 0); + + reg1 = new RegBitSet(bs,8, 8, 11, 0); + SweepShift = new RegBitSet(bs,8, 3, 11, 0); + SweepDecrease = new RegBitSet(bs,11, 1, 11, 0); + SweepRate = new RegBitSet(bs,12, 3, 11, 0); + SweepEnable = new RegBitSet(bs,15, 1, 11, 0); + PCMlength = new RegBitSet(bs,8, 8, 11, 0); + + reg2 = new RegBitSet(bs,16, 8, 11, 0); + NoiseFreq = new RegBitSet(bs,16, 4, 11, 0); + NoiseType = new RegBitSet(bs,23, 1, 11, 0); + WaveLength = new RegBitSet(bs,16, 11, 11, 0); + + reg3 = new RegBitSet(bs,24, 8, 11, 0); + LengthCounterInit = new RegBitSet(bs,27, 5, 11, 0); + LoopEnabled = new RegBitSet(bs,30, 1, 11, 0); + IRQenable = new RegBitSet(bs,31, 1, 11, 0); + + length_counter = new SignedRegBitSet(bs,0, 32, 11, 1); + linear_counter = new SignedRegBitSet(bs,0, 32, 11, 2); + address = new SignedRegBitSet(bs,0, 32, 11, 3); + envelope = new SignedRegBitSet(bs,0, 32, 11, 4); + sweep_delay = new SignedRegBitSet(bs,0, 32, 11, 5); + env_delay = new SignedRegBitSet(bs,0, 32, 11, 6); + wave_counter = new SignedRegBitSet(bs,0, 32, 11, 7); + hold = new SignedRegBitSet(bs,0, 32, 11, 8); + phase = new SignedRegBitSet(bs,0, 32, 11, 9); + level = new SignedRegBitSet(bs,0, 32, 11, 10); + } + public RegBitSetStore bs; + // 4000, 4004, 400C, 4012: + public RegBitSet reg0, DutyCycle, EnvDecayDisable, EnvDecayRate, EnvDecayLoopEnable, FixedVolume, LengthCounterDisable, LinearCounterInit, LinearCounterDisable; + // 4001, 4005, 4013: + public RegBitSet reg1, SweepShift, SweepDecrease, SweepRate, SweepEnable, PCMlength; + // 4002, 4006, 400A, 400E: + public RegBitSet reg2, NoiseFreq, NoiseType, WaveLength; + // 4003, 4007, 400B, 400F, 4010: + public RegBitSet reg3, LengthCounterInit, LoopEnabled, IRQenable; + // Internals: + public SignedRegBitSet length_counter, linear_counter, address, envelope, sweep_delay, env_delay, wave_counter, hold, phase, level; + } + + APUchannel[] channels = new APUchannel[5] { new APUchannel(), new APUchannel(), new APUchannel(), new APUchannel(), new APUchannel() }; + + static readonly byte[] LengthCounters = new byte[32] + { 10,254,20, 2,40, 4,80, 6, 160, 8,60,10,14,12,26,14, + 12, 16,24,18,48,20,96,22, 192,24,72,26,16,28,32,30 }; + + static readonly ushort[] NoisePeriods = new ushort[16] + { 2,4,8,16,32,48,64,80,101,127,190,254,381,508,1017,2034 }; + + static readonly ushort[] DMCperiods = new ushort[16] + { 428,380,340,320, 286,254,226,214, 190,160,142,128, 106,84,72,54 }; + /* + For PAL: + static const u16 DMCperiods[16] = + { 0x18E,0x162,0x13C,0x12A, 0x114,0x0EC,0x0D2,0x0C6, + 0x0B0,0x094,0x084,0x076, 0x062,0x04E,0x042,0x032 }; + */ + + const int frame_period = 7458; + + // Utility function for sound + bool count(ref int v, int reset) + { + if (--v < 0) + { + v = reset; + return true; + } + else return false; + } + + bool count(ref int v, int reset, int n_at_time) + { + if ((v -= n_at_time) < 0) + { + v += reset; + return true; + } + else return false; + } + + + int tick(int c) + { + APUchannel ch = channels[c]; + int wl = ch.WaveLength; + if (c != 4) ++wl; + if (c < 2) wl *= 2; + + if (c == 3) wl = NoisePeriods[ch.NoiseFreq]; + + //if(c != 4) wl = wl * (IO::UISpeed); + // ^ Match to the UI speed (but don't for DPCM, because it would skew the timings) + + int volume = (bool)ch.length_counter ? (bool)ch.EnvDecayDisable ? (int)ch.FixedVolume : ch.envelope : 0; + // Sample may change at wavelen intervals. + int ref_S = ch.level; + int ref_ch_wave_counter = ch.wave_counter; + if (!(data.ChannelsEnabled[c] != 0) + || !count(ref ref_ch_wave_counter, wl)) + { + ch.wave_counter.Value = ref_ch_wave_counter; + return ref_S; + } + ch.wave_counter.Value = ref_ch_wave_counter; + switch (c) + { + case 0: + default: + case 1: // Square wave. With four different 8-step binary waveforms (32 bits of data total). + ch.phase.Value++; + if (wl < 8) return ref_S; + if ((bool)ch.SweepEnable && !(bool)ch.SweepDecrease) + if (wl + (wl >> ch.SweepShift) >= 0x800) + return ref_S; + return ref_S = ch.level.Value = (0xF33C0C04u & (1u << (ch.phase % 8 + ch.DutyCycle * 8))) != 0 ? volume : 0; + case 2: // Triangle wave + if ((bool)ch.length_counter && (bool)ch.linear_counter && wl >= 3) ++ch.phase.Value; + return ref_S = ch.level.Value = (ch.phase & 15) ^ (((ch.phase & 16) != 0) ? 15 : 0); + + case 3: // Noise: Linear feedback shift register + if (!(bool)ch.hold) ch.hold.Value = 1; + ch.hold.Value = (ch.hold >> 1) + | (((ch.hold ^ (ch.hold >> ((bool)ch.NoiseType ? 6 : 1))) & 1) << 14); + return ref_S = ch.level.Value = ((ch.hold & 1) != 0) ? 0 : volume; + + case 4: // Delta modulation channel (DMC) + // hold = 8 bit value + // phase = number of bits buffered + // length_counter = + if (wl == 0) return ref_S; + if (ch.phase == 0) // Nothing in sample buffer? + { + if (!(bool)ch.length_counter && (bool)ch.LoopEnabled) // Loop? + { + ch.length_counter.Value = ch.PCMlength * 16 + 1; + ch.address.Value = (int)((ch.reg0 | 0x300) << 6); + } + if (ch.length_counter > 0) // Load next 8 bits if available + { + //==========================TODO============== DMC COST===================== + //for(unsigned t = data.DMC_CycleCost; t > 1; --t) + // CPU::RB(u16(ch.address) | 0x8000); // timing + ch.hold.Value = nes.ReadMemory((ushort)((ch.address.Value++) | 0x8000)); // Fetch byte + ch.phase.Value = 8; + --ch.length_counter.Value; + } + else // Otherwise, disable channel or issue IRQ + { + if ((bool)ch.IRQenable) + { + //CPU::reg.APU_DMC_IRQ = true; + dmc_irq = true; + SyncIRQ(); + } + + data.ChannelsEnabled[4] = 0; + } + } + if (ch.phase != 0) // Update the signal if sample buffer nonempty + { + int v = ch.linear_counter; + if (((ch.hold << --ch.phase.Value) & 0x80)!=0) v += 2; else v -= 2; + if (v >= 0 && v <= 0x7F) ch.linear_counter.Value = v; + } + return ref_S = ch.level = ch.linear_counter; + } + } + + bool dmc_irq; + bool sequencer_irq; + public bool irq_pending; + void SyncIRQ() + { + irq_pending = sequencer_irq | dmc_irq; + } + + NES nes; + public BisqAPU(NES nes) + { + this.nes = nes; + } + + public void RunOne() + { + if (data.IRQ_delay != 0x7FFF) + { + if (data.IRQ_delay > 0) --data.IRQ_delay.Value; + else { sequencer_irq = true; SyncIRQ(); data.IRQ_delay.Value = 0x7FFF; } + } + if (data.frame_delay > 0) + --data.frame_delay.Value; + else + { + bool Do240 = true, Do120 = false; + data.frame_delay.Value += frame_period; + switch (data.frame.Value++) + { + case 0: + if (!(bool)data.IRQdisable && !(bool)data.FiveCycleDivider) + data.IRQ_delay.Value = frame_period * 4 + 2; + // passthru + goto case 2; + case 2: + Do120 = true; + break; + case 1: + data.frame_delay.Value -= 2; + break; + case 3: + data.frame.Value = 0; + if ((bool)data.FiveCycleDivider) + data.frame_delay.Value += frame_period - 6; + break; + } + // Some events are invoked at 96 Hz or 120 Hz rate. Others, 192 Hz or 240 Hz. + for (int c = 0; c < 4; ++c) + { + APUchannel ch = channels[c]; + int wl = ch.WaveLength; + + // 96/120 Hz events: + if (Do120) + { + // Length tick (all channels except DMC, but different disable bit for triangle wave) + if ((bool)ch.length_counter + && !(c == 2 ? (bool)ch.LinearCounterDisable : (bool)ch.LengthCounterDisable)) + ch.length_counter.Value -= 1; + + // Sweep tick (square waves only) + int ref_ch_sweep_delay = ch.sweep_delay; + if (c < 2 && count(ref ref_ch_sweep_delay, ch.SweepRate)) + if (wl >= 8 && (bool)ch.SweepEnable && (bool)ch.SweepShift) + { + int s = wl >> ch.SweepShift; + wl += ((bool)ch.SweepDecrease ? ((c != 0) ? -s : ~s) : s); + if (wl < 0x800) ch.WaveLength.Value = (uint)wl; + } + + ch.sweep_delay.Value = ref_ch_sweep_delay; + } + + // 240/192 Hz events: + if (Do240) + { + // Linear tick (triangle wave only) (all ticks) + if (c == 2) + ch.linear_counter.Value = (bool)ch.LinearCounterDisable + ? ch.LinearCounterInit + : (ch.linear_counter > 0 ? ch.linear_counter - 1 : 0); + + // Envelope tick (square and noise channels) (all ticks) + int ref_ch_env_delay = ch.env_delay; + if (c != 2 && count(ref ref_ch_env_delay, ch.EnvDecayRate)) + if (ch.envelope > 0 || (bool)ch.EnvDecayLoopEnable) + ch.envelope.Value = (ch.envelope - 1) & 15; + ch.env_delay.Value = ref_ch_env_delay; + } + + } + } + + // Mix the audio: Get the momentary sample from each channel and mix them. + // #define s(c) tick() + //v = [](float m,float n, float d) { return n!=0.f ? m/n : d; }; + Func v = (float m,float n, float d) => ( n!=0.0f ? m/n : d ); + + short sample = (short)(30000 * + ( + // Square 0 and 1 + v(95.88f, (100.0f + v(8128.0f, tick(0) + tick(1), -100.0f)), 0.0f) + // Triangle, noise, DMC + + v(159.79f, (100.0f + v(1.0f, tick(2)/8227.0f + tick(3)/12241.0f + tick(4)/22638.0f, -1000.0f)), 0.0f) + // GamePak audio (these volume values are bogus, but sound acceptable) + + v(95.88f, (100.0f + v(32512.0f, /*GamePak::ExtAudio()*/ 0, -100.0f)), 0.0f) + - 0.5f + )); + + EmitSample(sample); + + ////this (and the similar line below) is a crude hack + ////we should be generating logic to suppress the $4015 clear when the assert signal is set instead + ////be sure to test "apu_test" if you mess with this + //sequencer_irq |= sequencer_irq_assert; + } + + void WriteC(int chno, int index, byte value) + { + APUchannel ch = channels[chno]; + switch (index) + { + case 0: + if ((bool)ch.LinearCounterDisable) ch.linear_counter.Value = value & 0x7F; + ch.reg0.Value = value; + break; + case 1: + ch.reg1.Value = value; + ch.sweep_delay.Value = ch.SweepRate; + break; + case 2: + ch.reg2.Value = value; + break; + case 3: + ch.reg3.Value = value; + if (data.ChannelsEnabled[chno]!=0) + ch.length_counter.Value = LengthCounters[ch.LengthCounterInit]; + ch.linear_counter.Value = ch.LinearCounterInit; + ch.env_delay.Value = ch.EnvDecayRate; + ch.envelope.Value = 15; + if (index < 8) ch.phase.Value = 0; + break; + case 0x12: + ch.reg0.Value = value; + ch.address.Value = ((int)ch.reg0 | 0x300) << 6; + break; + case 0x10: + ch.reg3.Value = value; + ch.WaveLength.Value = (uint)(DMCperiods[value & 0x0F] - 1); + if (!(bool)ch.IRQenable) { dmc_irq = false; SyncIRQ(); } + break; + case 0x13: // sample length + ch.reg1.Value = value; + if (ch.length_counter == 0) + ch.length_counter.Value = ch.PCMlength * 16 + 1; + break; + case 0x11: // dac value + ch.linear_counter.Value = value & 0x7F; + break; + case 0x15: + for (int c = 0; c < 5; ++c) + data.ChannelsEnabled[c] = (uint)((value >> c) & 1); //noteworthy tweak + for (int c = 0; c < 5; ++c) + if (data.ChannelsEnabled[c]==0) + channels[c].length_counter.Value = 0; + else if (c == 4 && channels[c].length_counter == 0) + { + APUchannel chh = channels[c]; + chh.length_counter.Value = chh.PCMlength * 16 + 1; + chh.address.Value = ((int)chh.reg0 | 0x300) << 6; + chh.phase.Value = 0; + } + //CPU::reg.APU_DMC_IRQ = false; + dmc_irq = false; SyncIRQ(); + break; + case 0x17: + data.IRQdisable.Value = (uint)(value & 0x40); + data.FiveCycleDivider.Value = (uint)(value & 0x80); + // apu_test 1-len_ctr: Writing $80 to $4017 should clock length immediately + // But Writing $00 to $4017 shouldn't clock length immediately + data.frame_delay.Value &= 1; + data.frame.Value = 0; + data.IRQ_delay.Value = 0x7FFF; + if ((bool)data.IRQdisable) { sequencer_irq = false; SyncIRQ(); } + if (!(bool)data.FiveCycleDivider) + { + data.frame.Value = 1; + data.frame_delay.Value += frame_period; + + if (!(bool)data.IRQdisable) + { + data.IRQ_delay.Value = data.frame_delay + frame_period * 3 + 1 - 3; + // ^ "- 3" makes apu_test "4-jitter" not complain + // that "Frame irq is set too late" + } + } + break; + } + } + + public byte ReadReg(int addr) + { + byte res = 0; + for (int c = 0; c < 5; ++c) res |= (byte)(((bool)channels[c].length_counter ? 1 << c : 0)); + if (sequencer_irq) res |= 0x40; sequencer_irq = false; SyncIRQ(); + if (dmc_irq) res |= 0x80; + return res; + } + + public void WriteReg(int addr, byte val) + { + int index = addr - 0x4000; + WriteC((index / 4) % 5, index < 0x10 ? index % 4 : index, val); + } + + public void NESSoftReset() + { + } + + public void DiscardSamples() + { + metaspu.buffer.clear(); + } + + public void SyncState(Serializer ser) + { + } + + double accumulate; + double timer; + Queue squeue = new Queue(); + int last_hwsamp; + int panic_sample, panic_count; + void EmitSample(int samp) + { + //kill the annoying hum that is a consequence of the shitty code below + if (samp == panic_sample) + panic_count++; + else panic_count = 0; + if (panic_count > 178977) + samp = 0; + else + panic_sample = samp; + + int this_samp = samp; + const double kMixRate = 44100.0 / 1789772.0; + const double kInvMixRate = (1 / kMixRate); + timer += kMixRate; + accumulate += samp; + if (timer <= 1) + return; + + accumulate -= samp; + timer -= 1; + double ratio = (timer / kMixRate); + double fractional = (this_samp - last_hwsamp) * ratio; + double factional_remainder = (this_samp - last_hwsamp) * (1 - ratio); + accumulate += fractional; + + accumulate *= 436; //32768/(15*4) -- adjust later for other sound channels + int outsamp = (int)(accumulate / kInvMixRate); + if (CFG_USE_METASPU) + metaspu.buffer.enqueue_sample((short)outsamp, (short)outsamp); + else squeue.Enqueue(outsamp); + accumulate = factional_remainder; + + last_hwsamp = this_samp; + } + + MetaspuSoundProvider metaspu = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); + public int MaxVolume { get; set; } // not supported + + void ISoundProvider.GetSamples(short[] samples) + { + if (CFG_USE_METASPU) + { + metaspu.GetSamples(samples); + //foreach(short sample in samples) bw.Write((short)sample); + } + else + MyGetSamples(samples); + + //mix in the cart's extra sound circuit + nes.board.ApplyCustomAudio(samples); + } + + //static BinaryWriter bw = new BinaryWriter(new FileStream("d:\\out.raw",FileMode.Create,FileAccess.Write,FileShare.Read)); + void MyGetSamples(short[] samples) + { + //Console.WriteLine("a: {0} with todo: {1}",squeue.Count,samples.Length/2); + + for (int i = 0; i < samples.Length / 2; i++) + { + int samp = 0; + if (squeue.Count != 0) + samp = squeue.Dequeue(); + + samples[i * 2 + 0] = (short)(samp); + samples[i * 2 + 1] = (short)(samp); + //bw.Write((short)samp); + } + } + + } //class BisqAPU + + + } + +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/SxROM.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/SxROM.cs index 9256da9adb..19c4fea07f 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/SxROM.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/SxROM.cs @@ -163,7 +163,7 @@ namespace BizHawk.Emulation.Consoles.Nintendo break; } //board.NES.LogLine("mapping.. chr_mode={0}, chr={1},{2}", chr_mode, chr_0, chr_1); - board.NES.LogLine("mapping.. prg_mode={0}, prg_slot{1}, prg={2}", prg_mode, prg_slot, prg); + //board.NES.LogLine("mapping.. prg_mode={0}, prg_slot{1}, prg={2}", prg_mode, prg_slot, prg); } public int Get_PRGBank(int addr)