////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// Nestopia is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
//
////////////////////////////////////////////////////////////////////////////////////////

#include <cstring>
#include "NstCpu.hpp"
#include "NstState.hpp"
#include "api/NstApiSound.hpp"
#include "NstSoundRenderer.inl"

namespace Nes
{
	namespace Core
	{
		const dword Apu::Cycles::frameClocks[3][4] =
		{
			{
				CPU_RP2A03_CC * 29830UL,
				CPU_RP2A03_CC,
				CPU_RP2A03_CC,
				CPU_RP2A03_CC * (29830UL - 2),
			},
			{
				CPU_RP2A07_CC * 33254UL,
				CPU_RP2A07_CC,
				CPU_RP2A07_CC,
				CPU_RP2A07_CC * (33254UL - 2)
			},
			{
				CPU_DENDY_CC * 29830UL,
				CPU_DENDY_CC,
				CPU_DENDY_CC,
				CPU_DENDY_CC * (29830UL - 2),
			}
		};

		const dword Apu::Cycles::oscillatorClocks[3][2][4] =
		{
			{
				{
					CPU_RP2A03_CC * (7459UL - 1),
					CPU_RP2A03_CC * 7456UL,
					CPU_RP2A03_CC * 7458UL,
					CPU_RP2A03_CC * 7458UL
				},
				{
					CPU_RP2A03_CC * 7458UL,
					CPU_RP2A03_CC * 7456UL,
					CPU_RP2A03_CC * 7458UL,
					CPU_RP2A03_CC * (7458UL + 7452)
				}
			},
			{
				{
					CPU_RP2A07_CC * (8315UL - 1),
					CPU_RP2A07_CC * 8314UL,
					CPU_RP2A07_CC * 8312UL,
					CPU_RP2A07_CC * 8314UL
				},
				{
					CPU_RP2A07_CC * 8314UL,
					CPU_RP2A07_CC * 8314UL,
					CPU_RP2A07_CC * 8312UL,
					CPU_RP2A07_CC * (8314UL + 8312)
				}
			},
			{
				{
					CPU_DENDY_CC * (7459UL - 1),
					CPU_DENDY_CC * 7456UL,
					CPU_DENDY_CC * 7458UL,
					CPU_DENDY_CC * 7458UL
				},
				{
					CPU_DENDY_CC * 7458UL,
					CPU_DENDY_CC * 7456UL,
					CPU_DENDY_CC * 7458UL,
					CPU_DENDY_CC * (7458UL + 7452)
				}
			}
		};

		const byte Apu::Channel::LengthCounter::lut[32] =
		{
			0x0A, 0xFE, 0x14, 0x02,
			0x28, 0x04, 0x50, 0x06,
			0xA0, 0x08, 0x3C, 0x0A,
			0x0E, 0x0C, 0x1A, 0x0E,
			0x0C, 0x10, 0x18, 0x12,
			0x30, 0x14, 0x60, 0x16,
			0xC0, 0x18, 0x48, 0x1A,
			0x10, 0x1C, 0x20, 0x1E
		};

		const word Apu::Noise::lut[3][16] =
		{
			{
				0x004, 0x008, 0x010, 0x020,
				0x040, 0x060, 0x080, 0x0A0,
				0x0CA, 0x0FE, 0x17C, 0x1FC,
				0x2FA, 0x3F8, 0x7F2, 0xFE4
			},
			{
				0x004, 0x007, 0x00E, 0x01E,
				0x03C, 0x058, 0x076, 0x094,
				0x0BC, 0x0EC, 0x162, 0x1D8,
				0x2C4, 0x3B0, 0x762, 0xEC2
			},
			{
				0x004, 0x008, 0x010, 0x020,
				0x040, 0x060, 0x080, 0x0A0,
				0x0CA, 0x0FE, 0x17C, 0x1FC,
				0x2FA, 0x3F8, 0x7F2, 0xFE4
			}
		};

		const word Apu::Dmc::lut[3][16] =
		{
			{
				0x1AC * CPU_RP2A03_CC,
				0x17C * CPU_RP2A03_CC,
				0x154 * CPU_RP2A03_CC,
				0x140 * CPU_RP2A03_CC,
				0x11E * CPU_RP2A03_CC,
				0x0FE * CPU_RP2A03_CC,
				0x0E2 * CPU_RP2A03_CC,
				0x0D6 * CPU_RP2A03_CC,
				0x0BE * CPU_RP2A03_CC,
				0x0A0 * CPU_RP2A03_CC,
				0x08E * CPU_RP2A03_CC,
				0x080 * CPU_RP2A03_CC,
				0x06A * CPU_RP2A03_CC,
				0x054 * CPU_RP2A03_CC,
				0x048 * CPU_RP2A03_CC,
				0x036 * CPU_RP2A03_CC
			},
			{
				0x18E * CPU_RP2A07_CC,
				0x162 * CPU_RP2A07_CC,
				0x13C * CPU_RP2A07_CC,
				0x12A * CPU_RP2A07_CC,
				0x114 * CPU_RP2A07_CC,
				0x0EC * CPU_RP2A07_CC,
				0x0D2 * CPU_RP2A07_CC,
				0x0C6 * CPU_RP2A07_CC,
				0x0B0 * CPU_RP2A07_CC,
				0x094 * CPU_RP2A07_CC,
				0x084 * CPU_RP2A07_CC,
				0x076 * CPU_RP2A07_CC,
				0x062 * CPU_RP2A07_CC,
				0x04E * CPU_RP2A07_CC,
				0x042 * CPU_RP2A07_CC,
				0x032 * CPU_RP2A07_CC
			},
			{
				0x1AC * CPU_DENDY_CC,
				0x17C * CPU_DENDY_CC,
				0x154 * CPU_DENDY_CC,
				0x140 * CPU_DENDY_CC,
				0x11E * CPU_DENDY_CC,
				0x0FE * CPU_DENDY_CC,
				0x0E2 * CPU_DENDY_CC,
				0x0D6 * CPU_DENDY_CC,
				0x0BE * CPU_DENDY_CC,
				0x0A0 * CPU_DENDY_CC,
				0x08E * CPU_DENDY_CC,
				0x080 * CPU_DENDY_CC,
				0x06A * CPU_DENDY_CC,
				0x054 * CPU_DENDY_CC,
				0x048 * CPU_DENDY_CC,
				0x036 * CPU_DENDY_CC
			}
		};

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		Apu::Apu(Cpu& c)
		:
		cpu        (c),
		extChannel (NULL),
		buffer     (16)
		{
			NST_COMPILE_ASSERT( CPU_RP2A03 == 0 && CPU_RP2A07 == 1 && CPU_DENDY == 2 );

			PowerOff();
		}

		void Apu::PowerOff()
		{
			Reset( false, true );
		}

		void Apu::Reset(bool hard)
		{
			Reset( true, hard );
		}

		void Apu::Reset(const bool on,const bool hard)
		{
			if (on)
				UpdateSettings();

			updater = &Apu::SyncOff;

			cycles.Reset( extChannel, cpu.GetModel() );
			synchronizer.Resync( settings.speed, cpu );

			for (uint i=0; i < 2; ++i)
				square[i].Reset();

			triangle.Reset();
			noise.Reset( cpu.GetModel() );
			dmc.Reset( cpu.GetModel() );

			dcBlocker.Reset();

			stream = NULL;

			buffer.Reset();

			if (on)
			{
				cpu.Map( 0x4000 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4000 );
				cpu.Map( 0x4001 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4001 );
				cpu.Map( 0x4002 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4002 );
				cpu.Map( 0x4003 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4003 );
				cpu.Map( 0x4004 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4000 );
				cpu.Map( 0x4005 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4001 );
				cpu.Map( 0x4006 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4002 );
				cpu.Map( 0x4007 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4003 );
				cpu.Map( 0x4008 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4008 );
				cpu.Map( 0x400A ).Set( this, &Apu::Peek_40xx, &Apu::Poke_400A );
				cpu.Map( 0x400B ).Set( this, &Apu::Peek_40xx, &Apu::Poke_400B );
				cpu.Map( 0x400C ).Set( this, &Apu::Peek_40xx, &Apu::Poke_400C );
				cpu.Map( 0x400E ).Set( this, &Apu::Peek_40xx, &Apu::Poke_400E );
				cpu.Map( 0x400F ).Set( this, &Apu::Peek_40xx, &Apu::Poke_400F );
				cpu.Map( 0x4010 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4010 );
				cpu.Map( 0x4011 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4011 );
				cpu.Map( 0x4012 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4012 );
				cpu.Map( 0x4013 ).Set( this, &Apu::Peek_40xx, &Apu::Poke_4013 );
				cpu.Map( 0x4015 ).Set( this, &Apu::Peek_4015, &Apu::Poke_4015 );

				if (cpu.GetApu().IsGenie())
				{
					NES_DO_POKE(4000,0x4000,0x30);
					NES_DO_POKE(4001,0x4001,0xF9);
					NES_DO_POKE(400C,0x400C,0x30);
					NES_DO_POKE(400E,0x400E,0x0E);
					NES_DO_POKE(400F,0x400F,0x04);
					NES_DO_POKE(4015,0x4015,0x09);
				}

				if (hard)
				{
					ctrl = STATUS_FRAME_IRQ_ENABLE;
				}

				if (ctrl == STATUS_FRAME_IRQ_ENABLE)
					cycles.frameIrqClock = (cycles.frameCounter / cycles.fixed) - cpu.GetClock();

				if (extChannel)
					extChannel->Reset();
			}
			else
			{
				ctrl = STATUS_FRAME_IRQ_ENABLE;
			}
		}

