struct APU : Thread {
  shared_pointer<Emulator::Stream> stream;

  inline auto rate() const -> uint { return Region::PAL() ? 16 : 12; }

  //apu.cpp
  APU();

  static auto Enter() -> void;
  auto main() -> void;
  auto tick() -> void;
  auto setIRQ() -> void;
  auto setSample(int16 sample) -> void;

  auto power(bool reset) -> void;

  auto readIO(uint16 addr) -> uint8;
  auto writeIO(uint16 addr, uint8 data) -> void;

  //serialization.cpp
  auto serialize(serializer&) -> void;

  struct Envelope {
    auto volume() const -> uint;
    auto clock() -> void;

    auto power() -> void;

    auto serialize(serializer&) -> void;

    uint4 speed;
    bool useSpeedAsVolume;
    bool loopMode;

    bool reloadDecay;
    uint8 decayCounter;
    uint4 decayVolume;
  };

  struct Sweep {
    auto checkPeriod() -> bool;
    auto clock(uint channel) -> void;

    auto power() -> void;

    auto serialize(serializer&) -> void;

    uint8 shift;
    bool decrement;
    uint3 period;
    uint8 counter;
    bool enable;
    bool reload;
    uint11 pulsePeriod;
  };

  struct Pulse {
    auto clockLength() -> void;
    auto checkPeriod() -> bool;
    auto clock() -> uint8;

    auto power() -> void;

    auto serialize(serializer&) -> void;

    uint lengthCounter;

    Envelope envelope;
    Sweep sweep;

    uint2 duty;
    uint3 dutyCounter;

    uint11 period;
    uint periodCounter;
  } pulse[2];

  struct Triangle {
    auto clockLength() -> void;
    auto clockLinearLength() -> void;
    auto clock() -> uint8;

    auto power() -> void;

    auto serialize(serializer&) -> void;

    uint lengthCounter;

    uint8 linearLength;
    bool haltLengthCounter;

    uint11 period;
    uint periodCounter;

    uint5 stepCounter;
    uint8 linearLengthCounter;
    bool reloadLinear;
  } triangle;

  struct Noise {
    auto clockLength() -> void;
    auto clock() -> uint8;

    auto power() -> void;

    auto serialize(serializer&) -> void;

    uint lengthCounter;

    Envelope envelope;

    uint4 period;
    uint periodCounter;

    bool shortMode;
    uint15 lfsr;
  } noise;

  struct DMC {
    auto start() -> void;
    auto stop() -> void;
    auto clock() -> uint8;

    auto power() -> void;

    auto serialize(serializer&) -> void;

    uint lengthCounter;
    bool irqPending;

    uint4 period;
    uint periodCounter;

    bool irqEnable;
    bool loopMode;

    uint8 dacLatch;
    uint8 addrLatch;
    uint8 lengthLatch;

    uint15 readAddr;
    uint dmaDelayCounter;

    uint3 bitCounter;
    bool dmaBufferValid;
    uint8 dmaBuffer;

    bool sampleValid;
    uint8 sample;
  } dmc;

  struct FrameCounter {
    auto serialize(serializer&) -> void;

    enum : uint { NtscPeriod = 14915 };  //~(21.477MHz / 6 / 240hz)

    bool irqPending;

    uint2 mode;
    uint2 counter;
    int divider;
  };

  auto clockFrameCounter() -> void;
  auto clockFrameCounterDivider() -> void;

  FrameCounter frame;

  uint8 enabledChannels;
  int16 cartridgeSample;

  int16 pulseDAC[32];
  int16 dmcTriangleNoiseDAC[128][16][16];

  static const uint8 lengthCounterTable[32];
  static const uint16 dmcPeriodTableNTSC[16];
  static const uint16 dmcPeriodTablePAL[16];
  static const uint16 noisePeriodTableNTSC[16];
  static const uint16 noisePeriodTablePAL[16];
};

extern APU apu;
