auto DSP::envelopeRun(Voice& v) -> void {
  s32 envelope = v.envelope;

  if(v.envelopeMode == Envelope::Release) {  //60%
    envelope -= 0x8;
    if(envelope < 0) envelope = 0;
    v.envelope = envelope;
    return;
  }

  s32 rate;
  s32 envelopeData = v.adsr1;
  if(latch.adsr0.bit(7)) {  //99% ADSR
    if(v.envelopeMode >= Envelope::Decay) {  //99%
      envelope--;
      envelope -= envelope >> 8;
      rate = envelopeData & 0x1f;
      if(v.envelopeMode == Envelope::Decay) {  //1%
        rate = latch.adsr0.bit(4,6) * 2 + 16;
      }
    } else {  //env_attack
      rate = latch.adsr0.bit(0,3) * 2 + 1;
      envelope += rate < 31 ? 0x20 : 0x400;
    }
  } else {  //GAIN
    envelopeData = v.gain;
    s32 mode = envelopeData >> 5;
    if(mode < 4) {  //direct
      envelope = envelopeData << 4;
      rate = 31;
    } else {
      rate = envelopeData & 0x1f;
      if(mode == 4) {  //4: linear decrease
        envelope -= 0x20;
      } else if(mode < 6) {  //5: exponential decrease
        envelope--;
        envelope -= envelope >> 8;
      } else {  //6, 7: linear increase
        envelope += 0x20;
        if(mode > 6 && (u32)v._envelope >= 0x600) {
          envelope += 0x8 - 0x20;  //7: two-slope linear increase
        }
      }
    }
  }

  //sustain level
  if((envelope >> 8) == (envelopeData >> 5) && v.envelopeMode == Envelope::Decay) {
    v.envelopeMode = Envelope::Sustain;
  }
  v._envelope = envelope;

  //u32 cast because linear decrease underflowing also triggers this
  if((u32)envelope > 0x7ff) {
    envelope = (envelope < 0 ? 0 : 0x7ff);
    if(v.envelopeMode == Envelope::Attack) v.envelopeMode = Envelope::Decay;
  }

  if(counterPoll(rate)) v.envelope = envelope;
}