		Result Apu::SetSampleRate(const dword rate)
		{
			if (settings.rate == rate)
				return RESULT_NOP;

			if (!rate)
				return RESULT_ERR_INVALID_PARAM;

			/*if (rate < 44100 || rate > 96000)
				return RESULT_ERR_UNSUPPORTED;*/

			settings.rate = rate;
			UpdateSettings();

			return RESULT_OK;
		}

		Result Apu::SetVolume(const uint channels,const uint volume)
		{
			if (volume > 100)
				return RESULT_ERR_INVALID_PARAM;

			bool updated = false;

			for (uint i=0; i < MAX_CHANNELS; ++i)
			{
				if (channels & (1U << i))
				{
					if (settings.volumes[i] != volume)
					{
						settings.volumes[i] = volume;
						updated = true;
					}
				}
			}

			if (!updated)
				return RESULT_NOP;

			UpdateSettings();

			return RESULT_OK;
		}

		uint Apu::GetVolume(const uint channel) const
		{
			for (uint i=0; i < MAX_CHANNELS; ++i)
			{
				if (channel & (1U << i))
					return settings.volumes[i];
			}

			return 0;
		}

		uint Apu::GetCtrl()
		{
			return ctrl;
		}

		Result Apu::SetSpeed(const uint speed)
		{
			if (settings.speed == speed)
				return RESULT_NOP;

			if ((speed > 0 && speed < 30) || speed > 240)
				return RESULT_ERR_UNSUPPORTED;

			settings.speed = speed;
			UpdateSettings();

			return RESULT_OK;
		}

		void Apu::Mute(const bool mute)
		{
			if (settings.muted != mute)
			{
				settings.muted = mute;
				UpdateSettings();
			}
		}

		void Apu::SetAutoTranspose(const bool transpose)
		{
			if (settings.transpose != transpose)
			{
				settings.transpose = transpose;
				UpdateSettings();
			}
		}

		void Apu::SetGenie(const bool genie)
		{
			if (settings.genie != genie)
			{
				settings.genie = genie;
				UpdateSettings();
			}
		}

		void Apu::EnableStereo(const bool enable)
		{
			if (settings.stereo != enable)
			{
				settings.stereo = enable;
				UpdateSettings();
			}
		}

		void Apu::UpdateSettings()
		{
			cycles.Update( settings.rate, settings.speed, cpu );
			synchronizer.Reset( settings.speed, settings.rate, cpu );
			dcBlocker.Reset();
			buffer.Reset();

			Cycle rate; uint fixed;
			CalculateOscillatorClock( rate, fixed );

			square[0].UpdateSettings ( settings.muted ? 0 : settings.volumes[ Channel::APU_SQUARE1  ], rate, fixed );
			square[1].UpdateSettings ( settings.muted ? 0 : settings.volumes[ Channel::APU_SQUARE2  ], rate, fixed );
			triangle.UpdateSettings  ( settings.muted ? 0 : settings.volumes[ Channel::APU_TRIANGLE ], rate, fixed );
			noise.UpdateSettings     ( settings.muted ? 0 : settings.volumes[ Channel::APU_NOISE    ], rate, fixed );
			dmc.UpdateSettings       ( settings.muted ? 0 : settings.volumes[ Channel::APU_DPCM     ] );

			UpdateVolumes();
		}

		void Apu::UpdateVolumes()
		{
			settings.audible = (extChannel && extChannel->UpdateSettings()) ||
			(
				uint(settings.volumes[ Channel::APU_SQUARE1  ]) |
				uint(settings.volumes[ Channel::APU_SQUARE2  ]) |
				uint(settings.volumes[ Channel::APU_TRIANGLE ]) |
				uint(settings.volumes[ Channel::APU_NOISE    ]) |
				uint(settings.volumes[ Channel::APU_DPCM     ])
			);
		}

		void Apu::Resync(const dword rate)
		{
			cycles.Update( rate, settings.speed, cpu );
			ClearBuffers( false );
		}

		void Apu::CalculateOscillatorClock(Cycle& rate,uint& fixed) const
		{
			dword sampleRate = settings.rate;

			if (settings.transpose && settings.speed)
				sampleRate = sampleRate * cpu.GetFps() / settings.speed;

			uint multiplier = 0;
			const qaword clockBase = cpu.GetClockBase();

			while (++multiplier < 0x1000 && clockBase * (multiplier+1) / sampleRate <= 0x7FFFF && clockBase * multiplier % sampleRate);

			rate = clockBase * multiplier / sampleRate;
			fixed = cpu.GetClockDivider() * cpu.GetClock() * multiplier;
		}

		void Apu::SaveState(State::Saver& state,const dword baseChunk) const
		{
			state.Begin( baseChunk );

			{
				Cycle clock = cycles.frameCounter / cycles.fixed;

				NST_VERIFY( clock >= cpu.GetCycles() );

				if (clock > cpu.GetCycles())
					clock = (clock - cpu.GetCycles()) / cpu.GetClock();
				else
					clock = 0;

				NST_VERIFY( cycles.frameCounter == (cpu.GetCycles() + clock * cpu.GetClock()) * cycles.fixed );

				const byte data[4] =
				{
					static_cast<byte>(ctrl),
					static_cast<byte>(clock & 0xFF),
					static_cast<byte>(clock >> 8),
					static_cast<byte>(cycles.frameDivider)
				};

				state.Begin( AsciiId<'F','R','M'>::V ).Write( data ).End();
			}

			if (cycles.frameIrqClock != Cpu::CYCLE_MAX)
			{
				Cycle clock = cycles.frameIrqClock;

				NST_VERIFY( clock >= cpu.GetCycles() );

				if (clock > cpu.GetCycles())
					clock = (clock - cpu.GetCycles()) / cpu.GetClock();
				else
					clock = 0;

				NST_VERIFY( cycles.frameIrqClock == cpu.GetCycles() + clock * cpu.GetClock() );

				const byte data[3] =
				{
					static_cast<byte>(clock & 0xFF),
					static_cast<byte>(clock >> 8),
					static_cast<byte>(cycles.frameIrqRepeat % 3)
				};

				state.Begin( AsciiId<'I','R','Q'>::V ).Write( data ).End();
			}

			if (cycles.extCounter != Cpu::CYCLE_MAX)
			{
				Cycle clock = cycles.extCounter / cycles.fixed;

				NST_VERIFY( clock >= cpu.GetCycles() || clock == 0 );

				if (clock > cpu.GetCycles())
				{
					clock = (clock - cpu.GetCycles()) / cpu.GetClock();
					NST_VERIFY( cycles.extCounter == (cpu.GetCycles() + clock * cpu.GetClock()) * cycles.fixed );
				}
				else
				{
					clock = 0;
				}

				state.Begin( AsciiId<'E','X','T'>::V ).Write16( clock ).End();
			}

			square[0].SaveState( state, AsciiId<'S','Q','0'>::V );
			square[1].SaveState( state, AsciiId<'S','Q','1'>::V );
			triangle.SaveState( state, AsciiId<'T','R','I'>::V );
			noise.SaveState( state, AsciiId<'N','O','I'>::V );
			dmc.SaveState( state, AsciiId<'D','M','C'>::V, cpu, cycles.dmcClock );

			dcBlocker.SaveState( state, AsciiId<'D','C','B'>::V );

			{
				const byte data[4] =
				{
					static_cast<byte>(cycles.rateCounter & 0xFFU),
					static_cast<byte>(cycles.rateCounter >> 8),
					static_cast<byte>(cycles.rateCounter >> 16),
					static_cast<byte>(cycles.rateCounter >> 24),
				};

				state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
			}

			state.End();
		}

