#include <stdio.h>
#include <string.h>
#include <SDL/SDL_stdinc.h> // for datatypes
#include "bigband.h"
#include "ps-out.h"
#include "templates.h"

PostscriptOut ps_out;

static const int
  subv_max=3,  // last subvoice only for test
  prog_dim=inst_max*70+100, // abc header dim
  meas_max=1000,     // max measures
  sclin_max=73*7/12, // from bigband.cpp
  signs[keys_max][7]= {
//  B   A   G   F   E   D   C
  { 0  ,0  ,0  ,0  ,0  ,0  ,0 },    // C
  { eLo,eLo,eLo,0  ,eLo,eLo,0 },    // Des
  { 0  ,0  ,0  ,eHi,0  ,0  ,eHi },  // D
  { eLo,eLo,0  ,0  ,eLo,0  ,0 },    // Es
  { 0  ,0  ,eHi,eHi,0  ,eHi,eHi },  // E
  { eLo,0  ,0  ,0  ,0  ,0  ,0 },    // F
  { eLo,eLo,eLo,eLo,eLo,eLo,0 },    // Ges
  { 0  ,0  ,0  ,eHi,0  ,0  ,0 },    // G
  { eLo,eLo,0  ,0  ,eLo,eLo,0 },    // As
  { 0  ,0  ,eHi,eHi,0  ,0  ,eHi },  // A
  { eLo,0  ,0  ,0  ,eLo,0  ,0 },    // Bes
  { 0  ,eHi,eHi,eHi,0  ,eHi,eHi }   // B
  },
  signsMode[keys_max] = {  0 ,eLo ,eHi,eLo ,eHi,eLo,eLo ,eHi,eLo,eHi ,eLo ,eHi };
const char
  *maj_keys[keys_max] = { "C","Db","D","Eb","E","F","Gb","G","Ab","A","Bb","B" },
  *clefname[2]= { "treble","bass" },
  *author="Beethoven",
  *perc_vn="perc";


static char
  buf1[10],buf2[10];

static bool rest_is_x=false, // can be true for percussion
            invis_perc_rest=true, // invisible perc rest?
            invis_rest,
            skip_fr,  // skip first rests?
            do_annot;

static int eq_prev_mnr=-1,
           first_color;  // for annotations

FILE *out;

struct StringBuf {
  int dim;
  char *buf,
       *bp;
  StringBuf():dim(500),buf(0),bp(0) { }
  void add(const char *form,...) { 
    va_list ap;
    va_start(ap,form);
    check_dim(50);
    bp += vsprintf(bp,form,ap);
    va_end(ap);
  }
  void check_dim(int headroom) {
    if (!buf) {
      bp=buf=new char[dim];
      buf[0]=0;
      return;
    }
    if (bp-buf>dim-headroom) {
      char *new_buf=new char[2*dim];
      for (int i=0;i<dim;++i) new_buf[i]=buf[i];
      bp=new_buf+(bp-buf);
      delete[] buf;
      buf=new_buf;
      dim*=2;
    }
  }
};

struct SubVoice:StringBuf {
  int busy,
      prev_mnr;     // previous non-rest measure nr
  void reset() {
    if (buf) buf[0]=0;
    bp=buf;
    busy=0;
    prev_mnr=-1;
  }
  void r_len(int mnr,int dur,int subv_nr);
};

struct Voice {
  SubVoice sv[subv_max];
  Uint8 local_lsign[7],
        voice_key_nr,
        voice_clef_nr;
  void reset() {
    for (int i=0;i<subv_max;++i) sv[i].reset();
  }
};

Voice voices[inst_max];
SubVoice perc_voices[2];

const int good_len[]={ 32,24,16,14,12,8,7,6,4,3,2,1,0 };

static int min(int a, int b) { return a>b ? b : a; }
static int idiv(int a,int b) {  //(2*a + b)/(b*2); } // (3 * a)/(b*2); }
  if (a%b) return a/b + 1;
  return a/b;
}

void midinr_to_lnr(Uint8 mnr,Uint8& lnr,Uint8& sign,int signs_mode) {
  static const int
                     // c  cis  d  es   e   f  fis  g  gis  a  bes  b 
             ar_0[12]={ 0 , 0 , 1 , 2 , 2 , 3 , 3 , 4 , 4 , 5 , 6 , 6 },
             s_0[12]= { 0 ,eHi, 0 ,eLo, 0 , 0 ,eHi, 0 ,eHi, 0 ,eLo, 0 },

                     // c  des  d  es   e   f  ges  g  as   a  bes  b 
             ar_f[12]={ 0 , 1 , 1 , 2 , 2 , 3 , 4 , 4 , 5 , 5 , 6 , 6 },
             s_f[12]= { 0 ,eLo, 0 ,eLo, 0 , 0 ,eLo, 0 ,eLo, 0 ,eLo, 0 },

                     // c  cis  d  dis  e   f  fis  g  gis a  ais  b 
             ar_s[12]={ 0 , 0 , 1 , 1 , 2 , 3 , 3 , 4 , 4 , 5 , 5 , 6 },
             s_s[12]= { 0 ,eHi, 0 ,eHi, 0 , 0 ,eHi, 0 ,eHi, 0 ,eHi, 0 };
  int ind=mnr%12;
  const int *ar, *s;

  switch (signs_mode) {
    case eLo: ar=ar_f; s=s_f; break;
    case eHi: ar=ar_s; s=s_s; break;
    case 0: ar=ar_0; s=s_0; break;
    default:
      ar=ar_0; s=s_0;
      alert("midinr_to_lnr: signs_mode %d?",signs_mode);
  }
  lnr=6 - (mnr/12*7 + ar[ind]) % 7;  
  sign=s[ind];
}

