/**
 * Furnace Tracker - multi-system chiptune tracker
 * Copyright (C) 2021-2023 tildearrow and contributors
 *
 * This program 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.
 *
 * This program 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 this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "engine.h"
#include "../ta-log.h"
#include "../utfutils.h"
#include "song.h"

constexpr int MASTER_CLOCK_PREC=(sizeof(void*)==8)?8:0;

void DivEngine::performVGMWrite(SafeWriter* w, DivSystem sys, DivRegWrite& write, int streamOff, double* loopTimer, double* loopFreq, int* loopSample, bool* sampleDir, bool isSecond, int* pendingFreq, int* playingSample, bool directStream) {
  unsigned char baseAddr1=isSecond?0xa0:0x50;
  unsigned char baseAddr2=isSecond?0x80:0;
  unsigned short baseAddr2S=isSecond?0x8000:0;
  unsigned char smsAddr=isSecond?0x30:0x50;
  unsigned char ggAddr=isSecond?0x3f:0x4f;
  unsigned char rf5c68Addr=isSecond?0xb1:0xb0;
  if (write.addr==0xffffffff) { // Furnace fake reset
    switch (sys) {
      case DIV_SYSTEM_YM2612:
      case DIV_SYSTEM_YM2612_EXT:
      case DIV_SYSTEM_YM2612_DUALPCM:
      case DIV_SYSTEM_YM2612_DUALPCM_EXT:
        for (int i=0; i<3; i++) { // set SL and RR to highest
          w->writeC(2|baseAddr1);
          w->writeC(0x80+i);
          w->writeC(0xff);
          w->writeC(2|baseAddr1);
          w->writeC(0x84+i);
          w->writeC(0xff);
          w->writeC(2|baseAddr1);
          w->writeC(0x88+i);
          w->writeC(0xff);
          w->writeC(2|baseAddr1);
          w->writeC(0x8c+i);
          w->writeC(0xff);

          w->writeC(3|baseAddr1);
          w->writeC(0x80+i);
          w->writeC(0xff);
          w->writeC(3|baseAddr1);
          w->writeC(0x84+i);
          w->writeC(0xff);
          w->writeC(3|baseAddr1);
          w->writeC(0x88+i);
          w->writeC(0xff);
          w->writeC(3|baseAddr1);
          w->writeC(0x8c+i);
          w->writeC(0xff);
        }
        for (int i=0; i<3; i++) { // note off
          w->writeC(2|baseAddr1);
          w->writeC(0x28);
          w->writeC(i);
          w->writeC(2|baseAddr1);
          w->writeC(0x28);
          w->writeC(4+i);
        }
        w->writeC(2|baseAddr1); // disable DAC
        w->writeC(0x2b);
        w->writeC(0);
        break;
      case DIV_SYSTEM_SMS:
        for (int i=0; i<4; i++) {
          w->writeC(smsAddr);
          w->writeC(0x90|(i<<5)|15);
        }
        break;
      case DIV_SYSTEM_T6W28:
        for (int i=0; i<4; i++) {
          w->writeC(0x30);
          w->writeC(0x90|(i<<5)|15);
          w->writeC(0x50);
          w->writeC(0x90|(i<<5)|15);
        }
        break;
      case DIV_SYSTEM_GB:
        // square 1
        w->writeC(0xb3);
        w->writeC(2|baseAddr2);
        w->writeC(0);
        w->writeC(0xb3);
        w->writeC(4|baseAddr2);
        w->writeC(0x80);

        // square 2
        w->writeC(0xb3);
        w->writeC(7|baseAddr2);
        w->writeC(0);
        w->writeC(0xb3);
        w->writeC(9|baseAddr2);
        w->writeC(0x80);

        // wave
        w->writeC(0xb3);
        w->writeC(0x0c|baseAddr2);
        w->writeC(0);
        w->writeC(0xb3);
        w->writeC(0x0e|baseAddr2);
        w->writeC(0x80);

        // noise
        w->writeC(0xb3);
        w->writeC(0x11|baseAddr2);
        w->writeC(0);
        w->writeC(0xb3);
        w->writeC(0x13|baseAddr2);
        w->writeC(0x80);
        break;
      case DIV_SYSTEM_PCE:
        for (int i=0; i<6; i++) {
          w->writeC(0xb9);
          w->writeC(0|baseAddr2);
          w->writeC(i);
          w->writeC(0xb9);
          w->writeC(4|baseAddr2);
          w->writeC(0x5f);
          w->writeC(0xb9);
          w->writeC(4|baseAddr2);
          w->writeC(0x1f);
          for (int j=0; j<32; j++) {
            w->writeC(0xb9);
            w->writeC(6|baseAddr2);
            w->writeC(0);
          }
        }
        break;
      case DIV_SYSTEM_NES:
        w->writeC(0xb4);
        w->writeC(0x15|baseAddr2);
        w->writeC(0);
        break;
      case DIV_SYSTEM_YM2151:
        for (int i=0; i<8; i++) {
          w->writeC(4|baseAddr1);
          w->writeC(0xe0+i);
          w->writeC(0xff);
          w->writeC(4|baseAddr1);
          w->writeC(0xe8+i);
          w->writeC(0xff);
          w->writeC(4|baseAddr1);
          w->writeC(0xf0+i);
          w->writeC(0xff);
          w->writeC(4|baseAddr1);
          w->writeC(0xf8+i);
          w->writeC(0xff);

          w->writeC(4|baseAddr1);
          w->writeC(0x08);
          w->writeC(i);
        }
        break;
      case DIV_SYSTEM_SEGAPCM:
      case DIV_SYSTEM_SEGAPCM_COMPAT:
        for (int i=0; i<16; i++) {
          w->writeC(0xc0);
          w->writeS((0x86|baseAddr2S)+(i<<3));
          w->writeC(3);
        }
        break;
      case DIV_SYSTEM_X1_010:
        for (int i=0; i<16; i++) {
          w->writeC(0xc8);
          w->writeS_BE(baseAddr2S+(i<<3));
          w->writeC(0);
        }
        break;
      case DIV_SYSTEM_YM2610:
      case DIV_SYSTEM_YM2610_FULL:
      case DIV_SYSTEM_YM2610B:
      case DIV_SYSTEM_YM2610_EXT:
      case DIV_SYSTEM_YM2610_FULL_EXT:
      case DIV_SYSTEM_YM2610B_EXT:
        // TODO: YM2610B channels 1 and 4 and ADPCM-B
        for (int i=0; i<2; i++) { // set SL and RR to highest
          w->writeC(8|baseAddr1);
          w->writeC(0x81+i);
          w->writeC(0xff);
          w->writeC(8|baseAddr1);
          w->writeC(0x85+i);
          w->writeC(0xff);
          w->writeC(8|baseAddr1);
          w->writeC(0x89+i);
          w->writeC(0xff);
          w->writeC(8|baseAddr1);
          w->writeC(0x8d+i);
          w->writeC(0xff);

          w->writeC(9|baseAddr1);
          w->writeC(0x81+i);
          w->writeC(0xff);
          w->writeC(9|baseAddr1);
          w->writeC(0x85+i);
          w->writeC(0xff);
          w->writeC(9|baseAddr1);
          w->writeC(0x89+i);
          w->writeC(0xff);
          w->writeC(9|baseAddr1);
          w->writeC(0x8d+i);
          w->writeC(0xff);
        }
        for (int i=0; i<2; i++) { // note off
          w->writeC(8|baseAddr1);
          w->writeC(0x28);
          w->writeC(1+i);
          w->writeC(8|baseAddr1);
          w->writeC(0x28);
          w->writeC(5+i);
        }
        
        // reset AY
        w->writeC(8|baseAddr1);
        w->writeC(7);
        w->writeC(0x3f);

        w->writeC(8|baseAddr1);
        w->writeC(8);
        w->writeC(0);

        w->writeC(8|baseAddr1);
        w->writeC(9);
        w->writeC(0);

        w->writeC(8|baseAddr1);
        w->writeC(10);
        w->writeC(0);

        // reset sample
        w->writeC(9|baseAddr1);
        w->writeC(0);
        w->writeC(0xbf);
        break;
      case DIV_SYSTEM_OPLL:
      case DIV_SYSTEM_OPLL_DRUMS:
      case DIV_SYSTEM_VRC7:
        for (int i=0; i<9; i++) {
          w->writeC(1|baseAddr1);
          w->writeC(0x20+i);
          w->writeC(0);
          w->writeC(1|baseAddr1);
          w->writeC(0x30+i);
          w->writeC(0);
          w->writeC(1|baseAddr1);
          w->writeC(0x10+i);
          w->writeC(0);
        }
        break;
      case DIV_SYSTEM_YM2203:
      case DIV_SYSTEM_YM2203_EXT:
        for (int i=0; i<3; i++) { // set SL and RR to highest
          w->writeC(5|baseAddr1);
          w->writeC(0x80+i);
          w->writeC(0xff);
          w->writeC(5|baseAddr1);
          w->writeC(0x84+i);
          w->writeC(0xff);
          w->writeC(5|baseAddr1);
          w->writeC(0x88+i);
          w->writeC(0xff);
          w->writeC(5|baseAddr1);
          w->writeC(0x8c+i);
          w->writeC(0xff);
        }
        for (int i=0; i<3; i++) { // note off
          w->writeC(5|baseAddr1);
          w->writeC(0x28);
          w->writeC(i);
        }

        // SSG
        w->writeC(5|baseAddr1);
        w->writeC(7);
        w->writeC(0x3f);

        w->writeC(5|baseAddr1);
        w->writeC(8);
        w->writeC(0);

        w->writeC(5|baseAddr1);
        w->writeC(9);
        w->writeC(0);

        w->writeC(5|baseAddr1);
        w->writeC(10);
        w->writeC(0);
        break;
      case DIV_SYSTEM_AY8910:
        w->writeC(0xa0);
        w->writeC(7|baseAddr2);
        w->writeC(0x3f);

        w->writeC(0xa0);
        w->writeC(8|baseAddr2);
        w->writeC(0);

        w->writeC(0xa0);
        w->writeC(9|baseAddr2);
        w->writeC(0);

        w->writeC(0xa0);
        w->writeC(10|baseAddr2);
        w->writeC(0);
        break;
      case DIV_SYSTEM_AY8930:
        w->writeC(0xa0);
        w->writeC(0x0d|baseAddr2);
        w->writeC(0);
        w->writeC(0xa0);
        w->writeC(0x0d|baseAddr2);
        w->writeC(0xa0);
        break;
      case DIV_SYSTEM_SAA1099:
        w->writeC(0xbd);
        w->writeC(0x1c|baseAddr2);
        w->writeC(0x02);
        w->writeC(0xbd);
        w->writeC(0x14|baseAddr2);
        w->writeC(0);
        w->writeC(0xbd);
        w->writeC(0x15|baseAddr2);
        w->writeC(0);

        for (int i=0; i<6; i++) {
          w->writeC(0xbd);
          w->writeC((0|baseAddr2)+i);
          w->writeC(0);
        }
        break;
      case DIV_SYSTEM_POKEY:
        for (int i=0; i<9; i++) {
          w->writeC(0xbb);
          w->writeC(i|baseAddr2);
          w->writeC(0);
        }
        break;
      case DIV_SYSTEM_LYNX:
        w->writeC(0x40);
        w->writeC(0x44);
        w->writeC(0xff); //stereo attenuation select
        w->writeC(0x40);
        w->writeC(0x50);
        w->writeC(0x00); //stereo channel disable
        for (int i=0; i<4; i++) { //stereo attenuation value
          w->writeC(0x40);
          w->writeC(0x40+i);
          w->writeC(0xff);
        }
        break;
      case DIV_SYSTEM_QSOUND:
        for (int i=0; i<16; i++) {
          w->writeC(0xc4);
          w->writeC(0);
          w->writeC(0);
          w->writeC(2+(i*8));
          w->writeC(0xc4);
          w->writeC(0);
          w->writeC(0);
          w->writeC(6+(i*8));
        }
        for (int i=0; i<3; i++) {
          w->writeC(0xc4);
          w->writeC(0);
          w->writeC(0);
          w->writeC(0xcd+(i*4));
          w->writeC(0xc4);
          w->writeC(0x00);
          w->writeC(0x01);
          w->writeC(0xd6+i);
        }
        break;
      case DIV_SYSTEM_ES5506:
        for (int i=0; i<32; i++) {
          for (int b=0; b<4; b++) {
            w->writeC(0xbe);
            w->writeC((0xf<<2)+b);
            w->writeC(i);
          }
          unsigned int init_cr=0x0303;
          for (int b=0; b<4; b++) {
            w->writeC(0xbe);
            w->writeC(b);
            w->writeC(init_cr>>(24-(b<<3)));
          }
          for (int r=1; r<11; r++) {
            for (int b=0; b<4; b++) {
              w->writeC(0xbe);
              w->writeC((r<<2)+b);
              w->writeC(((r==7 || r==9) && b&2)?0xff:0);
            }
          }
          for (int b=0; b<4; b++) {
            w->writeC(0xbe);
            w->writeC((0xf<<2)+b);
            w->writeC(0x20|i);
          }
          for (int r=1; r<10; r++) {
            for (int b=0; b<4; b++) {
              w->writeC(0xbe);
              w->writeC((r<<2)+b);
              w->writeC(0);
            }
          }
        }
        break;
      case DIV_SYSTEM_OPL:
      case DIV_SYSTEM_OPL_DRUMS:
        // disable envelope
        for (int i=0; i<6; i++) {
          w->writeC(0x0b|baseAddr1);
          w->writeC(0x80+i);
          w->writeC(0x0f);
          w->writeC(0x0b|baseAddr1);
          w->writeC(0x88+i);
          w->writeC(0x0f);
          w->writeC(0x0b|baseAddr1);
          w->writeC(0x90+i);
          w->writeC(0x0f);
        }
        // key off + freq reset
        for (int i=0; i<9; i++) {
          w->writeC(0x0b|baseAddr1);
          w->writeC(0xa0+i);
          w->writeC(0);
          w->writeC(0x0b|baseAddr1);
          w->writeC(0xb0+i);
          w->writeC(0);
        }
        break;
      case DIV_SYSTEM_Y8950:
      case DIV_SYSTEM_Y8950_DRUMS:
        // disable envelope
        for (int i=0; i<6; i++) {
          w->writeC(0x0c|baseAddr1);
          w->writeC(0x80+i);
          w->writeC(0x0f);
          w->writeC(0x0c|baseAddr1);
          w->writeC(0x88+i);
          w->writeC(0x0f);
          w->writeC(0x0c|baseAddr1);
          w->writeC(0x90+i);
          w->writeC(0x0f);
        }
        // key off + freq reset
        for (int i=0; i<9; i++) {
          w->writeC(0x0c|baseAddr1);
          w->writeC(0xa0+i);
          w->writeC(0);
          w->writeC(0x0c|baseAddr1);
          w->writeC(0xb0+i);
          w->writeC(0);
        }
        // TODO: ADPCM
        break;
      case DIV_SYSTEM_OPL2:
      case DIV_SYSTEM_OPL2_DRUMS:
        // disable envelope
        for (int i=0; i<6; i++) {
          w->writeC(0x0a|baseAddr1);
          w->writeC(0x80+i);
          w->writeC(0x0f);
          w->writeC(0x0a|baseAddr1);
          w->writeC(0x88+i);
          w->writeC(0x0f);
          w->writeC(0x0a|baseAddr1);
          w->writeC(0x90+i);
          w->writeC(0x0f);
        }
        // key off + freq reset
        for (int i=0; i<9; i++) {
          w->writeC(0x0a|baseAddr1);
          w->writeC(0xa0+i);
          w->writeC(0);
          w->writeC(0x0a|baseAddr1);
          w->writeC(0xb0+i);
          w->writeC(0);
        }
        break;
      case DIV_SYSTEM_OPL3:
      case DIV_SYSTEM_OPL3_DRUMS:
        // disable envelope
        for (int i=0; i<6; i++) {
          w->writeC(0x0e|baseAddr1);
          w->writeC(0x80+i);
          w->writeC(0x0f);
          w->writeC(0x0e|baseAddr1);
          w->writeC(0x88+i);
          w->writeC(0x0f);
          w->writeC(0x0e|baseAddr1);
          w->writeC(0x90+i);
          w->writeC(0x0f);
          w->writeC(0x0f|baseAddr1);
          w->writeC(0x80+i);
          w->writeC(0x0f);
          w->writeC(0x0f|baseAddr1);
          w->writeC(0x88+i);
          w->writeC(0x0f);
          w->writeC(0x0f|baseAddr1);
          w->writeC(0x90+i);
          w->writeC(0x0f);
        }
        // key off + freq reset
        for (int i=0; i<9; i++) {
          w->writeC(0x0e|baseAddr1);
          w->writeC(0xa0+i);
          w->writeC(0);
          w->writeC(0x0e|baseAddr1);
          w->writeC(0xb0+i);
          w->writeC(0);
          w->writeC(0x0f|baseAddr1);
          w->writeC(0xa0+i);
          w->writeC(0);
          w->writeC(0x0f|baseAddr1);
          w->writeC(0xb0+i);
          w->writeC(0);
        }
        // reset 4-op
        w->writeC(0x0f|baseAddr1);
        w->writeC(0x04);
        w->writeC(0x00);
        break;
      case DIV_SYSTEM_SCC:
      case DIV_SYSTEM_SCC_PLUS:
        w->writeC(0xd2);
        w->writeC(baseAddr2|3);
        w->writeC(0);
        w->writeC(0);
        break;
      case DIV_SYSTEM_RF5C68:
        w->writeC(rf5c68Addr);
        w->writeC(7);
        w->writeC(0);
        w->writeC(rf5c68Addr);
        w->writeC(8);
        w->writeC(0xff);
        break;
      case DIV_SYSTEM_MSM6295:
        w->writeC(0xb8); // disable all channels
        w->writeC(baseAddr2|0);
        w->writeC(0x78);
        w->writeC(0xb8); // select rate
        w->writeC(baseAddr2|12);
        w->writeC(1);
        break;
      case DIV_SYSTEM_VBOY:
        // isn't it amazing when a chip has a built-in reset command?
        w->writeC(0xc7);
        w->writeS_BE(baseAddr2S|(0x580>>2));
        w->writeC(0xff);
        break;
      case DIV_SYSTEM_GA20:
        for (int i=0; i<3; i++) {
          w->writeC(0xbf); // mute
          w->writeC((baseAddr2|5)+(i*8));
          w->writeC(0);
          w->writeC(0xbf); // keyoff
          w->writeC((baseAddr2|6)+(i*8));
          w->writeC(0);
        }
        break;
      default:
        break;
    }
  }
  if (write.addr>=0xffff0000) { // Furnace special command
    if (!directStream) {
      unsigned char streamID=streamOff+((write.addr&0xff00)>>8);
      logD("writing stream command %x:%x with stream ID %d",write.addr,write.val,streamID);
      switch (write.addr&0xff) {
        case 0: // play sample
          if (write.val<(unsigned int)song.sampleLen) {
            if (playingSample[streamID]!=(int)write.val) {
              pendingFreq[streamID]=write.val;
            } else {
              DivSample* sample=song.sample[write.val];
              w->writeC(0x95);
              w->writeC(streamID);
              w->writeS(write.val); // sample number
              w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags
              if (sample->isLoopable() && !sampleDir[streamID]) {
                loopTimer[streamID]=sample->length8;
                loopSample[streamID]=write.val;
              }
              playingSample[streamID]=write.val;
            }
          }
          break;
        case 1: // set sample freq
          w->writeC(0x92);
          w->writeC(streamID);
          w->writeI(write.val);
          loopFreq[streamID]=write.val;
          if (pendingFreq[streamID]!=-1) {
            DivSample* sample=song.sample[pendingFreq[streamID]];
            w->writeC(0x95);
            w->writeC(streamID);
            w->writeS(pendingFreq[streamID]); // sample number
            w->writeC((sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)==0 && sample->isLoopable())|(sampleDir[streamID]?0x10:0)); // flags
            if (sample->isLoopable() && !sampleDir[streamID]) {
              loopTimer[streamID]=sample->length8;
              loopSample[streamID]=pendingFreq[streamID];
            }
            playingSample[streamID]=pendingFreq[streamID];
            pendingFreq[streamID]=-1;
          }
          break;
        case 2: // stop sample
          w->writeC(0x94);
          w->writeC(streamID);
          loopSample[streamID]=-1;
          playingSample[streamID]=-1;
          pendingFreq[streamID]=-1;
          break;
        case 3: // set sample direction
          sampleDir[streamID]=write.val;
          break;
      }
    }
    return;
  }
  switch (sys) {
    case DIV_SYSTEM_YM2612:
    case DIV_SYSTEM_YM2612_EXT:
    case DIV_SYSTEM_YM2612_DUALPCM:
    case DIV_SYSTEM_YM2612_DUALPCM_EXT:
      switch (write.addr>>8) {
        case 0: // port 0
          w->writeC(2|baseAddr1);
          w->writeC(write.addr&0xff);
          w->writeC(write.val);
          break;
        case 1: // port 1
          w->writeC(3|baseAddr1);
          w->writeC(write.addr&0xff);
          w->writeC(write.val);
          break;
        case 2: // PSG
          w->writeC(smsAddr);
          w->writeC(write.val);
          break;
      }
      break;
    case DIV_SYSTEM_SMS:
      if (write.addr==1) {
        w->writeC(ggAddr);
      } else {
        w->writeC(smsAddr);
      }
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_T6W28:
      if (write.addr) {
        w->writeC(0x30);
      } else {
        w->writeC(0x50);
      }
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_GB:
      w->writeC(0xb3);
      w->writeC(baseAddr2|((write.addr-16)&0xff));
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_PCE:
      w->writeC(0xb9);
      w->writeC(baseAddr2|(write.addr&0xff));
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_NES:
      w->writeC(0xb4);
      w->writeC(baseAddr2|(write.addr&0xff));
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_FDS: // yeah
      w->writeC(0xb4);
      if ((write.addr&0xff)==0x23) {
        w->writeC(baseAddr2|0x3f);
      } else if ((write.addr&0xff)>=0x80) {
        w->writeC(baseAddr2|(0x20+(write.addr&0x7f)));
      } else {
        w->writeC(baseAddr2|(write.addr&0xff));
      }
      
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_YM2151:
      w->writeC(4|baseAddr1);
      w->writeC(write.addr&0xff);
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_SEGAPCM:
    case DIV_SYSTEM_SEGAPCM_COMPAT:
      w->writeC(0xc0);
      w->writeS(baseAddr2S|(write.addr&0xffff));
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_X1_010:
      w->writeC(0xc8);
      w->writeS_BE(baseAddr2S|(write.addr&0x1fff));
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_YM2610:
    case DIV_SYSTEM_YM2610_FULL:
    case DIV_SYSTEM_YM2610B:
    case DIV_SYSTEM_YM2610_EXT:
    case DIV_SYSTEM_YM2610_FULL_EXT:
    case DIV_SYSTEM_YM2610B_EXT:
      switch (write.addr>>8) {
        case 0: // port 0
          w->writeC(8|baseAddr1);
          w->writeC(write.addr&0xff);
          w->writeC(write.val);
          break;
        case 1: // port 1
          w->writeC(9|baseAddr1);
          w->writeC(write.addr&0xff);
          w->writeC(write.val);
          break;
      }
      break;
    case DIV_SYSTEM_YM2203:
    case DIV_SYSTEM_YM2203_EXT:
      w->writeC(5|baseAddr1);
      w->writeC(write.addr&0xff);
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_YM2608:
    case DIV_SYSTEM_YM2608_EXT:
      switch (write.addr>>8) {
        case 0: // port 0
          w->writeC(6|baseAddr1);
          w->writeC(write.addr&0xff);
          w->writeC(write.val);
          break;
        case 1: // port 1
          w->writeC(7|baseAddr1);
          w->writeC(write.addr&0xff);
          w->writeC(write.val);
          break;
      }
      break;
    case DIV_SYSTEM_OPLL:
    case DIV_SYSTEM_OPLL_DRUMS:
    case DIV_SYSTEM_VRC7:
      w->writeC(1|baseAddr1);
      w->writeC(write.addr&0xff);
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_AY8910:
    case DIV_SYSTEM_AY8930:
      w->writeC(0xa0);
      w->writeC(baseAddr2|(write.addr&0xff));
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_SAA1099:
      w->writeC(0xbd);
      w->writeC(baseAddr2|(write.addr&0xff));
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_POKEY:
      w->writeC(0xbb);
      w->writeC(baseAddr2|(write.addr&0x0f));
      w->writeC(write.val&0xff);
      break;
    case DIV_SYSTEM_LYNX:
      w->writeC(0x40);
      w->writeC(write.addr&0xff);
      w->writeC(write.val&0xff);
      break;
    case DIV_SYSTEM_QSOUND:
      w->writeC(0xc4);
      w->writeC((write.val>>8)&0xff);
      w->writeC(write.val&0xff);
      w->writeC(write.addr&0xff);
      break;
    case DIV_SYSTEM_SWAN:
      if ((write.addr&0x7f)<0x40) {
        w->writeC(0xbc);
        w->writeC(baseAddr2|(write.addr&0x3f));
        w->writeC(write.val&0xff);
      } else {
        // (Wave) RAM write
        w->writeC(0xc6);
        w->writeS_BE(baseAddr2S|(write.addr&0x3f));
        w->writeC(write.val&0xff);
      }
      break;
    case DIV_SYSTEM_ES5506:
      w->writeC(0xbe);
      w->writeC(write.addr&0xff);
      w->writeC(write.val&0xff);
      break;
    case DIV_SYSTEM_VBOY:
      w->writeC(0xc7);
      w->writeS_BE(baseAddr2S|(write.addr>>2));
      w->writeC(write.val&0xff);
      break;
    case DIV_SYSTEM_OPL:
    case DIV_SYSTEM_OPL_DRUMS:
      w->writeC(0x0b|baseAddr1);
      w->writeC(write.addr&0xff);
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_Y8950:
    case DIV_SYSTEM_Y8950_DRUMS:
      w->writeC(0x0c|baseAddr1);
      w->writeC(write.addr&0xff);
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_OPL2:
    case DIV_SYSTEM_OPL2_DRUMS:
      w->writeC(0x0a|baseAddr1);
      w->writeC(write.addr&0xff);
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_OPL3:
    case DIV_SYSTEM_OPL3_DRUMS:
      switch (write.addr>>8) {
        case 0: // port 0
          w->writeC(0x0e|baseAddr1);
          w->writeC(write.addr&0xff);
          w->writeC(write.val);
          break;
        case 1: // port 1
          w->writeC(0x0f|baseAddr1);
          w->writeC(write.addr&0xff);
          w->writeC(write.val);
          break;
      }
      break;
    case DIV_SYSTEM_SCC:
      if (write.addr<0x80) {
        w->writeC(0xd2);
        w->writeC(baseAddr2|0);
        w->writeC(write.addr&0x7f);
        w->writeC(write.val&0xff);
      } else if (write.addr<0x8a) {
        w->writeC(0xd2);
        w->writeC(baseAddr2|1);
        w->writeC((write.addr-0x80)&0x7f);
        w->writeC(write.val&0xff);
      } else if (write.addr<0x8f) {
        w->writeC(0xd2);
        w->writeC(baseAddr2|2);
        w->writeC((write.addr-0x8a)&0x7f);
        w->writeC(write.val&0xff);
      } else if (write.addr<0x90) {
        w->writeC(0xd2);
        w->writeC(baseAddr2|3);
        w->writeC((write.addr-0x8f)&0x7f);
        w->writeC(write.val&0xff);
      } else if (write.addr>=0xe0) {
        w->writeC(0xd2);
        w->writeC(baseAddr2|5);
        w->writeC((write.addr-0xe0)&0x7f);
        w->writeC(write.val&0xff);
      } else {
        logW("SCC: writing to unmapped address %.2x!",write.addr);
      }
      break;
    case DIV_SYSTEM_SCC_PLUS:
      if (write.addr<0x80) {
        w->writeC(0xd2);
        w->writeC(baseAddr2|0);
        w->writeC(write.addr&0x7f);
        w->writeC(write.val&0xff);
      } else if (write.addr<0xa0) {
        w->writeC(0xd2);
        w->writeC(baseAddr2|4);
        w->writeC(write.addr);
        w->writeC(write.val&0xff);
      } else if (write.addr<0xaa) {
        w->writeC(0xd2);
        w->writeC(baseAddr2|1);
        w->writeC((write.addr-0xa0)&0x7f);
        w->writeC(write.val&0xff);
      } else if (write.addr<0xaf) {
        w->writeC(0xd2);
        w->writeC(baseAddr2|2);
        w->writeC((write.addr-0xaa)&0x7f);
        w->writeC(write.val&0xff);
      } else if (write.addr<0xb0) {
        w->writeC(0xd2);
        w->writeC(baseAddr2|3);
        w->writeC((write.addr-0xaf)&0x7f);
        w->writeC(write.val&0xff);
      } else if (write.addr>=0xe0) {
        w->writeC(0xd2);
        w->writeC(baseAddr2|5);
        w->writeC((write.addr-0xe0)&0x7f);
        w->writeC(write.val&0xff);
      } else {
        logW("SCC+: writing to unmapped address %.2x!",write.addr);
      }
      break;
    case DIV_SYSTEM_YMZ280B:
      w->writeC(0x0d|baseAddr1);
      w->writeC(write.addr&0xff);
      w->writeC(write.val&0xff);
      break;
    case DIV_SYSTEM_RF5C68:
      w->writeC(rf5c68Addr);
      w->writeC(write.addr&0xff);
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_MSM6295:
      w->writeC(0xb8);
      w->writeC(baseAddr2|(write.addr&0x7f));
      w->writeC(write.val);
      break;
    case DIV_SYSTEM_GA20:
      w->writeC(0xbf);
      w->writeC(baseAddr2|(write.addr&0x7f));
      w->writeC(write.val);
      break;
    default:
      logW("write not handled!");
      break;
  }
}

#define CHIP_VOL(_id,_mult) { \
  double _vol=fabs((float)song.systemVol[i])*256.0*_mult; \
  if (_vol<0.0) _vol=0.0; \
  if (_vol>32767.0) _vol=32767.0; \
  chipVolSum+=(unsigned int)(_vol/_mult); \
  chipAccounting++; \
  chipVol.push_back((_id)|(0x80000000)|(((unsigned int)_vol)<<16)); \
}

#define CHIP_VOL_SECOND(_id,_mult) { \
  double _vol=fabs((float)song.systemVol[i])*256.0*_mult; \
  if (_vol<0.0) _vol=0.0; \
  if (_vol>32767.0) _vol=32767.0; \
  chipVolSum+=(unsigned int)(_vol/_mult); \
  chipAccounting++; \
  chipVol.push_back((_id)|(0x80000100)|(((unsigned int)_vol)<<16)); \
}

SafeWriter* DivEngine::saveVGM(bool* sysToExport, bool loop, int version, bool patternHints, bool directStream, int trailingTicks) {
  if (version<0x150) {
    lastError="VGM version is too low";
    return NULL;
  }
  stop();
  repeatPattern=false;
  setOrder(0);
  BUSY_BEGIN_SOFT;
  double origRate=got.rate;
  got.rate=44100;
  // determine loop point
  int loopOrder=0;
  int loopRow=0;
  int loopEnd=0;
  walkSong(loopOrder,loopRow,loopEnd);
  logI("loop point: %d %d",loopOrder,loopRow);
  warnings="";

  curOrder=0;
  freelance=false;
  playing=false;
  extValuePresent=false;
  remainingLoops=-1;

  // play the song ourselves
  bool done=false;
  int writeCount=0;

  int gd3Off=0;

  int hasSN=0;
  int snNoiseConfig=9;
  int snNoiseSize=16;
  int snFlags=0;
  int hasOPLL=0;
  int hasOPN2=0;
  int hasOPM=0;
  int hasSegaPCM=0;
  int segaPCMOffset=0xf8000d;
  int hasRFC=0;
  int hasOPN=0;
  int hasOPNA=0;
  int hasOPNB=0;
  int hasOPL2=0;
  int hasOPL=0;
  int hasY8950=0;
  int hasOPL3=0;
  int hasOPL4=0;
  int hasOPX=0;
  int hasZ280=0;
  int hasRFC1=0;
  int hasPWM=0;
  int hasAY=0;
  int ayConfig=0;
  int ayFlags=0;
  int hasGB=0;
  int hasNES=0;
  int hasMultiPCM=0;
  int hasuPD7759=0;
  int hasOKIM6258=0;
  int hasK054539=0;
  int hasOKIM6295=0;
  int hasK051649=0;
  int hasPCE=0;
  int hasNamco=0;
  int hasK053260=0;
  int hasPOKEY=0;
  int hasQSound=0;
  int hasSCSP=0;
  int hasSwan=0;
  int hasVSU=0;
  int hasSAA=0;
  int hasES5503=0;
  int hasES5505=0;
  int hasX1=0;
  int hasC352=0;
  int hasGA20=0;
  int hasLynx=0;

  int howManyChips=0;
  int chipVolSum=0;
  int chipAccounting=0;

  int loopPos=-1;
  int loopTickSong=-1;
  int songTick=0;

  unsigned int sampleOff8[256];
  unsigned int sampleOffSegaPCM[256];

  SafeWriter* w=new SafeWriter;
  w->init();

  // write header
  w->write("Vgm ",4);
  w->writeI(0); // will be written later
  w->writeI(version);

  bool willExport[DIV_MAX_CHIPS];
  bool isSecond[DIV_MAX_CHIPS];
  int streamIDs[DIV_MAX_CHIPS];
  double loopTimer[DIV_MAX_CHANS];
  double loopFreq[DIV_MAX_CHANS];
  int loopSample[DIV_MAX_CHANS];
  bool sampleDir[DIV_MAX_CHANS];
  int pendingFreq[DIV_MAX_CHANS];
  int playingSample[DIV_MAX_CHANS];
  std::vector<unsigned int> chipVol;
  std::vector<DivDelayedWrite> delayedWrites[DIV_MAX_CHIPS];
  std::vector<std::pair<int,DivDelayedWrite>> sortedWrites;
  std::vector<size_t> tickPos;
  std::vector<int> tickSample;

  bool trailing=false;
  bool beenOneLoopAlready=false;
  bool mayWriteRate=(fmod(curSubSong->hz,1.0)<0.00001 || fmod(curSubSong->hz,1.0)>0.99999);
  int countDown=MAX(0,trailingTicks)+1;

  for (int i=0; i<DIV_MAX_CHANS; i++) {
    loopTimer[i]=0;
    loopFreq[i]=0;
    loopSample[i]=-1;
    pendingFreq[i]=-1;
    playingSample[i]=-1;
    sampleDir[i]=false;
  }

  bool writeDACSamples=false;
  bool writeNESSamples=false;
  bool writePCESamples=false;
  DivDispatch* writeADPCM_OPNA[2]={NULL,NULL};
  DivDispatch* writeADPCM_OPNB[2]={NULL,NULL};
  DivDispatch* writeADPCM_Y8950[2]={NULL,NULL};
  DivDispatch* writeSegaPCM[2]={NULL,NULL};
  DivDispatch* writeX1010[2]={NULL,NULL};
  DivDispatch* writeQSound[2]={NULL,NULL};
  DivDispatch* writeES5506[2]={NULL,NULL};
  DivDispatch* writeZ280[2]={NULL,NULL};
  DivDispatch* writeRF5C68[2]={NULL,NULL};
  DivDispatch* writeMSM6295[2]={NULL,NULL};
  DivDispatch* writeGA20[2]={NULL,NULL};

  for (int i=0; i<song.systemLen; i++) {
    willExport[i]=false;
    isSecond[i]=false;
    streamIDs[i]=0;
    if (sysToExport!=NULL) {
      if (!sysToExport[i]) continue;
    }
    if (minVGMVersion(song.system[i])>version) continue;
    switch (song.system[i]) {
      case DIV_SYSTEM_SMS:
        if (!hasSN) {
          hasSN=disCont[i].dispatch->chipClock;
          CHIP_VOL(0,4.0);
          willExport[i]=true;
          switch (song.systemFlags[i].getInt("chipType",0)) {
            case 1: // real SN
              snNoiseConfig=3;
              snNoiseSize=15;
              break;
            case 2: // real SN atari bass (seemingly unsupported)
              snNoiseConfig=3;
              snNoiseSize=15;
              break;
            default: // Sega VDP
              snNoiseConfig=9;
              snNoiseSize=16;
              break;
          }
        } else if (!(hasSN&0x40000000)) {
          isSecond[i]=true;
          willExport[i]=true;
          CHIP_VOL_SECOND(0,4.0);
          hasSN|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_GB:
        if (!hasGB) {
          hasGB=disCont[i].dispatch->chipClock;
          CHIP_VOL(19,0.75);
          willExport[i]=true;
        } else if (!(hasGB&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(19,0.75);
          willExport[i]=true;
          hasGB|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_PCE:
        if (!hasPCE) {
          hasPCE=disCont[i].dispatch->chipClock;
          CHIP_VOL(27,0.98);
          willExport[i]=true;
          writePCESamples=true;
        } else if (!(hasPCE&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL(27,0.98);
          willExport[i]=true;
          hasPCE|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_NES:
        if (!hasNES) {
          hasNES=disCont[i].dispatch->chipClock;
          CHIP_VOL(20,1.7);
          willExport[i]=true;
          writeNESSamples=true;
        } else if (!(hasNES&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(20,1.7);
          willExport[i]=true;
          hasNES|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_SEGAPCM:
      case DIV_SYSTEM_SEGAPCM_COMPAT:
        if (!hasSegaPCM) {
          hasSegaPCM=4000000;
          CHIP_VOL(4,0.67);
          willExport[i]=true;
          writeSegaPCM[0]=disCont[i].dispatch;
        } else if (!(hasSegaPCM&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(4,0.67);
          willExport[i]=true;
          writeSegaPCM[1]=disCont[i].dispatch;
          hasSegaPCM|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_X1_010:
        if (!hasX1) {
          hasX1=disCont[i].dispatch->chipClock;
          CHIP_VOL(38,2.0);
          willExport[i]=true;
          writeX1010[0]=disCont[i].dispatch;
        } else if (!(hasX1&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(38,2.0);
          willExport[i]=true;
          writeX1010[1]=disCont[i].dispatch;
          hasX1|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_YM2610:
      case DIV_SYSTEM_YM2610_FULL:
      case DIV_SYSTEM_YM2610B:
      case DIV_SYSTEM_YM2610_EXT:
      case DIV_SYSTEM_YM2610_FULL_EXT:
      case DIV_SYSTEM_YM2610B_EXT:
        if (!hasOPNB) {
          hasOPNB=disCont[i].dispatch->chipClock;
          CHIP_VOL(8,1.0);
          CHIP_VOL(0x88,1.25);
          willExport[i]=true;
          writeADPCM_OPNB[0]=disCont[i].dispatch;
        } else if (!(hasOPNB&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(8,1.0);
          CHIP_VOL_SECOND(0x88,1.25);
          willExport[i]=true;
          writeADPCM_OPNB[1]=disCont[i].dispatch;
          hasOPNB|=0x40000000;
          howManyChips++;
        }
        if (((song.system[i]==DIV_SYSTEM_YM2610B) || (song.system[i]==DIV_SYSTEM_YM2610B_EXT)) && (!(hasOPNB&0x80000000))) { // YM2610B flag
          hasOPNB|=0x80000000;
        }
        break;
      case DIV_SYSTEM_AY8910:
      case DIV_SYSTEM_AY8930: {
        if (!hasAY) {
          bool hasClockDivider=false; // Configurable clock divider
          bool hasStereo=true; // Stereo
          hasAY=disCont[i].dispatch->chipClock;
          ayFlags=1;
          if (song.system[i]==DIV_SYSTEM_AY8930) { // AY8930
            ayConfig=0x03;
            hasClockDivider=true;
          } else {
            switch (song.systemFlags[i].getInt("chipType",0)) {
              case 1: // YM2149
                ayConfig=0x10;
                hasClockDivider=true;
                break;
              case 2: // Sunsoft 5B
                ayConfig=0x10;
                ayFlags|=0x12; // Clock internally divided, Single sound output
                hasStereo=false; // due to above, can't be per-channel stereo configurable
                break;
              case 3: // AY8914
                ayConfig=0x04;
                break;
              default: // AY8910
                ayConfig=0x00;
                break;
            }
          }
          if (hasClockDivider && song.systemFlags[i].getBool("halfClock",false)) {
            ayFlags|=0x10;
          }
          if (hasStereo && song.systemFlags[i].getBool("stereo",false)) {
            ayFlags|=0x80;
          }
          CHIP_VOL(18,1.0);
          willExport[i]=true;
        } else if (!(hasAY&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(18,1.0);
          willExport[i]=true;
          hasAY|=0x40000000;
          howManyChips++;
        }
        break;
      }
      case DIV_SYSTEM_SAA1099:
        if (!hasSAA) {
          hasSAA=disCont[i].dispatch->chipClock;
          CHIP_VOL(35,1.0);
          willExport[i]=true;
        } else if (!(hasSAA&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(35,1.0);
          willExport[i]=true;
          hasSAA|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_YM2612:
      case DIV_SYSTEM_YM2612_EXT:
      case DIV_SYSTEM_YM2612_DUALPCM:
      case DIV_SYSTEM_YM2612_DUALPCM_EXT:
        if (!hasOPN2) {
          hasOPN2=disCont[i].dispatch->chipClock;
          CHIP_VOL(2,1.6);
          willExport[i]=true;
          writeDACSamples=true;
        } else if (!(hasOPN2&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(2,1.6);
          willExport[i]=true;
          hasOPN2|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_YM2151:
        if (!hasOPM) {
          hasOPM=disCont[i].dispatch->chipClock;
          CHIP_VOL(3,1.0);
          willExport[i]=true;
        } else if (!(hasOPM&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(3,1.0);
          willExport[i]=true;
          hasOPM|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_YM2203:
      case DIV_SYSTEM_YM2203_EXT:
        if (!hasOPN) {
          hasOPN=disCont[i].dispatch->chipClock;
          willExport[i]=true;
          CHIP_VOL(6,1.0);
          CHIP_VOL(0x86,1.7);
          writeDACSamples=true;
        } else if (!(hasOPN&0x40000000)) {
          isSecond[i]=true;
          willExport[i]=true;
          CHIP_VOL_SECOND(6,1.0);
          CHIP_VOL_SECOND(0x86,1.7);
          hasOPN|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_YM2608:
      case DIV_SYSTEM_YM2608_EXT:
        if (!hasOPNA) {
          hasOPNA=disCont[i].dispatch->chipClock;
          CHIP_VOL(7,1.0);
          CHIP_VOL(0x87,1.3);
          willExport[i]=true;
          writeADPCM_OPNA[0]=disCont[i].dispatch;
        } else if (!(hasOPNA&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(7,1.0);
          CHIP_VOL_SECOND(0x87,1.3);
          willExport[i]=true;
          writeADPCM_OPNA[1]=disCont[i].dispatch;
          hasOPNA|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_OPLL:
      case DIV_SYSTEM_OPLL_DRUMS:
      case DIV_SYSTEM_VRC7:
        if (!hasOPLL) {
          hasOPLL=disCont[i].dispatch->chipClock;
          CHIP_VOL(1,3.2);
          willExport[i]=true;
        } else if (!(hasOPLL&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(1,3.2);
          willExport[i]=true;
          hasOPLL|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_FDS:
        if (!hasNES) {
          hasNES=0x80000000|disCont[i].dispatch->chipClock;
          willExport[i]=true;
        } else if (!(hasNES&0x80000000)) {
          hasNES|=0x80000000;
          willExport[i]=true;
        } else if (!(hasNES&0x40000000)) {
          isSecond[i]=true;
          willExport[i]=true;
          hasNES|=0xc0000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_POKEY:
        if (!hasPOKEY) {
          hasPOKEY=disCont[i].dispatch->chipClock;
          CHIP_VOL(30,0.8);
          willExport[i]=true;
        } else if (!(hasPOKEY&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(30,0.8);
          willExport[i]=true;
          hasPOKEY|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_LYNX:
        if (!hasLynx) {
          hasLynx=disCont[i].dispatch->chipClock;
          willExport[i]=true;
        } else if (!(hasLynx&0x40000000)) {
          isSecond[i]=true;
          willExport[i]=true;
          hasLynx|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_QSOUND:
        if (!hasQSound) {
          // could set chipClock to 4000000 here for compatibility
          // However I think it it not necessary because old VGM players will still
          // not be able to handle the 64kb sample bank trick
          hasQSound=disCont[i].dispatch->chipClock;
          CHIP_VOL(31,1.0);
          willExport[i]=true;
          writeQSound[0]=disCont[i].dispatch;
        } else if (!(hasQSound&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(31,1.0);
          willExport[i]=false;
          writeQSound[1]=disCont[i].dispatch;
          addWarning("dual QSound is not supported by the VGM format");
        }
        break;
      case DIV_SYSTEM_SWAN:
        if (!hasSwan) {
          hasSwan=disCont[i].dispatch->chipClock;
          CHIP_VOL(33,1.0);
          willExport[i]=true;
          // funny enough, VGM doesn't have support for WSC's sound DMA by design
          // so DAC stream it goes
          // since WS has the same PCM format as YM2612 DAC, I can just reuse this flag
          writeDACSamples=true;
        } else if (!(hasSwan&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(33,1.0);
          willExport[i]=true;
          hasSwan|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_ES5506:
        if (!hasES5505) {
          // VGM identifies ES5506 if highest bit sets, otherwise ES5505
          hasES5505=0x80000000|disCont[i].dispatch->chipClock;
          willExport[i]=true;
          writeES5506[0]=disCont[i].dispatch;
        } else if (!(hasES5505&0x40000000)) {
          isSecond[i]=true;
          willExport[i]=false;
          hasES5505|=0xc0000000;
          writeES5506[1]=disCont[i].dispatch;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_VBOY:
        if (!hasVSU) {
          hasVSU=disCont[i].dispatch->chipClock;
          CHIP_VOL(34,0.72);
          willExport[i]=true;
        } else if (!(hasVSU&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(34,0.72);
          willExport[i]=true;
          hasVSU|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_OPL:
      case DIV_SYSTEM_OPL_DRUMS:
        if (!hasOPL) {
          hasOPL=disCont[i].dispatch->chipClock;
          CHIP_VOL(9,1.0);
          willExport[i]=true;
        } else if (!(hasOPL&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(9,1.0);
          willExport[i]=true;
          hasOPL|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_Y8950:
      case DIV_SYSTEM_Y8950_DRUMS:
        if (!hasY8950) {
          hasY8950=disCont[i].dispatch->chipClock;
          CHIP_VOL(11,1.0);
          willExport[i]=true;
          writeADPCM_Y8950[0]=disCont[i].dispatch;
        } else if (!(hasY8950&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(11,1.0);
          willExport[i]=true;
          writeADPCM_Y8950[1]=disCont[i].dispatch;
          hasY8950|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_OPL2:
      case DIV_SYSTEM_OPL2_DRUMS:
        if (!hasOPL2) {
          hasOPL2=disCont[i].dispatch->chipClock;
          CHIP_VOL(10,1.0);
          willExport[i]=true;
        } else if (!(hasOPL2&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(10,1.0);
          willExport[i]=true;
          hasOPL2|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_OPL3:
      case DIV_SYSTEM_OPL3_DRUMS:
        if (!hasOPL3) {
          hasOPL3=disCont[i].dispatch->chipClock;
          CHIP_VOL(12,1.0);
          willExport[i]=true;
        } else if (!(hasOPL3&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(12,1.0);
          willExport[i]=true;
          hasOPL3|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_SCC:
      case DIV_SYSTEM_SCC_PLUS:
        if (!hasK051649) {
          hasK051649=disCont[i].dispatch->chipClock;
          if (song.system[i]==DIV_SYSTEM_SCC_PLUS) {
            hasK051649|=0x80000000;
          }
          CHIP_VOL(25,1.0);
          willExport[i]=true;
        } else if (!(hasK051649&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(25,1.0);
          willExport[i]=true;
          hasK051649|=0x40000000;
          if (song.system[i]==DIV_SYSTEM_SCC_PLUS) {
            hasK051649|=0x80000000;
          }
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_YMZ280B:
        if (!hasZ280) {
          hasZ280=disCont[i].dispatch->chipClock;
          CHIP_VOL(15,0.72);
          willExport[i]=true;
          writeZ280[0]=disCont[i].dispatch;
        } else if (!(hasZ280&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(15,0.72);
          willExport[i]=true;
          writeZ280[1]=disCont[i].dispatch;
          hasZ280|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_RF5C68:
        // here's the dumb part: VGM thinks RF5C68 and RF5C164 are different
        // chips even though the only difference is the output resolution
        // these system types are currently handled by reusing isSecond flag
        // also this system is not dual-able
        if (song.systemFlags[i].getInt("chipType",0)==1) {
          if (!hasRFC1) {
            hasRFC1=disCont[i].dispatch->chipClock;
            isSecond[i]=true;
            CHIP_VOL(16,1.6);
            willExport[i]=true;
            writeRF5C68[1]=disCont[i].dispatch;
          }
        } else if (!hasRFC) {
          hasRFC=disCont[i].dispatch->chipClock;
          CHIP_VOL(5,1.6);
          willExport[i]=true;
          writeRF5C68[0]=disCont[i].dispatch;
        }
        break;
      case DIV_SYSTEM_MSM6295:
        if (!hasOKIM6295) {
          hasOKIM6295=disCont[i].dispatch->chipClock;
          CHIP_VOL(24,1.0);
          willExport[i]=true;
          writeMSM6295[0]=disCont[i].dispatch;
        } else if (!(hasOKIM6295&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(24,1.0);
          willExport[i]=true;
          writeMSM6295[1]=disCont[i].dispatch;
          hasOKIM6295|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_GA20:
        if (!hasGA20) {
          hasGA20=disCont[i].dispatch->chipClock;
          CHIP_VOL(40,0.4);
          willExport[i]=true;
          writeGA20[0]=disCont[i].dispatch;
        } else if (!(hasGA20&0x40000000)) {
          isSecond[i]=true;
          CHIP_VOL_SECOND(40,0.4);
          willExport[i]=true;
          writeGA20[1]=disCont[i].dispatch;
          hasGA20|=0x40000000;
          howManyChips++;
        }
        break;
      case DIV_SYSTEM_T6W28:
        if (!hasSN) {
          hasSN=0xc0000000|disCont[i].dispatch->chipClock;
          CHIP_VOL(0,2.0);
          snNoiseConfig=3;
          snNoiseSize=15;
          willExport[i]=true;
        }
        break;
      default:
        break;
    }
    if (willExport[i]) {
      disCont[i].dispatch->toggleRegisterDump(true);
    }
  }

  // variable set but not used?
  logV("howManyChips: %d",howManyChips);

  // write chips and stuff
  w->writeI(hasSN);
  w->writeI(hasOPLL);
  w->writeI(0);
  w->writeI(0); // length. will be written later
  w->writeI(0); // loop. will be written later
  w->writeI(0); // loop length. why is this necessary?
  w->writeI(0); // tick rate
  w->writeS(snNoiseConfig);
  w->writeC(snNoiseSize);
  if (version>=0x151) {
    w->writeC(snFlags);
  } else {
    w->writeC(0);
  }
  w->writeI(hasOPN2);
  w->writeI(hasOPM);
  w->writeI(0); // data pointer. will be written later
  if (version>=0x151) {
    w->writeI(hasSegaPCM);
    w->writeI(segaPCMOffset);
    w->writeI(hasRFC);
    w->writeI(hasOPN);
    w->writeI(hasOPNA);
    w->writeI(hasOPNB);
    w->writeI(hasOPL2);
    w->writeI(hasOPL);
    w->writeI(hasY8950);
    w->writeI(hasOPL3);
    w->writeI(hasOPL4);
    w->writeI(hasOPX);
    w->writeI(hasZ280);
    w->writeI(hasRFC1);
    w->writeI(hasPWM);
    w->writeI(hasAY);
    w->writeC(ayConfig);
    w->writeC(ayFlags);
    w->writeC(ayFlags); // OPN
    w->writeC(ayFlags); // OPNA
  } else {
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeC(0);
    w->writeC(0);
    w->writeC(0); // OPN
    w->writeC(0); // OPNA
  }
  if (version>=0x160) { // global volume
    double abnormalVol=song.masterVol*(double)chipVolSum/(256.0*MAX(1,chipAccounting));
    int calcVolume=32.0*(log(abnormalVol)/log(2.0));
    if (calcVolume<-63) calcVolume=-63;
    if (calcVolume>192) calcVolume=192;
    w->writeC(calcVolume&0xff); // volume
  } else {
    w->writeC(0); // volume
  }
  // currently not used but is part of 1.60
  w->writeC(0); // reserved
  w->writeC(0); // loop count
  // 1.51
  w->writeC(0); // loop modifier
  
  if (version>=0x161) {
    w->writeI(hasGB);
    w->writeI(hasNES);
    w->writeI(hasMultiPCM);
    w->writeI(hasuPD7759);
    w->writeI(hasOKIM6258);
    w->writeC(0); // flags
    w->writeC(0); // K flags
    w->writeC(0); // C140 chip type
    w->writeC(0); // reserved
    w->writeI(hasOKIM6295);
    w->writeI(hasK051649);
    w->writeI(hasK054539);
    w->writeI(hasPCE);
    w->writeI(hasNamco);
    w->writeI(hasK053260);
    w->writeI(hasPOKEY);
    w->writeI(hasQSound);
  } else {
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeC(0); // flags
    w->writeC(0); // K flags
    w->writeC(0); // C140 chip type
    w->writeC(0); // reserved
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
  }
  if (version>=0x171) {
    w->writeI(hasSCSP);
  } else {
    w->writeI(0);
  }
  // 1.70
  w->writeI(0); // extra header
  // 1.71
  if (version>=0x171) {
    w->writeI(hasSwan);
    w->writeI(hasVSU);
    w->writeI(hasSAA);
    w->writeI(hasES5503);
    w->writeI(hasES5505);
    w->writeC(0); // 5503 chans
    w->writeC(hasES5505?1:0); // 5505 chans
    w->writeC(0); // C352 clock divider
    w->writeC(0); // reserved
    w->writeI(hasX1);
    w->writeI(hasC352);
    w->writeI(hasGA20);
    w->writeI(version>=0x172?hasLynx:0);  //Mikey introduced in 1.72
  } else {
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeC(0); // 5503 chans
    w->writeC(0); // 5505 chans
    w->writeC(0); // C352 clock divider
    w->writeC(0); // reserved
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
    w->writeI(0);
  }
  for (int i=0; i<6; i++) { // reserved
    w->writeI(0);
  }

  unsigned int exHeaderOff=w->tell();
  if (version>=0x170) {
    logD("writing extended header...");
    w->writeI(12);
    w->writeI(0);
    w->writeI(4);

    // write chip volumes
    logD("writing chip volumes (%ld)...",chipVol.size());
    w->writeC(chipVol.size());
    for (unsigned int& i: chipVol) {
      logV("- %.8x",i);
      w->writeI(i);
    }
  }

  unsigned int songOff=w->tell();

  // initialize sample offsets
  memset(sampleOff8,0,256*sizeof(unsigned int));
  memset(sampleOffSegaPCM,0,256*sizeof(unsigned int));

  // write samples
  unsigned int sampleSeek=0;
  for (int i=0; i<song.sampleLen; i++) {
    DivSample* sample=song.sample[i];
    logI("setting seek to %d",sampleSeek);
    sampleOff8[i]=sampleSeek;
    sampleSeek+=sample->length8;
  }

  if (writeDACSamples && !directStream) for (int i=0; i<song.sampleLen; i++) {
    DivSample* sample=song.sample[i];
    w->writeC(0x67);
    w->writeC(0x66);
    w->writeC(0);
    w->writeI(sample->length8);
    for (unsigned int j=0; j<sample->length8; j++) {
      w->writeC((unsigned char)sample->data8[j]+0x80);
    }
  }

  if (writeNESSamples && !directStream) for (int i=0; i<song.sampleLen; i++) {
    DivSample* sample=song.sample[i];
    w->writeC(0x67);
    w->writeC(0x66);
    w->writeC(7);
    w->writeI(sample->length8);
    for (unsigned int j=0; j<sample->length8; j++) {
      w->writeC(((unsigned char)sample->data8[j]+0x80)>>1);
    }
  }

  if (writePCESamples && !directStream) for (int i=0; i<song.sampleLen; i++) {
    DivSample* sample=song.sample[i];
    w->writeC(0x67);
    w->writeC(0x66);
    w->writeC(5);
    w->writeI(sample->length8);
    for (unsigned int j=0; j<sample->length8; j++) {
      w->writeC(((unsigned char)sample->data8[j]+0x80)>>3);
    }
  }

  for (int i=0; i<2; i++) {
    // SegaPCM
    if (writeSegaPCM[i]!=NULL && writeSegaPCM[i]->getSampleMemUsage(0)>0) {
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0x80);
      w->writeI((writeSegaPCM[i]->getSampleMemUsage(0)+8)|(i*0x80000000));
      w->writeI(writeSegaPCM[i]->getSampleMemCapacity(0));
      w->writeI(0);
      w->write(writeSegaPCM[i]->getSampleMem(0),writeSegaPCM[i]->getSampleMemUsage(0));
    }
    // ADPCM (OPNA)
    if (writeADPCM_OPNA[i]!=NULL && writeADPCM_OPNA[i]->getSampleMemUsage(0)>0) {
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0x81);
      w->writeI((writeADPCM_OPNA[i]->getSampleMemUsage(0)+8)|(i*0x80000000));
      w->writeI(writeADPCM_OPNA[i]->getSampleMemCapacity(0));
      w->writeI(0);
      w->write(writeADPCM_OPNA[i]->getSampleMem(0),writeADPCM_OPNA[i]->getSampleMemUsage(0));
    }
    // ADPCM-A (OPNB)
    if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(0)>0) {
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0x82);
      w->writeI((writeADPCM_OPNB[i]->getSampleMemUsage(0)+8)|(i*0x80000000));
      w->writeI(writeADPCM_OPNB[i]->getSampleMemCapacity(0));
      w->writeI(0);
      w->write(writeADPCM_OPNB[i]->getSampleMem(0),writeADPCM_OPNB[i]->getSampleMemUsage(0));
    }
    // ADPCM-B (OPNB)
    if (writeADPCM_OPNB[i]!=NULL && writeADPCM_OPNB[i]->getSampleMemUsage(1)>0) {
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0x83);
      w->writeI((writeADPCM_OPNB[i]->getSampleMemUsage(1)+8)|(i*0x80000000));
      w->writeI(writeADPCM_OPNB[i]->getSampleMemCapacity(1));
      w->writeI(0);
      w->write(writeADPCM_OPNB[i]->getSampleMem(1),writeADPCM_OPNB[i]->getSampleMemUsage(1));
    }
    // ADPCM (Y8950)
    if (writeADPCM_Y8950[i]!=NULL && writeADPCM_Y8950[i]->getSampleMemUsage(0)>0) {
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0x88);
      w->writeI((writeADPCM_Y8950[i]->getSampleMemUsage(0)+8)|(i*0x80000000));
      w->writeI(writeADPCM_Y8950[i]->getSampleMemCapacity(0));
      w->writeI(0);
      w->write(writeADPCM_Y8950[i]->getSampleMem(0),writeADPCM_Y8950[i]->getSampleMemUsage(0));
    }
    if (writeQSound[i]!=NULL && writeQSound[i]->getSampleMemUsage()>0) {
      unsigned int blockSize=(writeQSound[i]->getSampleMemUsage()+0xffff)&(~0xffff);
      if (blockSize > 0x1000000) {
        blockSize = 0x1000000;
      }
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0x8F);
      w->writeI((blockSize+8)|(i*0x80000000));
      w->writeI(writeQSound[i]->getSampleMemCapacity());
      w->writeI(0);
      w->write(writeQSound[i]->getSampleMem(),blockSize);
    }
    if (writeX1010[i]!=NULL && writeX1010[i]->getSampleMemUsage()>0) {
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0x91);
      w->writeI((writeX1010[i]->getSampleMemUsage()+8)|(i*0x80000000));
      w->writeI(writeX1010[i]->getSampleMemCapacity());
      w->writeI(0);
      w->write(writeX1010[i]->getSampleMem(),writeX1010[i]->getSampleMemUsage());
    }
    if (writeZ280[i]!=NULL && writeZ280[i]->getSampleMemUsage()>0) {
      // In VGM, YMZ280B's 16-bit PCM has an endianness swapped
      // which have been fixed in the upstream MAME since 2013
      // in order to get Konami FireBeat working
      // The reason given for VGM not applying this change was
      // "It matches OPL4 and MAME probably did an endianness optimization"
      size_t sampleMemLen=writeZ280[i]->getSampleMemUsage();
      unsigned char* sampleMem=new unsigned char[sampleMemLen];
      memcpy(sampleMem,writeZ280[i]->getSampleMem(),sampleMemLen);
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0x86);
      w->writeI((writeZ280[i]->getSampleMemUsage()+8)|(i*0x80000000));
      w->writeI(writeZ280[i]->getSampleMemCapacity());
      w->writeI(0);
      w->write(sampleMem,sampleMemLen);
      delete[] sampleMem;
    }
  }

  for (int i=0; i<2; i++) {
    if (writeRF5C68[i]!=NULL && writeRF5C68[i]->getSampleMemUsage()>0) {
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0xc0+i);
      w->writeI(writeRF5C68[i]->getSampleMemUsage()+8);
      w->writeI(writeRF5C68[i]->getSampleMemCapacity());
      w->writeI(0);
      w->write(writeRF5C68[i]->getSampleMem(),writeRF5C68[i]->getSampleMemUsage());
    }
    if (writeMSM6295[i]!=NULL && writeMSM6295[i]->getSampleMemUsage()>0) {
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0x8b);
      w->writeI((writeMSM6295[i]->getSampleMemUsage()+8)|(i*0x80000000));
      w->writeI(writeMSM6295[i]->getSampleMemCapacity());
      w->writeI(0);
      w->write(writeMSM6295[i]->getSampleMem(),writeMSM6295[i]->getSampleMemUsage());
    }
    if (writeGA20[i]!=NULL && writeGA20[i]->getSampleMemUsage()>0) {
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0x93);
      w->writeI((writeGA20[i]->getSampleMemUsage()+8)|(i*0x80000000));
      w->writeI(writeGA20[i]->getSampleMemCapacity());
      w->writeI(0);
      w->write(writeGA20[i]->getSampleMem(),writeGA20[i]->getSampleMemUsage());
    }
  }

  // TODO
  for (int i=0; i<2; i++) {
    if (writeES5506[i]!=NULL && writeES5506[i]->getSampleMemUsage()>0) {
      w->writeC(0x67);
      w->writeC(0x66);
      w->writeC(0x8F);
      w->writeI((writeES5506[i]->getSampleMemUsage()+8)|(i*0x80000000));
      w->writeI(writeES5506[i]->getSampleMemCapacity());
      w->writeI(0);
      w->write(writeES5506[i]->getSampleMem(),writeES5506[i]->getSampleMemUsage());
    }
  }

  // initialize streams
  int streamID=0;
  if (!directStream) {
    for (int i=0; i<song.systemLen; i++) {
      if (!willExport[i]) continue;
      streamIDs[i]=streamID;
      switch (song.system[i]) {
        case DIV_SYSTEM_YM2612:
        case DIV_SYSTEM_YM2612_EXT:
        case DIV_SYSTEM_YM2612_DUALPCM:
        case DIV_SYSTEM_YM2612_DUALPCM_EXT:
          w->writeC(0x90);
          w->writeC(streamID);
          w->writeC(0x02);
          w->writeC(0); // port
          w->writeC(0x2a); // DAC

          w->writeC(0x91);
          w->writeC(streamID);
          w->writeC(0);
          w->writeC(1);
          w->writeC(0);

          w->writeC(0x92);
          w->writeC(streamID);
          w->writeI(32000); // default
          streamID++;
          break;
        case DIV_SYSTEM_NES:
          w->writeC(0x90);
          w->writeC(streamID);
          w->writeC(20);
          w->writeC(0); // port
          w->writeC(0x11); // DAC

          w->writeC(0x91);
          w->writeC(streamID);
          w->writeC(7);
          w->writeC(1);
          w->writeC(0);

          w->writeC(0x92);
          w->writeC(streamID);
          w->writeI(32000); // default
          streamID++;
          break;
        case DIV_SYSTEM_PCE:
          for (int j=0; j<6; j++) {
            w->writeC(0x90);
            w->writeC(streamID);
            w->writeC(27);
            w->writeC(j); // port
            w->writeC(0x06); // select+DAC

            w->writeC(0x91);
            w->writeC(streamID);
            w->writeC(5);
            w->writeC(1);
            w->writeC(0);

            w->writeC(0x92);
            w->writeC(streamID);
            w->writeI(16000); // default
            streamID++;
          }
          break;
        case DIV_SYSTEM_SWAN:
          w->writeC(0x90);
          w->writeC(streamID);
          w->writeC(isSecond[i]?0xa1:0x21);
          w->writeC(0); // port
          w->writeC(0x09); // DAC

          w->writeC(0x91);
          w->writeC(streamID);
          w->writeC(0);
          w->writeC(1);
          w->writeC(0);

          w->writeC(0x92);
          w->writeC(streamID);
          w->writeI(24000); // default
          streamID++;
          break;
        default:
          break;
      }
    }
  }

  // write song data
  playSub(false);
  size_t tickCount=0;
  bool writeLoop=false;
  bool alreadyWroteLoop=false;
  int ord=-1;
  int exportChans=0;
  for (int i=0; i<chans; i++) {
    if (!willExport[dispatchOfChan[i]]) continue;
    exportChans++;
    chan[i].wentThroughNote=false;
    chan[i].goneThroughNote=false;
  }
  while (!done) {
    if (loopPos==-1) {
      if (loopOrder==curOrder && loopRow==curRow && ticks==1) {
        writeLoop=true;
      }
    }
    songTick++;
    tickPos.push_back(w->tell());
    tickSample.push_back(tickCount);
    if (nextTick(false,true)) {
      if (trailing) beenOneLoopAlready=true;
      trailing=true;
      if (!loop) countDown=0;
      for (int i=0; i<chans; i++) {
        if (!willExport[dispatchOfChan[i]]) continue;
        chan[i].wentThroughNote=false;
      }
    }
    if (trailing) {
      switch (trailingTicks) {
        case -1: { // automatic
          bool stillHaveTo=false;
          for (int i=0; i<chans; i++) {
            if (!willExport[dispatchOfChan[i]]) continue;
            if (!chan[i].goneThroughNote) continue;
            if (!chan[i].wentThroughNote) {
              stillHaveTo=true;
              break;
            }
          }
          if (!stillHaveTo) countDown=0;
          break;
        }
        case -2: // one loop
          break;
        default: // custom
          countDown--;
          break;
      }
      if (song.loopModality!=2) countDown=0;

      if (countDown>0 && !beenOneLoopAlready) {
        loopTickSong++;
      }
    }
    if (countDown<=0 || !playing || beenOneLoopAlready) {
      done=true;
      if (!loop) {
        for (int i=0; i<song.systemLen; i++) {
          disCont[i].dispatch->getRegisterWrites().clear();
        }
        break;
      }
      // stop all streams
      if (!directStream) {
        for (int i=0; i<streamID; i++) {
          w->writeC(0x94);
          w->writeC(i);
          loopSample[i]=-1;
        }
      }

      if (!playing) {
        writeLoop=false;
        loopPos=-1;
      }
    } else {
      // check for pattern change
      if (prevOrder!=ord) {
        logI("registering order change %d on %d",prevOrder, prevRow);
        ord=prevOrder;

        if (patternHints) {
          w->writeC(0x67);
          w->writeC(0x66);
          w->writeC(0xfe);
          w->writeI(3+exportChans);
          w->writeC(0x01);
          w->writeC(prevOrder);
          w->writeC(prevRow);
          for (int i=0; i<chans; i++) {
            if (!willExport[dispatchOfChan[i]]) continue;
            w->writeC(curSubSong->orders.ord[i][prevOrder]);
          }
        }
      }
    }
    // get register dumps
    for (int i=0; i<song.systemLen; i++) {
      std::vector<DivRegWrite>& writes=disCont[i].dispatch->getRegisterWrites();
      for (DivRegWrite& j: writes) {
        performVGMWrite(w,song.system[i],j,streamIDs[i],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i],pendingFreq,playingSample,directStream);
        writeCount++;
      }
      writes.clear();
    }
    // check whether we need to loop
    int totalWait=cycles>>MASTER_CLOCK_PREC;
    if (directStream) {
      // render stream of all chips
      for (int i=0; i<song.systemLen; i++) {
        disCont[i].dispatch->fillStream(delayedWrites[i],44100,totalWait);
        for (DivDelayedWrite& j: delayedWrites[i]) {
          sortedWrites.push_back(std::pair<int,DivDelayedWrite>(i,j));
        }
        delayedWrites[i].clear();
      }

      if (!sortedWrites.empty()) {
        // sort if more than one chip
        if (song.systemLen>1) {
          std::sort(sortedWrites.begin(),sortedWrites.end(),[](const std::pair<int,DivDelayedWrite>& a, const std::pair<int,DivDelayedWrite>& b) -> bool {
            return a.second.time<b.second.time;
          });
        }

        // write it out
        int lastOne=0;
        for (std::pair<int,DivDelayedWrite>& i: sortedWrites) {
          if (i.second.time>lastOne) {
            // write delay
            int delay=i.second.time-lastOne;
            if (delay>16) {
              w->writeC(0x61);
              w->writeS(delay);
            } else if (delay>0) {
              w->writeC(0x70+delay-1);
            }
            lastOne=i.second.time;
          }
          // write write
          performVGMWrite(w,song.system[i.first],i.second.write,streamIDs[i.first],loopTimer,loopFreq,loopSample,sampleDir,isSecond[i.first],pendingFreq,playingSample,directStream);
          writeCount++;
        }
        sortedWrites.clear();
        totalWait-=lastOne;
        tickCount+=lastOne;
      }
    } else {
      for (int i=0; i<streamID; i++) {
        if (loopSample[i]>=0) {
          loopTimer[i]-=(loopFreq[i]/44100.0)*(double)totalWait;
        }
      }
      bool haveNegatives=false;
      for (int i=0; i<streamID; i++) {
        if (loopSample[i]>=0) {
          if (loopTimer[i]<0) {
            haveNegatives=true;
          }
        }
      }
      while (haveNegatives) {
        // finish all negatives
        int nextToTouch=-1;
        for (int i=0; i<streamID; i++) {
          if (loopSample[i]>=0) {
            if (loopTimer[i]<0) {
              if (nextToTouch>=0) {
                if (loopTimer[nextToTouch]>loopTimer[i]) nextToTouch=i;
              } else {
                nextToTouch=i;
              }
            }
          }
        }
        if (nextToTouch>=0) {
          double waitTime=totalWait+(loopTimer[nextToTouch]*(44100.0/MAX(1,loopFreq[nextToTouch])));
          if (waitTime>0) {
            w->writeC(0x61);
            w->writeS(waitTime);
            logV("wait is: %f",waitTime);
            totalWait-=waitTime;
            tickCount+=waitTime;
          }
          if (loopSample[nextToTouch]<song.sampleLen) {
            DivSample* sample=song.sample[loopSample[nextToTouch]];
            // insert loop
            if (sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT)<sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)) {
              w->writeC(0x93);
              w->writeC(nextToTouch);
              w->writeI(sampleOff8[loopSample[nextToTouch]]+sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT));
              w->writeC(0x81);
              w->writeI(sample->getLoopEndPosition(DIV_SAMPLE_DEPTH_8BIT)-sample->getLoopStartPosition(DIV_SAMPLE_DEPTH_8BIT));
            }
          }
          loopSample[nextToTouch]=-1;
        } else {
          haveNegatives=false;
        }
      }
    }
    // write wait
    if (totalWait>0) {
      if (totalWait==735) {
        w->writeC(0x62);
      } else if (totalWait==882) {
        w->writeC(0x63);
      } else {
        w->writeC(0x61);
        w->writeS(totalWait);
      }
      tickCount+=totalWait;
    }
    if (writeLoop && !alreadyWroteLoop) {
      writeLoop=false;
      alreadyWroteLoop=true;
      loopPos=w->tell();
      loopTickSong=songTick;
    }
  }
  // end of song
  w->writeC(0x66);

  got.rate=origRate;

  for (int i=0; i<song.systemLen; i++) {
    disCont[i].dispatch->toggleRegisterDump(false);
  }

  // write GD3 tag
  gd3Off=w->tell();
  w->write("Gd3 ",4);
  w->writeI(0x100);
  w->writeI(0); // length. will be written later

  WString ws;
  ws=utf8To16(song.name.c_str());
  w->writeWString(ws,false); // name
  ws=utf8To16(song.nameJ.c_str());
  w->writeWString(ws,false); // japanese name
  ws=utf8To16(song.category.c_str());
  w->writeWString(ws,false); // game name
  ws=utf8To16(song.categoryJ.c_str());
  w->writeWString(ws,false); // japanese game name
  ws=utf8To16(song.systemName.c_str());
  w->writeWString(ws,false); // system name
  ws=utf8To16(song.systemNameJ.c_str());
  w->writeWString(ws,false); // japanese system name
  ws=utf8To16(song.author.c_str());
  w->writeWString(ws,false); // author name
  ws=utf8To16(song.authorJ.c_str());
  w->writeWString(ws,false); // japanese author name
  w->writeS(0); // date
  w->writeWString(L"Furnace Tracker",false); // ripper
  w->writeS(0); // notes

  int gd3Len=w->tell()-gd3Off-12;

  w->seek(gd3Off+8,SEEK_SET);
  w->writeI(gd3Len);

  // finish file
  size_t len=w->size()-4;
  w->seek(4,SEEK_SET);
  w->writeI(len);
  w->seek(0x14,SEEK_SET);
  w->writeI(gd3Off-0x14);
  w->writeI(tickCount);
  if (loop) {
    if (loopPos==-1) {
      w->writeI(0);
      w->writeI(0);
    } else if (loopTickSong<0 || loopTickSong>(int)tickPos.size()) {
      logW("loopTickSong out of range! %d>%d",loopTickSong,(int)tickPos.size());
      w->writeI(0);
      w->writeI(0);
    } else {
      int realLoopTick=tickSample[loopTickSong];
      int realLoopPos=tickPos[loopTickSong];
      logI("tickCount-realLoopTick: %d. realLoopPos: %d",tickCount-realLoopTick,realLoopPos);
      w->writeI(realLoopPos-0x1c);
      w->writeI(tickCount-realLoopTick);
    }
  } else {
    w->writeI(0);
    w->writeI(0);
  }
  if (mayWriteRate) {
    w->writeI(round(curSubSong->hz));
  }
  w->seek(0x34,SEEK_SET);
  w->writeI(songOff-0x34);
  if (version>=0x170) {
    w->seek(0xbc,SEEK_SET);
    w->writeI(exHeaderOff-0xbc);
  }

  remainingLoops=-1;
  playing=false;
  freelance=false;
  extValuePresent=false;

  logI("%d register writes total.",writeCount);

  BUSY_END;
  return w;
}
