// Register description for APU http://wiki.nesdev.com/w/index.php/APU public void write(ushort addr, byte val) { // Write to a pulse settings register if (addr >= 0x4000 && addr <= 0x4007) { Pulse pulse; if (addr < 0x4004) { pulse = PULSE_ONE; } else { pulse = PULSE_TWO; } int normalizedAddress = addr & 0x3; switch (normalizedAddress) { case 0: // 0x4000 or 0x4004 pulse.DUTY = (byte)((val >> 6) & 0x3); pulse.LENGTH_COUNTER_HALT = ((val >> 5) & 0x1) == 1; pulse.CONSTANT_VOLUME = ((val >> 4) & 0x1) == 1; pulse.ENVELOPE_DIVIDER_PERIOD_OR_VOLUME = (byte)(val & 0xF); pulse.envelope_volume = 15; pulse.envelope_counter = pulse.ENVELOPE_DIVIDER_PERIOD_OR_VOLUME; break; case 1: // 0x4001 or 0x4005 pulse.SWEEP_ENABLED = ((val >> 7) & 0x1) == 1; pulse.SWEEP_PERIOD = (byte)((val >> 4) & 0x7); pulse.sweep_period_counter = pulse.SWEEP_PERIOD; pulse.SWEEP_NEGATE = ((val >> 3) & 0x1) == 1; pulse.SWEEP_SHIFT = (byte)((val >> 4) & 0x7); break; case 2: // 0x4002 or 0x4006 pulse.TIMER = (ushort)((pulse.TIMER & 0xFF00) | val); break; case 3: // 0x4003 or 0x4007 pulse.LENGTH_COUNTER_LOAD = (byte)((val >> 3) & 0x1F); pulse.current_length_counter = lengthCounterLookupTable[pulse.LENGTH_COUNTER_LOAD]; pulse.TIMER = (ushort)((pulse.TIMER & 0x00FF) | ((val & 0x7) << 8)); break; default: break; } } else { // All other register writes switch (addr) { case 0x4008: TRIANGLE.LENGTH_COUNTER_HALT = (((val >> 7) & 0x1) == 1); TRIANGLE.LINEAR_COUNTER_LOAD = (byte)(val & 0x7F); TRIANGLE.current_linear_counter = TRIANGLE.LINEAR_COUNTER_LOAD; break; case 0x4009: // Unused register break; case 0x400A: TRIANGLE.TIMER = (ushort)((TRIANGLE.TIMER & 0xFF00) | val); break; case 0x400B: TRIANGLE.LENGTH_COUNTER_LOAD = (byte)((val >> 3) & 0x1F); TRIANGLE.current_length_counter = lengthCounterLookupTable[TRIANGLE.LENGTH_COUNTER_LOAD]; TRIANGLE.current_linear_counter = TRIANGLE.LINEAR_COUNTER_LOAD; TRIANGLE.TIMER = (ushort)((TRIANGLE.TIMER & 0x00FF) | ((val & 0x7) << 8)); break; case 0x400C: NOISE.ENVELOPE_LOOP = (((val >> 5) & 0x1) == 1); NOISE.CONSTANT_VOLUME = (((val >> 4) & 0x1) == 1); NOISE.VOLUME_ENVELOP = (byte)(val & 0xF); break; case 0x400D: // Unused register break; case 0x400E: NOISE.LOOP_NOISE = (((val >> 7) & 0x1) == 1); NOISE.NOISE_PERIOD = (byte)(val & 0xF); break; case 0x400F: NOISE.LENGTH_COUNTER_LOAD = (byte)((val >> 3) & 0xF); break; case 0x4010: DMC.IRQ_ENABLE = (((val >> 7) & 0x1) == 1); DMC.LOOP = (((val >> 6) & 0x1) == 1); DMC.FREQUENCY = (byte)(val & 0xF); break; case 0x4011: DMC.LOAD_COUNTER = (byte)(val & 0x7F); break; case 0x4012: DMC.SAMPLE_ADDRESS = val; break; case 0x4013: DMC.SAMPLE_LENGTH = val; break; case 0x4015: DMC.ENABLED = (((val >> 4) & 0x1) == 1); NOISE.ENABLED = (((val >> 3) & 0x1) == 1); TRIANGLE.ENABLED = (((val >> 2) & 0x1) == 1); PULSE_TWO.ENABLED = (((val >> 1) & 0x1) == 1); PULSE_ONE.ENABLED = (((val >> 0) & 0x1) == 1); break; case 0x4017: FRAME_COUNTER_MODE = (((val >> 7) & 0x1) == 1) ? FrameCounterMode.FIVE_STEP : FrameCounterMode.FOUR_STEP; IRQ_INHIBIT = (((val >> 6) & 0x1) == 1); break; default: log.error("Attempting to write to unknown address {0:X4}", addr); break; } } }