struct PsNote {
  int mnr;  // measure nr
  Uint8 time,
        col,
        dur,extra_dur,next_dur,
        midi_nr,
        lnr,
        note_sign,
        triad_cat;
  bool sampled,
       slashed,
       staccato;
  PsNote *tied;     // tied to next note
  PsNote(int c,int _mnr,int _time,int _midi_nr,int _dur,int tr_cat):
      mnr(_mnr),
      time(_time),
      col(c),
      dur(_dur),
      extra_dur(0),
      next_dur(0),
      midi_nr(_midi_nr),
      triad_cat(tr_cat),
      sampled(false),
      slashed(false),
      staccato(false),
      tied(0) {
    midinr_to_lnr(midi_nr,lnr,note_sign,signsMode[voices[col].voice_key_nr]);
    if (!tr_cat) {
      if (time + dur > meter*subdiv) {
        next_dur=dur - meter*subdiv + time;
        dur-=next_dur;
      }
      int t=time/subdiv % nupq;
      if (t && dur!=nupq*subdiv && t*subdiv + dur > nupq*subdiv) {   // <-- syncopated quarter note not split
        extra_dur=dur + (t - nupq)*subdiv;
      }
    }
  }
  PsNote(int c,int mn,int t,int d,int tr_cat):  // sampled note
      mnr(mn),
      time(t),
      col(c),
      dur(d),
      extra_dur(0),
      triad_cat(tr_cat),
      sampled(true),
      slashed(false),
      staccato(false),
      tied(0) {
  }

  bool operator<(PsNote &other) { return time < other.time; }
  bool operator==(PsNote &other) {
    if (slashed) return col==other.col && time==other.time;
    return false;    // always insert
  }
  const char* abc_note_name();
  const char* abc_perc_note_name();
};

void SubVoice::r_len(int mnr,int dur,int subv_nr) {  // abc rest code
  const char *rest= subv_nr>0 && dur==meter*subdiv ? " x" : " z";
  int tst_dur=idiv(dur,subdiv);
  if ((skip_fr && mnr==0 && busy==0) ||
      invis_rest || rest_is_x) rest="x";
  for (int i=0;;++i) {
    if (good_len[i]<tst_dur) {
      for (int j=0;;++j) {
        if (!good_len[j]) {
          alert("measure %d: bad rest length %d",mnr,tst_dur);
          add("%s%d",rest,tst_dur);
          break;
        }
        if (good_len[j]<tst_dur) {
          add("%s%d%s%d",rest,good_len[j],rest,tst_dur-good_len[j]);
          break;
        }
      }
      break;
    }
    if (tst_dur==good_len[i]) {
      add("%s%d",rest,tst_dur);
      break;
    }
  }
}