		void Apu::LoadState(State::Loader& state)
		{
			cycles.frameIrqClock = Cpu::CYCLE_MAX;
			cycles.frameIrqRepeat = 0;

			while (const dword chunk = state.Begin())
			{
				switch (chunk)
				{
					case AsciiId<'F','R','M'>::V:
					{
						State::Loader::Data<4> data( state );

						ctrl = data[0] & STATUS_BITS;

						cycles.rateCounter = cycles.fixed * cpu.GetCycles();

						cycles.frameCounter = cycles.fixed *
						(
							cpu.GetCycles() + (data[1] | data[2] << 8) * cpu.GetClock()
						);

						cycles.frameDivider = data[3] & 0x3;
						break;
					}

					case AsciiId<'I','R','Q'>::V:
					{
						State::Loader::Data<3> data( state );

						cycles.frameIrqClock = cpu.GetCycles() +
						(
							(data[0] | data[1] << 8) * cpu.GetClock()
						);

						cycles.frameIrqRepeat = (data[2] & 0x3) % 3;
						break;
					}

					case AsciiId<'E','X','T'>::V:

						NST_VERIFY( cycles.extCounter != Cpu::CYCLE_MAX );

						if (cycles.extCounter != Cpu::CYCLE_MAX)
						{
							cycles.extCounter = cycles.fixed *
							(
								cpu.GetCycles() + state.Read16() * cpu.GetClock()
							);
						}
						break;

					case AsciiId<'S','Q','0'>::V:

						square[0].LoadState( state );
						break;

					case AsciiId<'S','Q','1'>::V:

						square[1].LoadState( state );
						break;

					case AsciiId<'T','R','I'>::V:

						triangle.LoadState( state );
						break;

					case AsciiId<'N','O','I'>::V:

						noise.LoadState( state, cpu.GetModel() );
						break;

					case AsciiId<'D','M','C'>::V:

						dmc.LoadState( state, cpu, cpu.GetModel(), cycles.dmcClock );
						break;

					case AsciiId<'D','C','B'>::V:

						dcBlocker.LoadState( state );
						break;

					case AsciiId<'S','0','0'>::V:
					{
						State::Loader::Data<4> data( state );

						cycles.rateCounter = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
						break;
					}
				}

				state.End();
			}

			if (ctrl != STATUS_FRAME_IRQ_ENABLE)
			{
				cycles.frameIrqClock = Cpu::CYCLE_MAX;
				cycles.frameIrqRepeat = 0;
			}
			else if (cycles.frameIrqClock == Cpu::CYCLE_MAX)
			{
				cycles.frameIrqClock = (cycles.frameCounter / cycles.fixed) + (3 - cycles.frameDivider) * (Cycles::frameClocks[cpu.GetModel()][0] / 4);
				cycles.frameIrqRepeat = 0;
			}
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		void NST_FASTCALL Apu::SyncOn(const Cycle target)
		{
			NST_ASSERT( (stream && settings.audible) && (cycles.rate && cycles.fixed) && (cycles.extCounter == Cpu::CYCLE_MAX) );

			if (cycles.rateCounter < target)
			{
				Cycle rateCounter = cycles.rateCounter;
				const Cycle rate = cycles.rate;

				do
				{
					buffer << GetSample();

					if (cycles.frameCounter <= rateCounter)
						ClockFrameCounter();

					rateCounter += rate;
				}
				while (rateCounter < target);

				cycles.rateCounter = rateCounter;
			}

			if (cycles.frameCounter < target)
			{
				ClockFrameCounter();
				NST_ASSERT( cycles.frameCounter >= target );
			}
		}

		void NST_FASTCALL Apu::SyncOnExt(const Cycle target)
		{
			NST_ASSERT( (stream && settings.audible) && (cycles.rate && cycles.fixed) && extChannel );

			Cycle extCounter = cycles.extCounter;

			if (cycles.rateCounter < target)
			{
				Cycle rateCounter = cycles.rateCounter;

				do
				{
					buffer << GetSample();

					if (extCounter <= rateCounter)
						extCounter = extChannel->Clock( extCounter, cycles.fixed, rateCounter );

					if (cycles.frameCounter <= rateCounter)
						ClockFrameCounter();

					rateCounter += cycles.rate;
				}
				while (rateCounter < target);

				cycles.rateCounter = rateCounter;
			}

			if (extCounter <= target)
			{
				cycles.extCounter = extChannel->Clock( extCounter, cycles.fixed, target );
				NST_ASSERT( cycles.extCounter > target );
			}
			else
			{
				cycles.extCounter = extCounter;
			}

			if (cycles.frameCounter < target)
			{
				ClockFrameCounter();
				NST_ASSERT( cycles.frameCounter >= target );
			}
		}

		void NST_FASTCALL Apu::SyncOff(const Cycle target)
		{
			NST_ASSERT( !(stream && settings.audible) && cycles.fixed );

			cycles.rateCounter = target;

			while (cycles.frameCounter < target)
				ClockFrameCounter();

			NST_ASSERT( cycles.extCounter == Cpu::CYCLE_MAX || extChannel );

			if (cycles.extCounter <= target)
			{
				cycles.extCounter = extChannel->Clock( cycles.extCounter, cycles.fixed, target );
				NST_ASSERT( cycles.extCounter > target );
			}
		}

		void Apu::BeginFrame(Sound::Output* output)
		{
			stream = output;
			updater = (output && settings.audible ? (cycles.extCounter == Cpu::CYCLE_MAX ? &Apu::SyncOn : &Apu::SyncOnExt) : &Apu::SyncOff);
		}

		inline void Apu::Update(const Cycle target)
		{
			NST_ASSERT( cycles.fixed );
			(*this.*updater)( target * cycles.fixed );
		}

		void Apu::Update()
		{
			Update( cpu.Update() );
		}

		void Apu::UpdateLatency()
		{
			Update( cpu.Update() + 1 );
		}

		bool Apu::UpdateDelta()
		{
			const Cycle elapsed = cpu.Update();
			const bool delta = cycles.frameCounter != elapsed * cycles.fixed;
			Update( elapsed + 1 );
			return delta;
		}

		template<typename T,bool STEREO>
		void Apu::FlushSound()
		{
			NST_ASSERT( (stream && settings.audible) && (cycles.rate && cycles.fixed) );

			for (uint i=0; i < 2; ++i)
			{
				if (stream->length[i] && stream->samples[i])
				{
					Sound::Buffer::Block block( stream->length[i] );
					buffer >> block;

					Sound::Buffer::Renderer<T,STEREO> output( stream->samples[i], stream->length[i], buffer.history );

					if (output << block)
					{
						const Cycle target = cpu.GetCycles() * cycles.fixed;

						if (cycles.rateCounter < target)
						{
							Cycle rateCounter = cycles.rateCounter;

							do
							{
								output << GetSample();

								if (cycles.frameCounter <= rateCounter)
									ClockFrameCounter();

								if (cycles.extCounter <= rateCounter)
									cycles.extCounter = extChannel->Clock( cycles.extCounter, cycles.fixed, rateCounter );

								rateCounter += cycles.rate;
							}
							while (rateCounter < target && output);

							cycles.rateCounter = rateCounter;
						}

						if (output)
						{
							if (cycles.frameCounter < target)
								ClockFrameCounter();

							if (cycles.extCounter <= target)
								cycles.extCounter = extChannel->Clock( cycles.extCounter, cycles.fixed, target );

							do
							{
								output << GetSample();
							}
							while (output);
						}
					}
				}
			}
		}

		void Apu::EndFrame()
		{
			NST_ASSERT( (stream && settings.audible) == (updater != &Apu::SyncOff) );

			if (updater != &Apu::SyncOff)
			{
				dword streamed = 0;

				if (Sound::Output::lockCallback( *stream ))
				{
					streamed = stream->length[0] + stream->length[1];

					if (!settings.stereo)
						FlushSound<iword,false>();
					else
						FlushSound<iword,true>();

					Sound::Output::unlockCallback( *stream );
				}

				if (const dword rate = synchronizer.Clock( streamed, settings.rate, cpu ))
					Resync( rate );
			}

			Update( cpu.GetCycles() );

			Cycle frame = cpu.GetFrameCycles();

			NST_ASSERT
			(
				cycles.dmcClock >= frame &&
				cycles.frameIrqClock >= frame
			);

			cycles.dmcClock -= frame;

			if (cycles.frameIrqClock != Cpu::CYCLE_MAX)
				cycles.frameIrqClock -= frame;

			frame *= cycles.fixed;

			NST_ASSERT
			(
				cycles.rateCounter >= frame &&
				cycles.frameCounter >= frame &&
				cycles.extCounter >= frame
			);

			cycles.rateCounter -= frame;
			cycles.frameCounter -= frame;

			if (cycles.extCounter != Cpu::CYCLE_MAX)
				cycles.extCounter -= frame;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		Apu::Settings::Settings()
		: rate(44100), speed(0), muted(false), transpose(false), stereo(false), audible(true)
		{
			for (uint i=0; i < MAX_CHANNELS; ++i)
				volumes[i] = Channel::DEFAULT_VOLUME;
		}

		Apu::Cycles::Cycles()
		: fixed(1), rate(1) {}

		void Apu::Cycles::Reset(const bool extChannel,const CpuModel model)
		{
			rateCounter = 0;
			frameDivider = 0;
			frameIrqClock = Cpu::CYCLE_MAX;
			frameIrqRepeat = 0;
			dmcClock = Dmc::GetResetFrequency( model );
			frameCounter = frameClocks[model][0] * fixed;
			extCounter = (extChannel ? 0UL : Cpu::CYCLE_MAX);
		}

		void Apu::Cycles::Update(dword sampleRate,const uint speed,const Cpu& cpu)
		{
			frameCounter /= fixed;
			rateCounter /= fixed;

			if (extCounter != Cpu::CYCLE_MAX)
				extCounter /= fixed;

			if (speed)
				sampleRate = sampleRate * cpu.GetFps() / speed;

			uint multiplier = 0;
			const qaword clockBase = cpu.GetClockBase();

			while (++multiplier < 512 && clockBase * multiplier % sampleRate);

			rate = clockBase * multiplier / sampleRate;
			fixed = cpu.GetClockDivider() * multiplier;

			frameCounter *= fixed;
			rateCounter *= fixed;

			if (extCounter != Cpu::CYCLE_MAX)
				extCounter *= fixed;
		}

		Apu::Synchronizer::Synchronizer()
		: rate(0) {}

		void Apu::Synchronizer::Resync(uint speed,const Cpu& cpu)
		{
			duty = 0;
			streamed = 0;

			if (speed == 0 || speed == cpu.GetFps())
				sync = 4;
			else
				sync = 0;
		}

		void Apu::Synchronizer::Reset(uint speed,dword sampleRate,const Cpu& cpu)
		{
			rate = sampleRate;
			Resync( speed, cpu );
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		NST_SINGLE_CALL dword Apu::Synchronizer::Clock(const dword output,const dword sampleRate,const Cpu& cpu)
		{
			/*if (sync)
			{
				if (duty >= 60*4)
					streamed += output;

				if (duty < 60*12)
				{
					duty++;
				}
				else
				{
					duty = 60*4;

					dword actualRate = streamed / (60*8) * cpu.GetFps();
					const dword limit = sampleRate / 21;

					if (actualRate <= sampleRate-limit)
					{
						actualRate = sampleRate-limit;
						sync--;
					}
					else if (actualRate >= sampleRate+limit)
					{
						actualRate = sampleRate+limit;
						sync--;
					}
					else
					{
						sync = (sync > 2 ? sync - 2 : 0);
					}

					actualRate = actualRate * 9999 / 10000;
					streamed = 0;

					if (rate != actualRate)
					{
						rate = actualRate;
						return actualRate;
					}
				}
			}*/

			return 0;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		Apu::Channel::LengthCounter::LengthCounter()
		{
			Reset();
		}

		void Apu::Channel::LengthCounter::Reset()
		{
			enabled = 0;
			count = 0;
		}

		void Apu::Channel::LengthCounter::LoadState(State::Loader& state)
		{
			const uint data = state.Read8();
			enabled = (data == 0xFF ? 0U : ~0U);
			count = data & enabled;
		}

		void Apu::Channel::LengthCounter::SaveState(State::Saver& state,const dword chunk) const
		{
			NST_VERIFY( count < 0xFF );
			state.Begin( chunk ).Write8( enabled ? count : 0xFF ).End();
		}

		Apu::Channel::Envelope::Envelope()
		: outputVolume(OUTPUT_MUL)
		{
			Reset();
		}

		void Apu::Channel::Envelope::Reset()
		{
			output = 0;
			regs[0] = 0x0;
			regs[1] = 0x10;
			count = 0;
			reset = false;
		}

		void Apu::Channel::Envelope::SetOutputVolume(uint v)
		{
			outputVolume = v;
			UpdateOutput();
		}

		void Apu::Channel::Envelope::SaveState(State::Saver& state,const dword chunk) const
		{
			const byte data[3] =
			{
				count,
				static_cast<byte>(regs[0] | (reset ? 0x80U : 0x00U)),
				regs[1]
			};

			state.Begin( chunk ).Write( data ).End();
		}

		void Apu::Channel::Envelope::LoadState(State::Loader& state)
		{
			State::Loader::Data<3> data( state );

			count   = data[0] & 0x0F;
			reset   = data[1] >> 7;
			regs[0] = data[1] & 0x0F;
			regs[1] = data[2];

			UpdateOutput();
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		void Apu::Channel::Envelope::UpdateOutput()
		{
			output = (regs[regs[1] >> 4 & 1U] & 0xFUL) * outputVolume;
		}

		void Apu::Channel::Envelope::Clock()
		{
			if (!reset)
			{
				if (count)
				{
					count--;
					return;
				}

				if (regs[0] | (regs[1] & 0x20U))
					regs[0] = (regs[0] - 1U) & 0xF;
			}
			else
			{
				reset = false;
				regs[0] = 0xF;
			}

			count = regs[1] & 0x0FU;
			UpdateOutput();
		}

		void Apu::Channel::Envelope::Write(const uint data)
		{
			regs[1] = data;
			UpdateOutput();
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		Apu::Channel::DcBlocker::DcBlocker()
		{
			Reset();
		}

		void Apu::Channel::DcBlocker::Reset()
		{
			acc = 0;
			prev = 0;
			next = 0;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		Apu::Channel::Sample Apu::Channel::DcBlocker::Apply(Sample sample)
		{
			acc  -= prev;
			prev  = signed_shl(sample,15);
			acc  += prev - next * POLE;
			next  = signed_shr(acc,15);
			return next;
		}

		void Apu::Channel::DcBlocker::SaveState(State::Saver& state,const dword chunk) const
		{
			state.Begin( chunk );

			{
				const byte data[12] =
				{
					static_cast<byte>(acc & 0xFFU),
					static_cast<byte>(acc >> 8),
					static_cast<byte>(acc >> 16),
					static_cast<byte>(acc >> 24),
					static_cast<byte>(prev & 0xFFU),
					static_cast<byte>(prev >> 8),
					static_cast<byte>(prev >> 16),
					static_cast<byte>(prev >> 24),
					static_cast<byte>(next & 0xFFU),
					static_cast<byte>(next >> 8),
					static_cast<byte>(next >> 16),
					static_cast<byte>(next >> 24),
				};

				state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
			}

			state.End();
		}

		void Apu::Channel::DcBlocker::LoadState(State::Loader& state)
		{
			while (const dword chunk = state.Begin())
			{
				switch (chunk)
				{
					case AsciiId<'S','0','0'>::V:
					{
						State::Loader::Data<12> data( state );

						acc = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
						prev = data[4] | (data[5] << 8) | (data[6] << 16) | (data[7] << 24);
						next = data[8] | (data[9] << 8) | (data[10] << 16) | (data[11] << 24);
						break;
					}
				}

				state.End();
			}
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		Apu::Channel::Channel(Apu& a)
		: apu(a) {}

		Apu::Channel::~Channel()
		{
			if (apu.extChannel == this)
			{
				apu.extChannel = NULL;
				apu.UpdateVolumes();
			}
		}

		void Apu::Channel::Connect(bool audible)
		{
			NST_ASSERT( apu.extChannel == NULL );

			if (audible)
				apu.settings.audible = true;
			else
				apu.UpdateVolumes();

			apu.extChannel = this;
		}

		void Apu::Channel::GetOscillatorClock(Cycle& rate,uint& fixed) const
		{
			apu.CalculateOscillatorClock( rate, fixed );
		}

		uint Apu::Channel::GetVolume(uint channel) const
		{
			NST_ASSERT( channel < MAX_CHANNELS );
			return apu.settings.volumes[channel];
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		Cycle Apu::Channel::GetCpuClockBase() const
		{
			return apu.cpu.GetClockBase();
		}

		uint Apu::Channel::GetCpuClockDivider() const
		{
			return apu.cpu.GetClockDivider();
		}

		Cycle Apu::Channel::GetCpuClock(uint clock) const
		{
			return apu.cpu.GetClock(clock);
		}

		dword Apu::Channel::GetSampleRate() const
		{
			return apu.settings.rate;
		}

		bool Apu::Channel::IsMuted() const
		{
			return apu.settings.muted;
		}

		void Apu::Channel::Update() const
		{
			apu.Update();
		}

		Cycle Apu::Channel::Clock(Cycle,Cycle,Cycle)
		{
			return Cpu::CYCLE_MAX;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		Apu::Oscillator::Oscillator()
		: rate(1), fixed(1) {}

		void Apu::Oscillator::Reset()
		{
			active = false;
			timer = RESET_CYCLES * fixed;
			frequency = fixed;
			amp = 0;
		}

		inline void Apu::Oscillator::ClearAmp()
		{
			amp = 0;
		}

		void Apu::Oscillator::UpdateSettings(dword r,uint f)
		{
			NST_ASSERT( r && f );

			frequency = frequency / fixed * f;
			timer = timer / fixed * f;
			fixed = f;
			rate = r;
		}

		void Apu::Square::Reset()
		{
			Oscillator::Reset();

			frequency = fixed * 2;
			step = 0;
			duty = 0;

			envelope.Reset();
			lengthCounter.Reset();

			validFrequency = false;

			sweepRate = 0;
			sweepCount = 1;
			sweepReload = false;
			sweepIncrease = ~0U;
			sweepShift = 0;

			waveLength = 0;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		inline bool Apu::Square::CanOutput() const
		{
			return lengthCounter.GetCount() && envelope.Volume() && validFrequency;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		void Apu::Square::UpdateSettings(uint v,dword r,uint f)
		{
			Oscillator::UpdateSettings( r, f );
			envelope.SetOutputVolume( (v * Channel::OUTPUT_MUL + Channel::DEFAULT_VOLUME/2) / Channel::DEFAULT_VOLUME );
			active = CanOutput();
		}

		void Apu::Square::SaveState(State::Saver& state,const dword chunk) const
		{
			state.Begin( chunk );

			{
				byte data[4];

				data[0] = waveLength & 0xFFU;
				data[1] = (waveLength >> 8) | (duty ? duty << (2+3) : 2U << 3); // for version compatibility
				data[2] = (sweepCount - 1U) << 4;

				if (sweepRate)
					data[2] |= 0x08U | (sweepRate - 1);

				if (sweepReload)
					data[2] |= 0x80U;

				data[3] = sweepShift;

				if (!sweepIncrease)
					data[3] |= 0x08U;

				state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
			}

			lengthCounter.SaveState( state, AsciiId<'L','E','N'>::V );
			envelope.SaveState( state, AsciiId<'E','N','V'>::V );

			{
				const byte data[13] = 
				{
					static_cast<byte>(step),
					static_cast<byte>(timer & 0xFFU),
					static_cast<byte>(timer >> 8),
					static_cast<byte>(timer >> 16),
					static_cast<byte>(timer >> 24),
					static_cast<byte>(frequency & 0xFFU),
					static_cast<byte>(frequency >> 8),
					static_cast<byte>(frequency >> 16),
					static_cast<byte>(frequency >> 24),
					static_cast<byte>(amp & 0xFFU),
					static_cast<byte>(amp >> 8),
					static_cast<byte>(amp >> 16),
					static_cast<byte>(amp >> 24),
				};

				state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
			}

			state.End();
		}

		void Apu::Square::LoadState(State::Loader& state)
		{
			while (const dword chunk = state.Begin())
			{
				switch (chunk)
				{
					case AsciiId<'R','E','G'>::V:
					{
						State::Loader::Data<4> data( state );

						waveLength = data[0] | (data[1] << 8 & 0x0700);

						// for version compatibility
						switch (data[1] >> 3 & 0xF)
						{
							case 4:  duty = 1; break;
							case 8:  duty = 2; break;
							case 12: duty = 3; break;
							default: duty = 0; break;
						}

						if (data[2] & 0x08)
							sweepRate = (data[2] & 0x07) + 1;
						else
							sweepRate = 0;

						sweepCount = (data[2] >> 4 & 0x07) + 1;
						sweepReload = data[2] >> 7;
						sweepShift = data[3] & 0x07;
						sweepIncrease = (data[3] & 0x08) ? 0U : ~0U;

						step = 0;
						timer = 0;
						break;
					}

					case AsciiId<'L','E','N'>::V:

						lengthCounter.LoadState( state );
						break;

					case AsciiId<'E','N','V'>::V:

						envelope.LoadState( state );

						UpdateFrequency();
						break;

					case AsciiId<'S','0','0'>::V:
					{
						State::Loader::Data<13> data( state );

						step = data[0];
						timer = data[1] | (data[2] << 8) | (data[3] << 16) | (data[4] << 24);
						frequency = data[5] | (data[6] << 8) | (data[7] << 16) | (data[8] << 24);
						amp = data[9] | (data[10] << 8) | (data[11] << 16) | (data[12] << 24);
						break;
					}
				}

				state.End();
			}
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		NST_SINGLE_CALL void Apu::Square::Disable(const bool disable)
		{
			active &= lengthCounter.Disable( disable );
		}

		void Apu::Square::UpdateFrequency()
		{
			if (waveLength >= MIN_FRQ && waveLength + (sweepIncrease & waveLength >> sweepShift) <= MAX_FRQ)
			{
				frequency = (waveLength + 1UL) * 2 * fixed;
				validFrequency = true;
				active = lengthCounter.GetCount() && envelope.Volume();
			}
			else
			{
				validFrequency = false;
				active = false;
			}
		}

		NST_SINGLE_CALL void Apu::Square::WriteReg0(const uint data)
		{
			envelope.Write( data );
			duty = data >> REG0_DUTY_SHIFT;
			active = CanOutput();
		}

		NST_SINGLE_CALL void Apu::Square::WriteReg1(const uint data)
		{
			sweepIncrease = (data & REG1_SWEEP_DECREASE) ? 0U : ~0U;
			sweepShift = data & REG1_SWEEP_SHIFT;
			sweepRate = 0;

			if ((data & (REG1_SWEEP_ENABLED|REG1_SWEEP_SHIFT)) > REG1_SWEEP_ENABLED)
			{
				sweepRate = ((data & REG1_SWEEP_RATE) >> REG1_SWEEP_RATE_SHIFT) + 1;
				sweepReload = true;
			}

			UpdateFrequency();
		}

		NST_SINGLE_CALL void Apu::Square::WriteReg2(const uint data)
		{
			waveLength = (waveLength & uint(REG3_WAVELENGTH_HIGH)) | (data & REG3_WAVELENGTH_LOW);

			UpdateFrequency();
		}

		NST_SINGLE_CALL void Apu::Square::WriteReg3(const uint data,const Cycle frameCounterDelta)
		{
			step = 0;

			envelope.ResetClock();
			lengthCounter.Write( data, frameCounterDelta );

			waveLength = (data << 8 & REG3_WAVELENGTH_HIGH) | (waveLength & uint(REG3_WAVELENGTH_LOW));

			UpdateFrequency();
		}

		NST_SINGLE_CALL void Apu::Square::ClockEnvelope()
		{
			envelope.Clock();
			active = CanOutput();
		}

		NST_SINGLE_CALL void Apu::Square::ClockSweep(const uint complement)
		{
			if (!envelope.Looping() && lengthCounter.Clock())
				active = false;

			if (sweepRate && !--sweepCount)
			{
				sweepCount = sweepRate;

				if (waveLength >= MIN_FRQ)
				{
					const uint shifted = waveLength >> sweepShift;

					if (!sweepIncrease)
					{
						waveLength += complement - shifted;
						UpdateFrequency();
					}
					else if (waveLength + shifted <= MAX_FRQ)
					{
						waveLength += shifted;
						UpdateFrequency();
					}
				}
			}

			if (sweepReload)
			{
				sweepReload = false;
				sweepCount = sweepRate;
			}
		}

		inline uint Apu::Square::GetLengthCounter() const
		{
			return lengthCounter.GetCount();
		}

		dword Apu::Square::GetSample()
		{
			NST_VERIFY( bool(active) == CanOutput() && timer >= 0 );

			dword sum = timer;
			timer -= idword(rate);

			if (active)
			{
				static const byte forms[4][8] =
				{
					{0x1F,0x00,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F},
					{0x1F,0x00,0x00,0x1F,0x1F,0x1F,0x1F,0x1F},
					{0x1F,0x00,0x00,0x00,0x00,0x1F,0x1F,0x1F},
					{0x00,0x1F,0x1F,0x00,0x00,0x00,0x00,0x00}
				};

				const byte* const NST_RESTRICT form = forms[duty];

				if (timer >= 0)
				{
					amp = envelope.Volume() >> form[step];
				}
				else
				{
					sum >>= form[step];

					do
					{
						sum += NST_MIN(-timer,idword(frequency)) >> form[step = (step + 1) & 0x7];
						timer += idword(frequency);
					}
					while (timer < 0);

					NST_VERIFY( !envelope.Volume() || sum <= 0xFFFFFFFF / envelope.Volume() + rate/2 );
					amp = (sum * envelope.Volume() + rate/2) / rate;
				}
			}
			else
			{
				if (timer < 0)
				{
					const uint count = (-timer + frequency - 1) / frequency;
					step = (step + count) & 0x7;
					timer += idword(count * frequency);
				}

				if (amp < Channel::OUTPUT_DECAY)
				{
					return 0;
				}
				else
				{
					amp -= Channel::OUTPUT_DECAY;
				}
			}

			return amp;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		Apu::Triangle::Triangle()
		: outputVolume(0) {}

		void Apu::Triangle::Reset()
		{
			Oscillator::Reset();

			step = 0x7;
			status = STATUS_COUNTING;
			waveLength = 0;
			//linearCtrl = 0;
			linearCounter = 0;

			lengthCounter.Reset();
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		inline bool Apu::Triangle::CanOutput() const
		{
			return lengthCounter.GetCount() && linearCounter && waveLength >= MIN_FRQ && outputVolume;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		void Apu::Triangle::UpdateSettings(uint v,dword r,uint f)
		{
			Oscillator::UpdateSettings( r, f );

			outputVolume = (v * Channel::OUTPUT_MUL + Channel::DEFAULT_VOLUME/2) / Channel::DEFAULT_VOLUME;
			active = CanOutput();
		}

		void Apu::Triangle::SaveState(State::Saver& state,const dword chunk) const
		{
			state.Begin( chunk );

			{
				const byte data[4] =
				{
					static_cast<byte>(waveLength & 0xFFU),
					static_cast<byte>(waveLength >> 8),
					static_cast<byte>(linearCounter | (uint(status) << 7)),
					static_cast<byte>(linearCtrl)
				};

				state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
			}

			lengthCounter.SaveState( state, AsciiId<'L','E','N'>::V );

			{
				const byte data[9] =
				{
					static_cast<byte>(step),
					static_cast<byte>(timer & 0xFFU),
					static_cast<byte>(timer >> 8),
					static_cast<byte>(timer >> 16),
					static_cast<byte>(timer >> 24),
					static_cast<byte>(amp & 0xFFU),
					static_cast<byte>(amp >> 8),
					static_cast<byte>(amp >> 16),
					static_cast<byte>(amp >> 24),
				};

				state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
			}

			state.End();
		}

		void Apu::Triangle::LoadState(State::Loader& state)
		{
			while (const dword chunk = state.Begin())
			{
				switch (chunk)
				{
					case AsciiId<'R','E','G'>::V:
					{
						State::Loader::Data<4> data( state );

						waveLength = data[0] | (data[1] << 8 & 0x0700);
						linearCounter = data[2] & 0x7F;
						status = static_cast<Status>(data[2] >> 7);
						linearCtrl = data[3];

						frequency = (waveLength + 1UL) * fixed;
						timer = 0;
						step = 0;
						break;
					}

					case AsciiId<'L','E','N'>::V:

						lengthCounter.LoadState( state );
						break;

					case AsciiId<'S','0','0'>::V:
					{
						State::Loader::Data<9> data( state );

						step = data[0];
						timer = data[1] | (data[2] << 8) | (data[3] << 16) | (data[4] << 24);
						amp = data[5] | (data[6] << 8) | (data[7] << 16) | (data[8] << 24);
						break;
					}
				}

				state.End();
			}

			active = CanOutput();
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		NST_SINGLE_CALL void Apu::Triangle::Disable(const bool disable)
		{
			active &= lengthCounter.Disable( disable );
		}

		NST_SINGLE_CALL void Apu::Triangle::WriteReg0(const uint data)
		{
			linearCtrl = data;
		}

		NST_SINGLE_CALL void Apu::Triangle::WriteReg2(const uint data)
		{
			waveLength = (waveLength & uint(REG3_WAVE_LENGTH_HIGH)) | (data & REG2_WAVE_LENGTH_LOW);
			frequency = (waveLength + 1UL) * fixed;

			active = CanOutput();
		}

		NST_SINGLE_CALL void Apu::Triangle::WriteReg3(const uint data,const Cycle frameCounterDelta)
		{
			waveLength = (data << 8 & REG3_WAVE_LENGTH_HIGH) | (waveLength & uint(REG2_WAVE_LENGTH_LOW));
			frequency = (waveLength + 1UL) * fixed;

			status = STATUS_RELOAD;
			lengthCounter.Write( data, frameCounterDelta );

			active = CanOutput();
		}

		NST_SINGLE_CALL void Apu::Triangle::ClockLinearCounter()
		{
			if (status == STATUS_COUNTING)
			{
				if (linearCounter && !--linearCounter)
					active = false;
			}
			else
			{
				if (!(linearCtrl & uint(REG0_LINEAR_COUNTER_START)))
					status = STATUS_COUNTING;

				linearCounter = linearCtrl & uint(REG0_LINEAR_COUNTER_LOAD);
				active = CanOutput();
			}
		}

		NST_SINGLE_CALL void Apu::Triangle::ClockLengthCounter()
		{
			if (!(linearCtrl & uint(REG0_LINEAR_COUNTER_START)) && lengthCounter.Clock())
				active = false;
		}

		NST_SINGLE_CALL dword Apu::Triangle::GetSample()
		{
			NST_VERIFY( bool(active) == CanOutput() && timer >= 0 );

			if (active)
			{
				static const byte pyramid[32] =
				{
					0x0,0x1,0x2,0x3,0x4,0x5,0x6,0x7,
					0x8,0x9,0xA,0xB,0xC,0xD,0xE,0xF,
					0xF,0xE,0xD,0xC,0xB,0xA,0x9,0x8,
					0x7,0x6,0x5,0x4,0x3,0x2,0x1,0x0
				};

				dword sum = timer;
				timer -= idword(rate);

				if (timer >= 0)
				{
					amp = pyramid[step] * outputVolume * 3;
				}
				else
				{
					sum *= pyramid[step];

					do
					{
						sum += NST_MIN(-timer,idword(frequency)) * pyramid[step = (step + 1) & 0x1F];
						timer += idword(frequency);
					}
					while (timer < 0);

					NST_VERIFY( !outputVolume || sum <= 0xFFFFFFFF / outputVolume + rate/2 );
					amp = (sum * outputVolume + rate/2) / rate * 3;
				}
			}
			/*else if (amp < Channel::OUTPUT_DECAY)
			{
				return 0;
			}
			else
			{
				amp -= Channel::OUTPUT_DECAY;
				step &= STEP_CHECK;
			}*/

			return amp;
		}

		inline uint Apu::Triangle::GetLengthCounter() const
		{
			return lengthCounter.GetCount();
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		void Apu::Noise::Reset(const CpuModel model)
		{
			Oscillator::Reset();

			frequency = lut[model][0] * dword(fixed);

			bits = 1;
			shifter = 13;

			envelope.Reset();
			lengthCounter.Reset();
		}

		uint Apu::Noise::GetFrequencyIndex() const
		{
			for (uint v=frequency/fixed, i=0; i < 16; ++i)
			{
				if (v == lut[0][i] || v == lut[1][i])
					return i;
			}

			return 0;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		inline bool Apu::Noise::CanOutput() const
		{
			return lengthCounter.GetCount() && envelope.Volume();
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		void Apu::Noise::UpdateSettings(uint v,dword r,uint f)
		{
			Oscillator::UpdateSettings( r, f );
			envelope.SetOutputVolume( (v * Channel::OUTPUT_MUL + Channel::DEFAULT_VOLUME/2) / Channel::DEFAULT_VOLUME );
			active = CanOutput();
		}

		void Apu::Noise::SaveState(State::Saver& state,const dword chunk) const
		{
			state.Begin( chunk );

			state.Begin( AsciiId<'R','E','G'>::V ).Write8( (shifter == 8 ? 0x10 : 0x00) | GetFrequencyIndex() ).End();
			lengthCounter.SaveState( state, AsciiId<'L','E','N'>::V );
			envelope.SaveState( state, AsciiId<'E','N','V'>::V );

			{
				const byte data[6] =
				{
					static_cast<byte>(bits & 0xFFU),
					static_cast<byte>(bits >> 8),
					static_cast<byte>(timer & 0xFFU),
					static_cast<byte>(timer >> 8),
					static_cast<byte>(timer >> 16),
					static_cast<byte>(timer >> 24)
				};

				state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
			}

			state.End();
		}

		void Apu::Noise::LoadState(State::Loader& state,const CpuModel model)
		{
			while (const dword chunk = state.Begin())
			{
				switch (chunk)
				{
					case AsciiId<'R','E','G'>::V:
					{
						const uint data = state.Read8();

						frequency = lut[model][data & 0x0F] * dword(fixed);
						shifter = (data & 0x10) ? 8 : 13;

						timer = 0;
						bits = 1;
						break;
					}

					case AsciiId<'L','E','N'>::V:

						lengthCounter.LoadState( state );
						break;

					case AsciiId<'E','N','V'>::V:

						envelope.LoadState( state );
						break;

					case AsciiId<'S','0','0'>::V:
					{
						State::Loader::Data<6> data( state );

						bits = data[0] | (data[1] << 8);
						timer = data[2] | (data[3] << 8) | (data[4] << 16) | (data[5] << 24);
						break;
					}
				}

				state.End();
			}

			active = CanOutput();
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		NST_SINGLE_CALL void Apu::Noise::Disable(const bool disable)
		{
			active &= lengthCounter.Disable( disable );
		}

		NST_SINGLE_CALL void Apu::Noise::WriteReg0(const uint data)
		{
			envelope.Write( data );
			active = CanOutput();
		}

		NST_SINGLE_CALL void Apu::Noise::WriteReg2(const uint data,const CpuModel model)
		{
			frequency = lut[model][data & REG2_FREQUENCY] * dword(fixed);
			shifter = (data & REG2_93BIT_MODE) ? 8 : 13;
		}

		NST_SINGLE_CALL void Apu::Noise::WriteReg3(const uint data,const Cycle frameCounterDelta)
		{
			envelope.ResetClock();
			lengthCounter.Write( data, frameCounterDelta );

			active = CanOutput();
		}

		NST_SINGLE_CALL void Apu::Noise::ClockEnvelope()
		{
			envelope.Clock();
			active = CanOutput();
		}

		NST_SINGLE_CALL void Apu::Noise::ClockLengthCounter()
		{
			if (!envelope.Looping() && lengthCounter.Clock())
				active = false;
		}

		NST_SINGLE_CALL dword Apu::Noise::GetSample()
		{
			NST_VERIFY( bool(active) == CanOutput() && timer >= 0 );

			dword sum = timer;
			timer -= idword(rate);

			if (active)
			{
				if (timer >= 0)
				{
					if (!(bits & 0x4000))
						return envelope.Volume() * 2;
				}
				else
				{
					if (bits & 0x4000)
						sum = 0;

					do
					{
						bits = (bits << 1) | ((bits >> 14 ^ bits >> shifter) & 0x1);

						if (!(bits & 0x4000))
							sum += NST_MIN(-timer,idword(frequency));

						timer += idword(frequency);
					}
					while (timer < 0);

					NST_VERIFY( !envelope.Volume() || sum <= 0xFFFFFFFF / envelope.Volume() + rate/2 );
					return (sum * envelope.Volume() + rate/2) / rate * 2;
				}
			}
			else while (timer < 0)
			{
				bits = (bits << 1) | ((bits >> 14 ^ bits >> shifter) & 0x1);
				timer += idword(frequency);
			}

			return 0;
		}

		inline uint Apu::Noise::GetLengthCounter() const
		{
			return lengthCounter.GetCount();
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		Apu::Dmc::Dmc()
		: outputVolume(0)
		{
			frequency = GetResetFrequency( CPU_RP2A03 );
		}

		void Apu::Dmc::Reset(const CpuModel model)
		{
			curSample          = 0;
			linSample          = 0;
			frequency          = GetResetFrequency( model );
			regs.ctrl          = 0;
			regs.lengthCounter = 1;
			regs.address       = 0xC000;
			out.active         = false;
			out.shifter        = 0;
			out.dac            = 0;
			out.buffer         = 0x00;
			dma.lengthCounter  = 0;
			dma.buffered       = false;
			dma.address        = 0xC000;
			dma.buffer         = 0x00;
		}

		Cycle Apu::Dmc::GetResetFrequency(CpuModel model)
		{
			return lut[model][0];
		}

		void Apu::Dmc::UpdateSettings(uint v)
		{
			v = (v * Channel::OUTPUT_MUL + Channel::DEFAULT_VOLUME/2) / Channel::DEFAULT_VOLUME;

			if (outputVolume)
				linSample /= outputVolume;

			if (outputVolume)
				curSample /= outputVolume;

			linSample *= v;
			curSample *= v;
			outputVolume = v;

			if (!v)
				out.active = false;
		}

		inline void Apu::Dmc::ClearAmp()
		{
			curSample = 0;
			linSample = 0;
		}

		void Apu::Dmc::SaveState(State::Saver& state,const dword chunk,const Cpu& cpu,const Cycle dmcMcClock) const
		{
			NST_VERIFY( dmcMcClock >= cpu.GetCycles() );

			dword dmcClock = dmcMcClock;

			if (dmcClock > cpu.GetCycles())
				dmcClock = (dmcClock - cpu.GetCycles()) / cpu.GetClock();
			else
				dmcClock = 0;

			NST_VERIFY( dmcClock <= 0x1FFF && dmcMcClock == cpu.GetCycles() + dmcClock * cpu.GetClock() );

			state.Begin( chunk );

			{
				const byte data[12] =
				{
					static_cast<byte>(dmcClock & 0xFF),
					static_cast<byte>(dmcClock >> 8),
					static_cast<byte>((
						( ( regs.ctrl & REG0_FREQUENCY  )              ) |
						( ( regs.ctrl & REG0_LOOP       ) ? 0x10U : 0U ) |
						( ( regs.ctrl & REG0_IRQ_ENABLE ) ? 0x20U : 0U ) |
						( ( dma.lengthCounter           ) ? 0x40U : 0U )
					)),
					static_cast<byte>((regs.address - 0xC000U) >> 6),
					static_cast<byte>((regs.lengthCounter - 1U) >> 4),
					static_cast<byte>((dma.address >> 0 & 0xFFU)),
					static_cast<byte>((dma.address >> 8 & 0x7FU) | (dma.buffered ? 0x80 : 0x00)),
					static_cast<byte>(dma.lengthCounter ? (dma.lengthCounter - 1U) >> 4 : 0),
					static_cast<byte>(dma.buffer),
					static_cast<byte>(7 - out.shifter),
					out.buffer,
					out.dac,
				};

				state.Begin( AsciiId<'R','E','G'>::V ).Write( data ).End();
			}

			{
				const byte data[4] =
				{
					static_cast<byte>(linSample & 0xFFU),
					static_cast<byte>(linSample >> 8),
					static_cast<byte>(dma.lengthCounter & 0xFFU),
					static_cast<byte>(dma.lengthCounter >> 8),
				};

				state.Begin( AsciiId<'S','0','0'>::V ).Write( data ).End();
			}
			
			state.End();
		}

		void Apu::Dmc::LoadState(State::Loader& state,const Cpu& cpu,const CpuModel model,Cycle& dmcClock)
		{
			while (const dword chunk = state.Begin())
			{
				switch (chunk)
				{
					case AsciiId<'R','E','G'>::V:
					{
						State::Loader::Data<12> data( state );

						dmcClock = cpu.GetCycles() + ((data[0] | data[1] << 8) * cpu.GetClock());

						regs.ctrl =
						(
							( ( data[2] & 0x10 ) ? REG0_LOOP       : 0U ) |
							( ( data[2] & 0x20 ) ? REG0_IRQ_ENABLE : 0U ) |
							( ( data[2] & REG0_FREQUENCY )              )
						);

						frequency          = lut[model][regs.ctrl & REG0_FREQUENCY];
						regs.address       = 0xC000 | (data[3] << 6);
						regs.lengthCounter = (data[4] << 4) + 1;
						dma.address        = 0x8000 | data[5] | (data[6] << 8 & 0x7F00);
						dma.buffered       = data[6] >> 7;
						dma.lengthCounter  = (data[2] & 0x40) ? (data[7] << 4) + 1 : 0;
						dma.buffer         = data[8];
						out.shifter        = 7 - (data[9] & 0x7);
						out.buffer         = data[10];
						out.dac            = data[11] & 0x7F;

						curSample = out.dac * outputVolume;
						linSample = curSample;
						out.active = dma.buffered && outputVolume;
						break;
					}

					case AsciiId<'S','0','0'>::V:
					{
						State::Loader::Data<4> data( state );

						linSample = data[0] | (data[1] << 8);
						dma.lengthCounter = data[2] | (data[3] << 8);
						break;
					}
				}

				state.End();
			}
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		NST_SINGLE_CALL void Apu::Dmc::Disable(const bool disable,Cpu& cpu)
		{
			cpu.ClearIRQ( Cpu::IRQ_DMC );

			if (disable)
			{
				dma.lengthCounter = 0;
			}
			else if (!dma.lengthCounter)
			{
				dma.lengthCounter = regs.lengthCounter;
				dma.address = regs.address;

				if (!dma.buffered)
					DoDMA( cpu, cpu.GetCycles() );
			}
		}

		NST_SINGLE_CALL dword Apu::Dmc::GetSample()
		{
			if (curSample != linSample)
			{
				const uint step = outputVolume * INP_STEP;

				if (curSample + step - linSample <= step*2)
				{
					linSample = curSample;
				}
				else if (curSample > linSample)
				{
					linSample += step;
				}
				else
				{
					linSample -= step;
				}
			}

			return linSample;
		}

		void Apu::Dmc::DoDMA(Cpu& cpu,const Cycle clock,const uint readAddress)
		{
			NST_VERIFY( !dma.buffered && (!readAddress || !cpu.IsWriteCycle(clock)) );

			/* DMC DMA adds:
			 * case 1: 4 cycles normally
			 * case 2: 3 if it lands on a CPU write
			 * case 3: 2 if it lands on the $4014 write or during OAM DMA
			 * case 4: 1 if on the next-to-next-to-last DMA cycle
			 * case 5: 3 if on the last DMA cycle
			 * https://forums.nesdev.org/viewtopic.php?f=3&t=6100
			 * https://www.nesdev.org/wiki/DMA
			*/
			uint cyclesToSteal = cpu.IsWriteCycle(clock) ? 3 : 4;

			if (cpu.GetOamDMA())
			{
				if (cpu.GetOamDMACycle() == 255)
				{
					cyclesToSteal = 3;
				}
				else if (cpu.GetOamDMACycle() == 254)
				{
					cyclesToSteal = 1;
				}
				else
				{
					cyclesToSteal = 2;
				}
			}

			if (readAddress && cpu.GetCycles() == clock)
			{
				NST_DEBUG_MSG("DMA/Read conflict!");

				/* According to dmc_dma_during_read4/dma_2007_read, DMC DMA during read causes
				 * 2-3 extra $2007 reads before the real read. The nesdev wiki states that this
				 * also happens when polling $2002 for vblank.
				*/
				if ((readAddress & 0xF000) != 0x4000)
				{
					cpu.Peek( readAddress );
					cpu.Peek( readAddress );
				}

				cpu.Peek( readAddress );
			}

			cpu.StealCycles( cpu.GetClock() * cyclesToSteal);

			dma.buffer = cpu.Peek( dma.address );
			dma.address = 0x8000 | ((dma.address + 1U) & 0x7FFF);
			dma.buffered = true;

			NST_VERIFY( dma.lengthCounter );

			if (!--dma.lengthCounter)
			{
				if (regs.ctrl & REG0_LOOP)
				{
					dma.address = regs.address;
					dma.lengthCounter = regs.lengthCounter;
				}
				else if (regs.ctrl & REG0_IRQ_ENABLE)
				{
					cpu.DoIRQ( Cpu::IRQ_DMC );
				}
			}
		}

		NST_SINGLE_CALL bool Apu::Dmc::WriteReg0(const uint data,const CpuModel model)
		{
			regs.ctrl = data;
			frequency = lut[model][data & REG0_FREQUENCY];
			return data & REG0_IRQ_ENABLE;
		}

		NST_SINGLE_CALL void Apu::Dmc::WriteReg1(const uint data)
		{
			out.dac = data & 0x7F;
			curSample = out.dac * outputVolume;
		}

		NST_SINGLE_CALL void Apu::Dmc::WriteReg2(const uint data)
		{
			regs.address = 0xC000 | (data << 6);
		}

		NST_SINGLE_CALL void Apu::Dmc::WriteReg3(const uint data)
		{
			regs.lengthCounter = (data << 4) + 1;
		}

		NST_SINGLE_CALL bool Apu::Dmc::ClockDAC()
		{
			if (out.active)
			{
				const uint next = out.dac + ((out.buffer & 0x1U) << 2) - 2;
				out.buffer >>= 1;

				if (next <= 0x7F && next != out.dac)
				{
					out.dac = next;
					return true;
				}
			}

			return false;
		}

		NST_SINGLE_CALL void Apu::Dmc::Update()
		{
			curSample = out.dac * outputVolume;
		}

		NST_SINGLE_CALL void Apu::Dmc::ClockDMA(Cpu& cpu,Cycle& clock,const uint readAddress)
		{
			const Cycle tmp = clock;
			clock += frequency;

			if (out.shifter)
			{
				out.shifter--;
			}
			else
			{
				out.shifter = 7;
				out.active = dma.buffered;

				if (out.active)
				{
					out.active = outputVolume;
					dma.buffered = false;
					out.buffer = dma.buffer;

					if (dma.lengthCounter)
						DoDMA( cpu, tmp, readAddress );
				}
			}
		}

		inline uint Apu::Dmc::GetLengthCounter() const
		{
			return dma.lengthCounter;
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("s", on)
		#endif

		void Apu::ClearBuffers()
		{
			ClearBuffers( true );
		}

		NST_NO_INLINE void Apu::ClearBuffers(bool resync)
		{
			if (resync)
				synchronizer.Resync( settings.speed, cpu );

			square[0].ClearAmp();
			square[1].ClearAmp();
			triangle.ClearAmp();
			noise.ClearAmp();
			dmc.ClearAmp();

			dcBlocker.Reset();

			buffer.Reset( false );
		}

		#ifdef NST_MSVC_OPTIMIZE
		#pragma optimize("", on)
		#endif

		Cycle Apu::Clock()
		{
			if (cycles.dmcClock <= cpu.GetCycles())
				ClockDmc( cpu.GetCycles() );

			if (cycles.frameIrqClock <= cpu.GetCycles())
				ClockFrameIRQ( cpu.GetCycles() );

			return NST_MIN(cycles.dmcClock,cycles.frameIrqClock);
		}

		void Apu::ClockDMA(uint readAddress)
		{
			if (cycles.dmcClock <= cpu.GetCycles())
				ClockDmc( cpu.GetCycles(), readAddress );
		}

		NST_NO_INLINE void Apu::ClockOscillators(const bool twoClocks)
		{
			for (uint i=0; i < 2; ++i)
				square[i].ClockEnvelope();

			triangle.ClockLinearCounter();
			noise.ClockEnvelope();

			if (twoClocks)
			{
				for (uint i=0; i < 2; ++i)
					square[i].ClockSweep( i-1 );

				triangle.ClockLengthCounter();
				noise.ClockLengthCounter();
			}
		}

		NST_NO_INLINE void Apu::ClockDmc(const Cycle target,const uint readAddress)
		{
			NST_ASSERT( cycles.dmcClock <= target );

			do
			{
				if (dmc.ClockDAC())
				{
					Update( cycles.dmcClock );
					dmc.Update();
				}

				dmc.ClockDMA( cpu, cycles.dmcClock, readAddress );
			}
			while (cycles.dmcClock <= target);
		}

		NST_NO_INLINE void Apu::ClockFrameCounter()
		{
			NST_COMPILE_ASSERT( STATUS_SEQUENCE_5_STEP == 0x80 );
			NST_VERIFY( cycles.frameCounter <= cpu.GetCycles() * cycles.fixed );

			ClockOscillators( cycles.frameDivider & 0x1U );

			cycles.frameDivider = (cycles.frameDivider + 1) & 0x3U;
			cycles.frameCounter += Cycles::oscillatorClocks[cpu.GetModel()][ctrl >> 7][cycles.frameDivider] * cycles.fixed;
		}

		NST_NO_INLINE void Apu::ClockFrameIRQ(const Cycle target)
		{
			NST_VERIFY( ctrl == STATUS_FRAME_IRQ_ENABLE );

			cpu.DoIRQ( Cpu::IRQ_FRAME, cycles.frameIrqClock );

			Cycle clock = cycles.frameIrqClock;
			uint repeat = cycles.frameIrqRepeat;

			do
			{
				clock += Cycles::frameClocks[cpu.GetModel()][1 + repeat++ % 3];
			}
			while (clock <= target);

			cycles.frameIrqClock = clock;
			cycles.frameIrqRepeat = repeat;
		}

		NST_NO_INLINE Apu::Channel::Sample Apu::GetSample()
		{
			dword dac[2];

			return Clamp<Channel::OUTPUT_MIN,Channel::OUTPUT_MAX>
			(
				dcBlocker.Apply
				(
					(0 != (dac[0] = square[0].GetSample() + square[1].GetSample()) ? NLN_SQ_0 / (NLN_SQ_1 / dac[0] + NLN_SQ_2) : 0) +
					(0 != (dac[1] = triangle.GetSample() + noise.GetSample() + dmc.GetSample()) ? NLN_TND_0 / (NLN_TND_1 / dac[1] + NLN_TND_2) : 0)
				) + (extChannel ? extChannel->GetSample() : 0)
			);
		}

		NES_POKE_AD(Apu,4000)
		{
			UpdateLatency();
			square[address >> 2 & 0x1].WriteReg0( data );
		}

		NES_POKE_AD(Apu,4001)
		{
			Update();
			square[address >> 2 & 0x1].WriteReg1( data );
		}

		NES_POKE_AD(Apu,4002)
		{
			Update();
			square[address >> 2 & 0x1].WriteReg2( data );
		}

		NES_POKE_AD(Apu,4003)
		{
			square[address >> 2 & 0x1].WriteReg3( data, UpdateDelta() );
		}

		NES_POKE_D(Apu,4008)
		{
			Update();
			triangle.WriteReg0( data );
		}

		NES_POKE_D(Apu,400A)
		{
			Update();
			triangle.WriteReg2( data );
		}

		NES_POKE_D(Apu,400B)
		{
			triangle.WriteReg3( data, UpdateDelta() );
		}

		NES_POKE_D(Apu,400C)
		{
			UpdateLatency();
			noise.WriteReg0( data );
		}

		NES_POKE_D(Apu,400E)
		{
			Update();
			noise.WriteReg2( data, cpu.GetModel() );
		}

		NES_POKE_D(Apu,400F)
		{
			noise.WriteReg3( data, UpdateDelta() );
		}

		NES_POKE_D(Apu,4010)
		{
			if (!dmc.WriteReg0( data, cpu.GetModel() ))
				cpu.ClearIRQ( Cpu::IRQ_DMC );
		}

		NES_POKE_D(Apu,4011)
		{
			Update();
			dmc.WriteReg1( data );
		}

		NES_POKE_D(Apu,4012)
		{
			dmc.WriteReg2( data );
		}

		NES_POKE_D(Apu,4013)
		{
			dmc.WriteReg3( data );
		}

		NES_POKE_D(Apu,4015)
		{
			Update();

			data = ~data;

			square[0].Disable ( data >> 0 & 0x1  );
			square[1].Disable ( data >> 1 & 0x1  );
			triangle.Disable  ( data >> 2 & 0x1  );
			noise.Disable     ( data >> 3 & 0x1  );
			dmc.Disable       ( data & 0x10, cpu );
		}

		NES_PEEK_A(Apu,4015)
		{
			NST_COMPILE_ASSERT( Cpu::IRQ_FRAME == 0x40 && Cpu::IRQ_DMC == 0x80 );

			const Cycle elapsed = cpu.Update( address );

			if (cycles.frameIrqClock <= elapsed)
				ClockFrameIRQ( elapsed );

			if (cycles.frameCounter < elapsed * cycles.fixed)
				Update( elapsed );

			const uint data = cpu.GetIRQ();
			cpu.ClearIRQ( Cpu::IRQ_FRAME );

			return (data & (Cpu::IRQ_FRAME|Cpu::IRQ_DMC)) |
			(
				( square[0].GetLengthCounter() ? 0x01U : 0x00U ) |
				( square[1].GetLengthCounter() ? 0x02U : 0x00U ) |
				( triangle.GetLengthCounter()  ? 0x04U : 0x00U ) |
				( noise.GetLengthCounter()     ? 0x08U : 0x00U ) |
				( dmc.GetLengthCounter()       ? 0x10U : 0x00U )
			);
		}

		void Apu::WriteFrameCtrl(uint data)
		{
			Cycle next = cpu.Update();

			if (cpu.IsOddCycle())
				next += cpu.GetClock();

			Update( next );

			if (cycles.frameIrqClock <= next)
				ClockFrameIRQ( next );

			next += cpu.GetClock();

			data &= STATUS_BITS;

			cycles.frameCounter = (next + Cycles::oscillatorClocks[cpu.GetModel()][data >> 7][0]) * cycles.fixed;
			cycles.frameDivider = 0;
			cycles.frameIrqRepeat = 0;

			ctrl = data;

			if (data)
			{
				cycles.frameIrqClock = Cpu::CYCLE_MAX;

				if (data & STATUS_NO_FRAME_IRQ)
					cpu.ClearIRQ( Cpu::IRQ_FRAME );

				if (data & STATUS_SEQUENCE_5_STEP)
					ClockOscillators( true );
			}
			else
			{
				cycles.frameIrqClock = next + Cycles::frameClocks[cpu.GetModel()][0];
			}
		}

		NES_PEEK(Apu,40xx)
		{
			return 0x40;
		}
	}
}
