auto APU::Sequencer::run() -> void {
  if(enable == false) {
    center = 0;
    left   = 0;
    right  = 0;

    centerBias = leftBias = rightBias = 0;
    return;
  }

  int sample = 0;
  sample += apu.square1.output;
  sample += apu.square2.output;
  sample +=    apu.wave.output;
  sample +=   apu.noise.output;
  center = (sample * 512) - 16384;

  sample = 0;
  if(square1.leftEnable) sample += apu.square1.output;
  if(square2.leftEnable) sample += apu.square2.output;
  if(   wave.leftEnable) sample +=    apu.wave.output;
  if(  noise.leftEnable) sample +=   apu.noise.output;
  sample = (sample * 512) - 16384;
  sample = (sample * (leftVolume + 1)) / 8;
  left = sample;

  sample = 0;
  if(square1.rightEnable) sample += apu.square1.output;
  if(square2.rightEnable) sample += apu.square2.output;
  if(   wave.rightEnable) sample +=    apu.wave.output;
  if(  noise.rightEnable) sample +=   apu.noise.output;
  sample = (sample * 512) - 16384;
  sample = (sample * (rightVolume + 1)) / 8;
  right = sample;

  //reduce audio volume
  center >>= 1;
  left   >>= 1;
  right  >>= 1;
}

auto APU::Sequencer::read(uint16 addr) -> uint8 {
  if(addr == 0xff24) {  //NR50
    return leftEnable << 7 | leftVolume << 4 | rightEnable << 3 | rightVolume;
  }

  if(addr == 0xff25) {  //NR51
    return noise.leftEnable    << 7
         | wave.leftEnable     << 6
         | square2.leftEnable  << 5
         | square1.leftEnable  << 4
         | noise.rightEnable   << 3
         | wave.rightEnable    << 2
         | square2.rightEnable << 1
         | square1.rightEnable << 0;
  }

  if(addr == 0xff26) {  //NR52
    return enable << 7 | 0x70
         | apu.noise.enable   << 3
         | apu.wave.enable    << 2
         | apu.square2.enable << 1
         | apu.square1.enable << 0;
  }

  return 0xff;
}

auto APU::Sequencer::write(uint16 addr, uint8 data) -> void {
  if(addr == 0xff24) {  //NR50
    leftEnable  = (uint1)(data >> 7);
    leftVolume  = (uint3)(data >> 4);
    rightEnable = (uint1)(data >> 3);
    rightVolume = (uint3)(data >> 0);
  }

  if(addr == 0xff25) {  //NR51
    noise.leftEnable    = data & 0x80;
    wave.leftEnable     = data & 0x40;
    square2.leftEnable  = data & 0x20;
    square1.leftEnable  = data & 0x10;
    noise.rightEnable   = data & 0x08;
    wave.rightEnable    = data & 0x04;
    square2.rightEnable = data & 0x02;
    square1.rightEnable = data & 0x01;
  }

  if(addr == 0xff26) {  //NR52
    if(enable != (bool)(data & 0x80)) {
      enable = data & 0x80;

      if(!enable) {
        //power(bool) resets length counters when true (eg for CGB only)
        apu.square1.power(system.cgb());
        apu.square2.power(system.cgb());
        apu.wave.power(system.cgb());
        apu.noise.power(system.cgb());
        power();
      } else {
        apu.phase = 0;
      }
    }
  }
}

auto APU::Sequencer::power() -> void {
  leftEnable = 0;
  leftVolume = 0;
  rightEnable = 0;
  rightVolume = 0;
  noise.leftEnable = 0;
  wave.leftEnable = 0;
  square2.leftEnable = 0;
  square1.leftEnable = 0;
  noise.rightEnable = 0;
  wave.rightEnable = 0;
  square2.rightEnable = 0;
  square1.rightEnable = 0;
  enable = 0;

  center = 0;
  left   = 0;
  right  = 0;

  centerBias = 0;
  leftBias = 0;
  rightBias = 0;
}

auto APU::Sequencer::serialize(serializer& s) -> void {
  s.integer(leftEnable);
  s.integer(leftVolume);
  s.integer(rightEnable);
  s.integer(rightVolume);
  s.integer(noise.leftEnable);
  s.integer(wave.leftEnable);
  s.integer(square2.leftEnable);
  s.integer(square1.leftEnable);
  s.integer(noise.rightEnable);
  s.integer(wave.rightEnable);
  s.integer(square2.rightEnable);
  s.integer(square1.rightEnable);
  s.integer(enable);

  s.integer(center);
  s.integer(left);
  s.integer(right);

  s.integer(centerBias);
  s.integer(leftBias);
  s.integer(rightBias);
}