const char *PsNote::abc_note_name() {
  // abc note name: midi_nr=60 -> abc note = 'c'
  static const char *name_hi[84]={
     "C,,,","^C,,,","D,,,","^D,,,","E,,,","F,,,","^F,,,","G,,,","^G,,,","A,,,","^A,,,","B,,,",
     "C,," ,"^C,," ,"D,," ,"^D,," ,"E,," ,"F,," ,"^F,," ,"G,," ,"^G,," ,"A,," ,"^A,," ,"B,,",
     "C,"  ,"^C,"  ,"D,"  ,"^D,"  ,"E,"  ,"F,"  ,"^F,"  ,"G,"  ,"^G,"  ,"A,"  ,"^A,"  ,"B,",
     "C"   ,"^C"   ,"D"   ,"^D"   ,"E"   ,"F"   ,"^F"   ,"G"   ,"^G"   ,"A"   ,"^A"   ,"B",
     "c"   ,"^c"   ,"d"   ,"^d"   ,"e"   ,"f"   ,"^f"   ,"g"   ,"^g"   ,"a"   ,"^a"   ,"b",
     "c'"  ,"^c'"  ,"d'"  ,"^d'"  ,"e'"  ,"f'"  ,"^f'"  ,"g'"  ,"^g'"  ,"a'"  ,"^a'"  ,"b'",
     "c''" ,"^c''" ,"d''" ,"^d''" ,"e''" ,"f''" ,"^f''" ,"g''" ,"^g''" ,"a''" ,"^a''" ,"b''" };
  static const char *name_lo[84]={
     "C,,,","_D,,,","D,,,","_E,,,","E,,,","F,,,","_G,,,","G,,,","_A,,,","A,,,","_B,,,","B,,,",
     "C,," ,"_D,," ,"D,," ,"_E,," ,"E,," ,"F,," ,"_G,," ,"G,," ,"_A,," ,"A,," ,"_B,," ,"B,,",
     "C,"  ,"_D,"  ,"D,"  ,"_E,"  ,"E,"  ,"F,"  ,"_G,"  ,"G,"  ,"_A,"  ,"A,"  ,"_B,"  ,"B,",
     "C"   ,"_D"   ,"D"   ,"_E"   ,"E"   ,"F"   ,"_G"   ,"G"   ,"_A"   ,"A"   ,"_B"   ,"B",
     "c"   ,"_d"   ,"d"   ,"_e"   ,"e"   ,"f"   ,"_g"   ,"g"   ,"_a"   ,"a"   ,"_b"   ,"b",
     "c'"  ,"_d'"  ,"d'"  ,"_e'"  ,"e'"  ,"f'"  ,"_g'"  ,"g'"  ,"_a'"  ,"a'"  ,"_b'"  ,"b'",
     "c''" ,"_d''" ,"d''" ,"_e''" ,"e''" ,"f''" ,"_g''" ,"g''" ,"_a''" ,"a''" ,"_b''" ,"b''" };
  static const char *eq_name[84]={
     "=C,,,",0,"=D,,,",0,"=E,,,","=F,,,",0,"=G,,,",0,"=A,,,",0,"=B,,,",
     "=C,," ,0,"=D,," ,0,"=E,," ,"=F,," ,0,"=G,," ,0,"=A,," ,0,"=B,,",
     "=C,"  ,0,"=D,"  ,0,"=E,"  ,"=F,"  ,0,"=G,"  ,0,"=A,"  ,0,"=B,",
     "=C"   ,0,"=D"   ,0,"=E"   ,"=F"   ,0,"=G"   ,0,"=A"   ,0,"=B",
     "=c"   ,0,"=d"   ,0,"=e"   ,"=f"   ,0,"=g"   ,0,"=a"   ,0,"=b",
     "=c'"  ,0,"=d'"  ,0,"=e'"  ,"=f'"  ,0,"=g'"  ,0,"=a'"  ,0,"=b'",
     "=c''" ,0,"=d''" ,0,"=e''" ,"=f''" ,0,"=g''" ,0,"=a''" ,0,"=b''" };
  int ind=midi_nr-24;
  if (ind<0) { alert("abc_note_nname: note too low"); return 0; }
  if (ind>=84) { alert("abc_note_nname: note too high"); return 0; }
  Uint8 line_sign=voices[col].local_lsign[lnr];
  if (line_sign && !note_sign)
    return eq_name[ind];
  bool skip=false;
  if (line_sign && line_sign==note_sign) skip=true;
  const char *p;
  switch (note_sign) {
    case eHi:
    case 0: p=name_hi[ind]; break;
    case eLo: p=name_lo[ind]; break;
    default: alert("note_sign:%d?",note_sign); return 0;
  }
  if (skip && (p[0]=='_' || p[0]=='^')) return p+1;
  return p;
}

const char *PsNote::abc_perc_note_name() {
  return pprop[col].abc_sym;
}

Array<SLinkedList<struct PsNote>,meas_max> psn_buf;

int transp_key(int key_nr,int tr) {
  if (!tr) return key_nr;
  int ind=0;
  if (tr<0) tr=-(-tr%12);
  else tr=tr%12;
  ind=key_nr+tr;
  if (ind<0) ind+=keys_max;
  else if (ind>=keys_max) ind-=keys_max;
  return ind;
}

void PostscriptOut::reset_ps() {
  for (int i=0;i<=lst_ind;++i) psn_buf[i].reset();
  first_color=inst_max;
}

void PostscriptOut::set(int tr_mode,bool sk_fr,bool inv_pr) {
  for (int i=0;i<inst_max;++i) {
    if (tr_mode==1) {
      voices[i].voice_key_nr=transp_key(abc_key,iprop[i].transpose);
      voices[i].voice_clef_nr=iprop[i].clef;
    }
    else if (tr_mode==2) {
      voices[i].voice_key_nr=transp_key(abc_key,iprop[i].transpose2);
      voices[i].voice_clef_nr=iprop[i].clef2;
    }
    else { alert("ps_out.set?"); break; }
  }
  skip_fr=sk_fr;
  invis_perc_rest=inv_pr;
}

static void test_dur(int mnr,Uint8& dur) {
  int tst_dur=idiv(dur,subdiv);
  for (int i=0;;++i) {
    if (good_len[i]<tst_dur) {
      int old_dur=tst_dur;
      dur=good_len[i]*subdiv;
      alert("warning: measure %d: note length %d -> %d",mnr,old_dur,tst_dur);
      break;
    }
    if (tst_dur==good_len[i]) break;
  }
}

