auto APU::Wave::getPattern(uint5 offset) const -> uint4 {
  return pattern[offset >> 1] >> (offset & 1 ? 0 : 4);
}

auto APU::Wave::run() -> void {
  if(patternHold) patternHold--;

  if(period && --period == 0) {
    period = 1 * (2048 - frequency);
    patternSample = getPattern(++patternOffset);
    patternHold = 1;
  }

  static const uint shift[] = {4, 0, 1, 2};  //0%, 100%, 50%, 25%
  uint4 sample = patternSample >> shift[volume];
  if(!enable) sample = 0;

  output = sample;
}

auto APU::Wave::clockLength() -> void {
  if(counter) {
    if(length && --length == 0) enable = false;
  }
}

auto APU::Wave::read(uint16 addr) -> uint8 {
  if(addr == 0xff1a) {  //NR30
    return dacEnable << 7 | 0x7f;
  }

  if(addr == 0xff1b) {  //NR31
    return 0xff;
  }

  if(addr == 0xff1c) {  //NR32
    return 0x80 | volume << 5 | 0x1f;
  }

  if(addr == 0xff1d) {  //NR33
    return 0xff;
  }

  if(addr == 0xff1e) {  //NR34
    return 0x80 | counter << 6 | 0x3f;
  }

  if(addr >= 0xff30 && addr <= 0xff3f) {
    if(enable) {
      if(!Model::GameBoyColor() && !patternHold) return 0xff;
      return pattern[patternOffset >> 1];
    } else {
      return pattern[addr & 15];
    }
  }

  return 0xff;
}

auto APU::Wave::write(uint16 addr, uint8 data) -> void {
  if(addr == 0xff1a) {  //NR30
    dacEnable = data.bit(7);
    if(!dacEnable) enable = false;
  }

  if(addr == 0xff1b) {  //NR31
    length = 256 - data;
  }

  if(addr == 0xff1c) {  //NR32
    volume = data.bits(6,5);
  }

  if(addr == 0xff1d) {  //NR33
    frequency.bits(7,0) = data;
  }

  if(addr == 0xff1e) {  //NR34
    if(apu.phase.bit(0) && !counter && data.bit(6)) {
      if(length && --length == 0) enable = false;
    }

    counter = data.bit(6);
    frequency.bits(10,8) = data.bits(2,0);

    if(data.bit(7)) {
      if(!Model::GameBoyColor() && patternHold) {
        //DMG,SGB trigger while channel is being read corrupts wave RAM
        if((patternOffset >> 1) <= 3) {
          //if current pattern is with 0-3; only byte 0 is corrupted
          pattern[0] = pattern[patternOffset >> 1];
        } else {
          //if current pattern is within 4-15; pattern&~3 is copied to pattern[0-3]
          pattern[0] = pattern[((patternOffset >> 1) & ~3) + 0];
          pattern[1] = pattern[((patternOffset >> 1) & ~3) + 1];
          pattern[2] = pattern[((patternOffset >> 1) & ~3) + 2];
          pattern[3] = pattern[((patternOffset >> 1) & ~3) + 3];
        }
      }

      enable = dacEnable;
      period = 1 * (2048 - frequency);
      patternOffset = 0;
      patternSample = 0;
      patternHold = 0;

      if(!length) {
        length = 256;
        if(apu.phase.bit(0) && counter) length--;
      }
    }
  }

  if(addr >= 0xff30 && addr <= 0xff3f) {
    if(enable) {
      if(!Model::GameBoyColor() && !patternHold) return;
      pattern[patternOffset >> 1] = data;
    } else {
      pattern[addr & 15] = data;
    }
  }
}

auto APU::Wave::power(bool initializeLength) -> void {
  enable = 0;

  dacEnable = 0;
  volume = 0;
  frequency = 0;
  counter = 0;

  output = 0;
  period = 0;
  patternOffset = 0;
  patternSample = 0;
  patternHold = 0;

  if(initializeLength) length = 256;
}

auto APU::Wave::serialize(serializer& s) -> void {
  s.integer(enable);

  s.integer(dacEnable);
  s.integer(volume);
  s.integer(frequency);
  s.integer(counter);
  s.array(pattern);

  s.integer(output);
  s.integer(length);
  s.integer(period);
  s.integer(patternOffset);
  s.integer(patternSample);
  s.integer(patternHold);
}