void PostscriptOut::insert_slashed(int col,int ev_time,int triad_cat) {
  int ind=ev_time/meter/subdiv;
  if (ind>lst_ind)
    lst_ind=ind;
  PsNote note(col,ind,ev_time % (meter*subdiv),60,nupq*subdiv,triad_cat);
  note.slashed=true;
  if (debug) printf("insert slashed ps-note: col=%d tim=%d\n",col,ev_time % (meter*subdiv));
  psn_buf[ind].ord_insert(note)->d;
}

void PostscriptOut::insert(int col,int ev_time,int midi_nr,int dur,int triad_cat) {
  int ind=ev_time/meter/subdiv;
  if (ind>lst_ind)
    lst_ind=ind;
  if (first_color>col) first_color=col;
  if (triad_cat) {
    int exp_dur=dur; // espected dur
    switch (triad_cat) {
      case e1_2: case e2_2: case e3_2:
        exp_dur=2*subdiv/3; break;
      case e1_4: case e2_4: case e3_4:
        exp_dur=4*subdiv/3; break;
      default: alert("triad_cat?");
    }
    if (dur!=exp_dur) {
      alert("PsOut::insert: meas=%d dur=%d tr_cat=%d exp_dur=%d",ind+1,dur,triad_cat,exp_dur);
      triad_cat=0;
    }
  }
  PsNote note(col,ind,ev_time % (meter*subdiv),midi_nr,dur,triad_cat);
  if (debug) printf("insert ps-note: col=%d ind=%d tim=%d dur=%d\n",col,ind,note.time,dur);
  PsNote *np=&psn_buf[ind].ord_insert(note)->d;
  if (np->extra_dur>0) test_dur(ind,np->extra_dur);
  else test_dur(ind,np->dur);
  while (np->next_dur>0) {
    ++ind;
    PsNote note1(col,ind,0,midi_nr,np->next_dur,0);
    test_dur(ind,note1.dur);
    np->tied=&psn_buf[ind].prepend(note1)->d;
    if (ind>lst_ind) lst_ind=ind;
    np=&note1;
    if (np->extra_dur>0) test_dur(ind,np->extra_dur);
    else test_dur(ind,np->dur);
  }
}

void PostscriptOut::insert_perc(int col,int ev_time,int triad_cat) {
  int ind=ev_time/meter/subdiv,
      dur=nupq*subdiv/2; // modified later-on
  if (ind>lst_ind)
    lst_ind=ind;
  PsNote perc_note(col,ind,ev_time % (meter*subdiv),dur,triad_cat);
  if (debug) printf("insert perc-note: col=%d tim=%d dur=%d triad_cat=%d\n",col,ev_time % (meter*subdiv),dur,triad_cat);
  psn_buf[ind].ord_insert(perc_note);
}

static char *n_len(char *buf,int dur) {  // abc note length code
  if (dur==1) strcpy(buf,"");
  else sprintf(buf,"%d",idiv(dur,subdiv));
  return buf;
}

static bool is_chord(SLList_elem<PsNote> *psn,SLList_elem<PsNote> *psn1) {
  if (psn1->d.sampled || psn->d.sampled) return false;
  return psn1->d.col==psn->d.col && psn1->d.time==psn->d.time && psn1->d.dur==psn->d.dur && psn1->d.triad_cat==psn->d.triad_cat;
}

static bool is_perc_chord(SLList_elem<PsNote> *psn,SLList_elem<PsNote> *psn1) {
  if (!psn1->d.sampled || !psn->d.sampled) return false;
  return psn1->d.time==psn->d.time && psn1->d.dur==psn->d.dur;
}

bool perc_filter(SLList_elem<PsNote>*psn,int v_nr) {
  if (!psn->d.sampled) return true;
  int mode= v_nr==0 ? eUp : eDown;
  if (pprop[psn->d.col].abc_stem!=mode) return true;
  return false;
}

static void add_rest(int mnr,int time,SubVoice *sv,int subv_nr,int len) {
  int extra_len= time % (nupq*subdiv) && time % (nupq*subdiv) + len > nupq*subdiv ? len - nupq*subdiv + time % (nupq*subdiv) : 0;
  if (extra_len) {
    sv->r_len(mnr,len-extra_len,subv_nr);
    if ((time+len-extra_len) % (4*subdiv)==0) sv->add(" "); //<---
    sv->r_len(mnr,extra_len,subv_nr);
  }
  else {
    sv->r_len(mnr,len,subv_nr);
  }
}

static void add_Z_rest(SubVoice *subv,int subv_nr,int meas_nr) {
  if (skip_fr && subv->prev_mnr<0) {
    subv->prev_mnr=0;
    if (meas_nr>0) subv->add("[I:setbarnb %d]",meas_nr+1);  // now 2nd measure will be numbered (see format.txt at abcm2ps)
  }
  int mnr=subv->prev_mnr+1;
  if (meas_nr>mnr) {
    int mnr1,d_mnr;
    if (subv_nr==0)
      for (mnr1=mnr;mnr1<meas_nr;++mnr1) {
        if (inform(mnr1)->annot && do_annot) {
          d_mnr=mnr1 - mnr;
          if (d_mnr==1)
            subv->add("z%d|",meter);
          else if (d_mnr>1) // could be 0
            subv->add("Z%d|",d_mnr);
          subv->add("\"%c\"",inform(mnr1)->annot);
          mnr=mnr1;
        }
      }
    d_mnr=meas_nr-mnr;
    if (d_mnr==1)
      subv->add("z%d|",meter);
    else if (d_mnr>1)
      subv->add("Z%d|",d_mnr);
  }
}

static void add_first_rests(SubVoice *subv,int subv_nr,int meas_nr) {
  if (subv->buf && subv->buf[0]) return;
  const char *rest= subv_nr==0 && !(skip_fr && meas_nr==0) ? "z" : "x";
  for (int i=0;i<meas_nr;++i) {
    if (subv_nr==0 && do_annot && inform(i)->annot) subv->add("\"%c\"",inform(i)->annot);
    if (i==0 && skip_fr) subv->add("x%d|",meter);
    else subv->add("%s%d|",rest,meter);
  }
}

static void add_last_rest(SubVoice *subv,int subv_nr,int meas_nr) {
  if (subv->busy < meter*subdiv) {
    if (subv_nr==0 && do_annot && inform(meas_nr)->annot && subv->busy==0)
      subv->add("\"%c\"",inform(meas_nr)->annot);
    add_rest(meas_nr,subv->busy,subv,subv_nr,meter*subdiv - subv->busy);
  }
  subv->add("|");
}

void PostscriptOut::add_staccato(const int meas_nr) {
  SLList_elem<PsNote> *psn,*psn1,*psn2;
  for (int nupq2=nupq;nupq2>1;nupq2/=2) {
    for (psn=psn_buf[meas_nr].lis;psn;psn=psn->nxt) {
      if (psn->d.sampled) continue;
      int dur=psn->d.dur,
          col=psn->d.col,
          time=psn->d.time;
      if (time%(nupq2*subdiv)==0 && dur==nupq2*subdiv/2 && !psn->d.triad_cat) { // create staccato notes
        for (psn1=psn->nxt;psn1;psn1=psn1->nxt) {
          if (psn1->d.sampled || psn1->d.col!=col) continue;
          if (psn1->d.time == psn->d.time+psn->d.dur)
            break;
          if (psn1->d.time < time + dur + nupq2*subdiv/2 &&  // empty space behind?
              !(psn1->d.time == time && psn1->d.dur == dur)) // and not equal duration?
                break;

          for (psn2=psn;psn2;psn2=psn2->nxt) {  // find chord notes
            if (psn2->d.sampled || psn2->d.col!=col) continue;
            if (psn2->d.time == time && psn2->d.dur == dur) {
              psn2->d.dur=nupq2*subdiv;
              psn2->d.staccato=true;
            }
          }
        }
        if (!psn1 && meter*subdiv - time - dur >= nupq2*subdiv/2) { // last note in measure?
          for (psn2=psn_buf[meas_nr].lis;psn2;psn2=psn2->nxt) {  // find chord notes
            if (psn2->d.sampled || psn2->d.col!=col) continue;
            if (psn2->d.time == time && psn2->d.dur == dur) {
              psn2->d.dur=nupq2*subdiv;
              psn2->d.staccato=true;
            }
          }
        }
      }
      if (debug) printf("add_stacc: meas=%d nupq2=%d col=%d tim=%d dur=%d stacc=%d x_dur=%d nxt_dur=%d\n",
        meas_nr,nupq2,psn->d.col,psn->d.time,psn->d.dur,psn->d.staccato,psn->d.extra_dur,psn->d.next_dur);
    }
  }
}

void PostscriptOut::fill_subvoice(const int meas_nr,int subv_nr) {
  SLList_elem<PsNote> *psn,*psn1;
  if (debug) printf("fsub meas=%d subv_nr=%d\n",meas_nr,subv_nr);
  invis_rest=false;
  for (int c=0;c<inst_max;++c)
    voices[c].sv[subv_nr].busy=0;
  for (psn=psn_buf[meas_nr].lis;psn;) {
    if (psn->d.sampled) {
      psn=psn->nxt; continue;
    }
    bool chord_notes=false,
         add_chord=ctr_data[psn->d.col].add_chords;
    Voice *voice=voices+psn->d.col;
    SubVoice *subv=voice->sv+subv_nr;
    do_annot= psn->d.col==first_color;
    if (debug) printf("meas_nr=%d tim=%d dur=%d stacc=%d busy=%d col='%s' tied=%p\n",
      meas_nr,psn->d.time,psn->d.dur,psn->d.staccato,subv->busy,iprop[psn->d.col].name,psn->d.tied);
    if (psn->d.time<subv->busy) {
      if (debug) printf("skip in meas %d\n",meas_nr);
      psn=psn->nxt;
      continue;
    }
    if (s_voice) {
      add_Z_rest(voice->sv+subv_nr,subv_nr,meas_nr);
      subv->prev_mnr=meas_nr;
    }
    else
      add_first_rests(voice->sv+subv_nr,subv_nr,meas_nr); // does nothing if buf != 0
    if (subv_nr==0 && subv->busy==0) {
      if (add_chord && inform(meas_nr)->chord) subv->add("\"^%s\"",inform(meas_nr)->chord);
      if (inform(meas_nr)->annot && do_annot) subv->add("\"%c\"",inform(meas_nr)->annot);
    }
    if (subv->busy>0 && subv->busy%(4*subdiv)==0 && psn->d.triad_cat!=e3_2 && psn->d.triad_cat!=e3_4) {
      subv->add(" "); // interrupt note beaming
      if (add_chord && subv->busy%(meter*subdiv/2)==0 && (subv_nr==0 || s_voice) && inform(meas_nr)->chord2) {
        subv->add("\"^%s\"",inform(meas_nr)->chord2);
      }
    }
    if (subv->busy < psn->d.time)
      add_rest(psn->d.mnr,subv->busy,subv,subv_nr,psn->d.time - subv->busy);
    if (psn->d.triad_cat==e1_2 || psn->d.triad_cat==e1_4) subv->add("(3");
    for (psn1=psn->nxt;psn1;psn1=psn1->nxt) {
      if (psn1->d.sampled || psn1->d.time < subv->busy) continue;
      if (is_chord(psn,psn1)) {
        chord_notes=true;
        if (psn->d.staccato) { subv->add(".["); psn->d.staccato=false; }
        else subv->add("[");
        break; 
      }
    }
    const char *nn1=psn->d.abc_note_name();
    if (!nn1) { psn=psn->nxt; continue; }
    voice->local_lsign[psn->d.lnr]=psn->d.note_sign;
    if (debug) printf("time=%d, abc nname=%s, slashed=%d\n",psn->d.time,nn1,psn->d.slashed);
    if (psn->d.slashed)
      subv->add("A0%d",nupq); // slashed note 'A'
    else if (psn->d.extra_dur) {
      subv->add("%s%s-%s%s",
                 nn1,n_len(buf1,psn->d.dur - psn->d.extra_dur),
                 psn->d.abc_note_name(),n_len(buf2,psn->d.extra_dur));
    }
    else {
      subv->add("%s%s%s",psn->d.staccato ? "." : "",nn1,n_len(buf1,psn->d.dur));
    }
    if (psn->d.tied) {
      subv->add("-");
    }
    if (chord_notes) {
      for (psn1=psn->nxt;psn1;) {
        if (psn1->d.sampled || psn1->d.time < subv->busy) { psn1=psn1->nxt; continue; }
        if (is_chord(psn,psn1)) {
          nn1=psn1->d.abc_note_name();
          if (!nn1) { psn1=psn1->nxt; continue; }
          subv->add("%s%s",nn1,n_len(buf1,psn->d.dur));
          if (psn1->d.tied) {
            subv->add("-");
          }
          psn1=psn_buf[meas_nr].remove(psn1);
        }
        else psn1=psn1->nxt;
      }
      subv->add("]");
    }
    subv->busy=psn->d.time + psn->d.dur;
    psn=psn_buf[meas_nr].remove(psn);
  }
  if (debug) puts("---");
}

bool equal_notes(SLList_elem<PsNote> *lis1,SLList_elem<PsNote> *lis2) { // supposed: s_perc = true, so no instr notes
  if (!lis1 || !lis2) return false;
  for (;;) {
    if (lis1->d.time!=lis2->d.time || lis1->d.col!=lis2->d.col) return false;
    lis1=lis1->nxt; lis2=lis2->nxt;
    if (!lis1 && !lis2) break;
    if (!lis1 || !lis2) return false;
  }
  return true;
}

void adept_perc_dur(int meas_nr) { // such that many rests are avoided
  SLList_elem<PsNote> *psn,*psn1;
  for (psn=psn_buf[meas_nr].lis;psn;psn=psn->nxt) {
    if (psn->d.triad_cat) {
      switch (psn->d.triad_cat) {
        case e1_2: case e2_2: case e3_2:
          psn->d.dur=2*subdiv/3;
          break;
        case e1_4: case e2_4: case e3_4:
          psn->d.dur=4*subdiv/3;
          break;
      }
      continue;
    }
    if (!psn->nxt) { psn->d.dur=min(nupq*subdiv,meter*subdiv - psn->d.time); break; }
    psn->d.dur=nupq*subdiv; // default
    for (psn1=psn->nxt;psn1;psn1=psn1->nxt) {
      if (pprop[psn1->d.col].abc_stem!=pprop[psn->d.col].abc_stem) continue;
      int dtim=psn1->d.time - psn->d.time;
      if (dtim==0) continue;
      if (dtim>=nupq*subdiv/4 && dtim<nupq*subdiv/2) { psn->d.dur=nupq*subdiv/4; break; }
      if (dtim>=nupq*subdiv/2 && dtim<nupq*subdiv) { psn->d.dur=nupq*subdiv/2; break; }
      if (dtim>nupq*subdiv) break;
    }
  }
}

void PostscriptOut::fill_perc_subv(const int meas_nr,int subv_nr) {
  invis_rest=invis_perc_rest;
  perc_voices[subv_nr].busy=0;
  if (s_perc) do_annot=true;
  if (s_perc && subv_nr==0) {  // equal notes in next measure?
    bool yes= meas_nr==eq_prev_mnr;
    if (meas_nr<ps_out.lst_ind && equal_notes(psn_buf[meas_nr+1].lis,psn_buf[meas_nr].lis)) {
      eq_prev_mnr=meas_nr+1;
    }
    if (yes) {
      if (inform(meas_nr)->annot && do_annot) perc_voices[0].add("\"%c\"",inform(meas_nr)->annot);
      psn_buf[meas_nr].reset();
      rest_is_x=true;
      return;
    }
  }
  SLList_elem<PsNote> *psn,*psn1;
  SubVoice *subv;
  for (psn=psn_buf[meas_nr].lis;psn;) {
    if (perc_filter(psn,subv_nr)) { psn=psn->nxt; continue; }
    bool chord_notes=false;
    subv=perc_voices+subv_nr;
    if (psn->d.time<subv->busy) {
      if (debug) printf("perc: skip in meas %d, time=%d\n",meas_nr,psn->d.time);
      psn=psn->nxt;
      continue;
    }
    if (!(subv->buf && subv->buf[0])) 
      add_first_rests(perc_voices+subv_nr,subv_nr,meas_nr);
    if (subv_nr==0 && subv->busy==0) {
      if (inform(meas_nr)->annot && do_annot) subv->add("\"%c\"",inform(meas_nr)->annot);
    }
    if (psn->d.time>0 && psn->d.time%(4*subdiv)==0)  // interrupt percussion-note beaming
      subv->add(" ");
    if (psn->d.triad_cat==e1_2 || psn->d.triad_cat==e1_4) subv->add("(3");
    for (psn1=psn->nxt;psn1;psn1=psn1->nxt) {
      if (perc_filter(psn1,subv_nr)/* || psn1->d.time < subv->busy*/) continue;
      if (is_perc_chord(psn,psn1)) {
        chord_notes=true;
        subv->add("[");
        break; 
      }
    }
    const char *nn=psn->d.abc_perc_note_name();
    subv->add("%s%s", nn, n_len(buf1,psn->d.dur));
    if (chord_notes) {
      for (psn1=psn->nxt;psn1;) {
        if (perc_filter(psn1,subv_nr)/* || psn1->d.time < subv->busy*/) { psn1=psn1->nxt; continue; }
        if (is_perc_chord(psn,psn1)) {
          nn=psn1->d.abc_perc_note_name();
          subv->add("%s%s",nn,n_len(buf1,psn->d.dur));
          psn1=psn_buf[meas_nr].remove(psn1);
        }
        else psn1=psn1->nxt;
      }
      subv->add("]");
    }
    subv->busy=psn->d.time + psn->d.dur;
    psn=psn_buf[meas_nr].remove(psn);
  }
}

static char *calc_svn(int voice_nr,int subv_nr) { // subvoice name
  static char buf[30];
  char name[30];
  strncpy(name,iprop[voice_nr].shortname,30);
  for (char *p=name;*p;++p) if (*p==' ') { *p=0; break; } // no spaces in name
  if (subv_nr==0) snprintf(buf,30,"%s",name);
  else snprintf(buf,30,"%s%d",name,subv_nr+1);
  return buf;
}

static char *calc_perc_svn(int subv_nr) { // perc subvoice name
  static char buf[30];
  if (subv_nr==0) strcpy(buf,perc_vn);
  else snprintf(buf,30,"%s%d",perc_vn,subv_nr+1);
  return buf;
}

void PostscriptOut::print_subvoice(int voice_nr,Voice *voice,int subv) {
  if (!voice->sv[subv].buf || !voice->sv[subv].buf[0]) return;
  fprintf(out,"V:%s\n",calc_svn(voice_nr,subv));
  if (subv==0)
    fprintf(out,"[K:%s]",maj_keys[voice->voice_key_nr]);
  fprintf(out,"%s|\n",voice->sv[subv].buf);
}

void PostscriptOut::print_perc_subvoice(SubVoice *subv,int subv_nr) {
  if (!subv->buf || !subv->buf[0]) return;
  fprintf(out,"V:%s\n",calc_perc_svn(subv_nr));
  if (subv_nr==0)
    fprintf(out,"[K:C]");
  fprintf(out,"%s|\n",subv->buf);
}

void PostscriptOut::write_ps() {
  int meas_nr,
      subv_nr,
      v_nr;
  SubVoice *subv;
  for (int c=0;c<inst_max;++c)
    voices[c].reset();
  for (int i=0;i<2;++i)
    perc_voices[i].reset();
  if ((out=fopen(abc_outfile,"w"))==0) {
    alert("abc-file '%s' not opened",abc_outfile);
    return;
  }
  for (meas_nr=0;meas_nr<=lst_ind;++meas_nr) {
    add_staccato(meas_nr);
    for (subv_nr=0;;) {
      for (int c=0;c<inst_max;++c)
        for (int lnr=0;lnr<7;++lnr)  // reset line sign inside measure to global value
          voices[c].local_lsign[lnr]=signs[voices[c].voice_key_nr][lnr];
      fill_subvoice(meas_nr,subv_nr);
      if (++subv_nr>=subv_max) break;
    }
    for (int i=0;i<inst_max;++i) { // add rest's to all subvoices
      for (subv_nr=0;subv_nr<subv_max;++subv_nr) {
        do_annot= i==first_color;
        subv=voices[i].sv+subv_nr;
        if (!(subv->buf && subv->buf[0])) continue;
        if (!s_voice || subv->busy)
          add_last_rest(subv,subv_nr,meas_nr);
      }
    }
    adept_perc_dur(meas_nr);
    for (subv_nr=0;subv_nr<2;++subv_nr) {
      fill_perc_subv(meas_nr,subv_nr);
    }
    for (int i=0;i<2;++i) { // add rest's to perc voices
      subv=perc_voices+i;
      if (!(subv->buf && subv->buf[0])) continue;
      add_last_rest(subv,subv_nr,meas_nr);
    }
    rest_is_x=false;
  }
  StringBuf program;
  program.add("X:1\n");             // start
  program.add("T:%s\n",tune_title); // title
  program.add("C:%s\n",author);     // author
  program.add("M:%d/4\n",meter*4/nupq/4);  // meter
  program.add("L:1/%d\n",4*nupq);   // unit note length
  if (debug) printf("s_voice=%d s_i_v=%d s_perc=%d\n",s_voice,s_inst_voice,s_perc);
  if (s_voice)
    program.add("P:%s\n",iprop[s_inst_voice].name);
  else if (s_perc)
    program.add("P:drums\n");
  program.add("K:%s\n",maj_keys[abc_key]); // key
  program.add("%%%%staves [");      // stave numbers
  for (v_nr=0;v_nr<inst_max;++v_nr) {
    subv=voices[v_nr].sv;
    if (subv->buf && subv->buf[0]) {
      SubVoice *sv=voices[v_nr].sv;
      if (sv[1].buf && sv[1].buf[0]) {
        alert("warning: extra voice for %s",iprop[v_nr].name);
        program.add("(");
        for (int i=0;i<subv_max && sv[i].buf && sv[i].buf[0];++i) {
          program.add("%s ",calc_svn(v_nr,i));
        }
        --program.bp; program.add(") ");
      }
      else
        program.add("%s ",calc_svn(v_nr,0));
    }   
  }
  bool is_perc=false;
  for (v_nr=0;v_nr<2;++v_nr) {
    subv=perc_voices+v_nr;
    if (subv->buf && subv->buf[0]) is_perc=true;
  }
  if (is_perc) {
    bool bracket= perc_voices[0].buf && perc_voices[0].buf[0] && perc_voices[1].buf && perc_voices[1].buf[0];
    if (bracket) program.add("(");
    for (v_nr=0;v_nr<2;++v_nr) {
      subv=perc_voices+v_nr;
      if (subv->buf && subv->buf[0]) {
        program.add("%s ",calc_perc_svn(v_nr));
      }   
    }
    if (bracket) { --program.bp; program.add(") "); }
  }
  program.bp--; program.add("]\n");
  for (v_nr=0;v_nr<inst_max;++v_nr) {   // stave name
    subv=voices[v_nr].sv;
    if (debug) printf("v=%d buf=%p svnr=%s\n",v_nr,subv->buf,calc_svn(v_nr,0));
    if (subv->buf && subv->buf[0]) {
      if (s_voice)
        program.add("V:%s clef=%s\n",calc_svn(v_nr,0),clefname[voices[v_nr].voice_clef_nr]);
      else 
        program.add("V:%s nm=\"%s\" snm=\"%s\" clef=%s\n",
                      calc_svn(v_nr,0),iprop[v_nr].name,iprop[v_nr].shortname,clefname[voices[v_nr].voice_clef_nr]);
    }
  }
  if (debug) printf("buf=%p svname=%s\n",subv->buf,calc_perc_svn(0));
  for (v_nr=0;v_nr<2;++v_nr) {  // perc stave name
    subv=perc_voices+v_nr;
    if (debug) printf("buf=%p svname=%s\n",subv->buf,calc_perc_svn(v_nr));
    if (subv->buf && subv->buf[0]) {
      if (s_perc)
        program.add("V:%s clef=perc %s\n",calc_perc_svn(v_nr),v_nr==0 ? "up" : "down");
      else if (v_nr==1)
        program.add("V:%s clef=perc down\n",calc_perc_svn(v_nr));
      else 
        program.add("V:%s nm=\"drums\" snm=\"drums\" clef=perc up\n",calc_perc_svn(v_nr));
    }
  }
  fputs(program.buf,out);
  for (v_nr=0;v_nr<inst_max;++v_nr)
    for (subv_nr=0;subv_nr<subv_max;++subv_nr)
      print_subvoice(v_nr,voices+v_nr,subv_nr);
  for (v_nr=0;v_nr<2;++v_nr)
    print_perc_subvoice(perc_voices+v_nr,v_nr);
  fclose(out);
}
