#include <stdio.h>
#include <unistd.h> // for chdir()
#include <dirent.h> // for DIR, dirent, opendir()
#include "sdl-widgets.h"
#include "bigband.h"
#include "dump-wave.h"
#include "midi-out.h"
#include "ps-out.h"
#include "str.h"
#include "midi-in.h"
#include "gm.h"
#include "templates.h"
#ifdef __AROS__
#include "AddPart.h"
#else
#include "bb-icon.xpm"
#endif

const int
  amp_mul=20000,
  patch_max=15,
  voice_max=40,
  wavef_max=50,
  SAMPLE_RATE=44100,
  sect_len=8,   // ScSection
  sclin_dist=5,
  top_yoff=14, scv_yoff=3, // y offset of score lines
  ap_yoff=8,               // y offset for alt perc  
  top_staff_yoff=top_yoff+(36-21)*sclin_dist, // idem, staff display
  nom_snr_max=44,
  scv_w=nom_snr_max*sect_len, // scoreview width
  scope_dim=128,
  scope_h=30,
  scw_w=16*keys_max,  // scales_win width
  tscale=SAMPLE_RATE/2,
  perc_shift=24, // percussion view: lnr shift
  iv_h=3*15,   // infoview height
  ap_scale=2,  // alt display, scaling vertical pos 
  signs_wid=12, // signs_view
  num_harms=14,
  ldist=13,
  harm_w=ldist*num_harms-ldist/2,  // 176, bgw_harm width
  harm_h=60;  // bgw_harm height

const float amp_arr[8]={ 0, 0.15, 0.2, 0.3, 0.5, 0.7, 1., 1.4 };

static const float
  freq_arr[9]={ 0.25, 1./3, 0.5, 1, 2, 3, 4, 5, 6 },
  det_arr[6]={ 0,0.5,1,1.4,2,3 },
  index_arr[8]={ 0, 0.5, 1, 2, 3, 5, 7, 10 },
  sta_arr[4]={ 0, 0.5, 1., 1.5 },
  mm_arr[8]  = { 0, 0.02, 0.03, 0.05, 0.1, 0.2, 0.4, 0.8 },
  mmf_arr[5]= { 0.5, 1., 2., 4., 8. },
  pan_arr[5]={ 0.,0.3,0.5,0.7,1. },
  clip_arr[5]={ 0,40000,20000,10000,5000 },
  chorus_var[4]={ 1.0005,1.001,1.002,1.004 },
  chorus_add[4]={ 0.1,   0.2,  0.4,  0.8   },
  chorus_mix[3]={ 0,0.2,0.5 };

static const int su_arr[6]={ 0,1,2,5,10,20 },
                 dcay_arr[6]={ 1,5,10,20,40,80 },
                 mf_arr[9]={ 4,3,2,1,1,1,1,1,1 },
                 meter_arr[7]={ 4,6,8,12,16,24,32 },
                 midi_pan_arr[5]={ 0,27,64,100,127 };

static const char
  *fnames[wavef_max],
#ifndef __AROS__
  *working_dir=".",
#else
  *working_dir="PROGDIR:",
#endif
  *pan_name[5]={ "<<","<","0",">",">>" },
  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
  },
  //                C        D        E   F        G        A        B   C        D         E    F          G          A
  *degree_name[]={ "1","b2","2","b3","3","4","b5","5","b6","6","b7","7","8","b9","9","b10","10","11","b12","12","b13","13" };

const char *tune_title="Opus One";
const Uint32 red=0xff0000ff,
             green=0xff00ff;

const Uint32 col2inst_color[inst_max]={
  0x0000a0ff,0x0000ffff,0x6868ffff,0x8080ffff,
  0x00a700ff,0x00d700ff,0x00ff00ff,
  0xa00000ff,0xff0000ff,0xa07020ff,0xc000c0ff,0xff6060ff
};

const Uint32 col2perc_color[perc_max]={
  0x0000a0ff,0x0000ffff,0x8080ffff,
  0x00a700ff,0x00d700ff,0x00ff00ff,
  0xff0000ff,0xff6060ff
};

enum {
  ePlayMode,
  eSleep, eNote, eGusPatch, eHarm, eSampled, ePause,  // note category
  eIdle, eTracking, eErasing, eTrackingCol, eErasingCol, eCollectSel, eCollectSel1, eScroll, // mouse states
  eMoving,
  eCopying,
  eEdit, eSelect,      // scoreview edit mode
  eMidiOut,
  ePsOut
};

enum {
  eSilent,
  ePlay,
  ePlay_x
};

int leftside,
    cur_leftside,    // set when mouse down
    meter=8,
    act_inst_nr=0,
    act_perc_nr=0,
    scv_mode=eEdit,  // scoreview edit mode
    tempo=11, // 6 - 16
    IBsize, // set by init_audio()
    cur_snr,        // current section nr, set by play_threadfun()
    scope_buf1[scope_dim],scope_buf2[scope_dim],
    nupq=4,  // note units per quarter
    repeat_end=-1,
    metersl_val=2,
    mv_key_nr=0,
    abc_key=0,
    project_format=0,  // format project file
    i_ord[inst_max],  // instr order
    p_ord[perc_max],
    i_combine[inst_max]; // instr combine in score


bool i_am_playing,
     stop_req,
     debug,
     report_values,
     read_only,
     swing_instr, swing_perc,
     d_staff,  // display staff iso piano-roll?
     d_alt,    // alternative perc display?
     info_to_term, // for 'i' key and chord hints
     skip_first_rest=false, // skip first measure if empty, for abc output
     inv_perc_rests=true; // invisible perc rests in abc output

const char *cfg_file="x.bb",
           *wav_outfile="out.wav",
           *midi_outfile="out.mid",
           *abc_outfile="out.abc",
           *midi_infile="in.mid";

const bool T=true,
           F=false;

TopWin *top_win;
HSlider *tempo_sl,
        *meter_sl,
        *nupq_sl,
        *key_sl;
ExtRButCtrl *inst_tab_ctr,
            *perc_tab_ctr,
            *patch_tab_ctr;
RButWin *triad_mode;
HScrollbar *sv_scroll;
BgrWin *scope_win,
       *scales_win,
       *signs_view,
       *help_win;
Button *play_but,
       *unselect,
       *inst_all_1,*inst_all_0,
       *perc_all_1,*perc_all_0,
       *ch_hint;
Message *txtmes2,
        *title_mes,
        *wdir_mes,
        *patch_nam,
        *midi_inst,
        *patch_info[inst_max],
        *perc_info[perc_max];
Lamp *mod_notes,
     *mod_settings;
CheckBox *inst_checkb[inst_max],
         *perc_checkb[perc_max],
         *set_repeat,
         *draw_staff,
         *alt_draw_perc,
         *set_swing, *set_swing_perc;
DialogWin *dialog;
RExtButton *inst_tabs[inst_max],
           *perc_tabs[perc_max],
           *patch_tabs[3];
CmdMenu *file_menu,
        *set_menu;

Uint8 *keystate=SDL_GetKeyState(0);

void audio_callback(Uint8 *stream, int len);
bool read_project(FILE *conf);
SDL_Thread *audio_thread,
           *wav_thread;

Uint32 cMesBg,  // light background color
       cAlert,  // alert color
       cLightBlue,
       cLightGrey;

static const float
  nA=440, nBes=466.2, nB=493.9, nC=523.3, nCis=554.4, nD=587.3,
  nDis=622.3, nE=659.3, nF=698.5, nFis=740.0, nG=784.0, nGis=830.6,
  freqs[73]= {
                                                                             nC*4,
    nB*4,nBes*4,nA*4,nGis*2 ,nG*2 ,nFis*2 ,nF*2 ,nE*2 ,nDis*2 ,nD*2 ,nCis*2 ,nC*2,
    nB*2,nBes*2,nA*2,nGis   ,nG   ,nFis   ,nF   ,nE   ,nDis   ,nD   ,nCis   ,nC  ,
    nB  ,nBes  ,nA  ,nGis/2 ,nG/2 ,nFis/2 ,nF/2 ,nE/2 ,nDis/2 ,nD/2 ,nCis/2 ,nC/2,
    nB/2,nBes/2,nA/2,nGis/4 ,nG/4 ,nFis/4 ,nF/4 ,nE/4 ,nDis/4 ,nD/4 ,nCis/4 ,nC/4,
    nB/4,nBes/4,nA/4,nGis/8 ,nG/8 ,nFis/8 ,nF/8 ,nE/8 ,nDis/8 ,nD/8 ,nCis/8 ,nC/8,
    nB/8,nBes/8,nA/8,nGis/16,nG/16,nFis/16,nF/16,nE/16,nDis/16,nD/16,nCis/16,nC/16
  };

const float
  freq_scale=2./SAMPLE_RATE,
  mid_c=nC/2;

int play_threadfun(void* data);
void restore_marked_sects(int play_start);

static int max(int a, int b) { return a>b ? a : b; }
static int min(int a, int b) { return a>b ? b : a; }
static int minmax(int a, int x, int b) { return x>b ? b : x<a ? a : x; }
//static int pad(int a,int b) { return a - a % b + b; }  // padding value a to value b
static const char* i2s(int i) {
  static char buf[10];
  snprintf(buf,10,"%d",i);
  return buf;
}

const char *set_char(int dim,...) {
  char *arr=new char[dim+1];
  int elem=0;
  va_list ap;
  va_start(ap,dim);
  while (dim--) {
    arr[elem]=va_arg(ap,int);
    elem++;
  }
  arr[elem]=-1;  // end of array
  va_end(ap);
  return arr;
}

struct Chord {
  const char *name;
  int nr_tones; // nr chord tones, e.g. 4
  const char *chord_tones;  // e.g. { 4,0,4,7,10 }
  int nr_scale_tones; // e.g. 8
  const char *scale;  // e.g. { 8,0,2,4,6,7,9,11 }
  const char *scale_name;
  bool result[12][12];
  char xx;    
  const char* chordt_list(int mode) { // mode = 1,2
    static char buf[50];
    char *p=buf;
    int dim= mode==0 ? nr_tones : nr_scale_tones;
    const char *arr= mode==0 ? chord_tones : scale;
    const char *dash="";
    *p++='[';
    for (int i=0;i<dim;++i) {
      p+=sprintf(p,"%s%s",dash,degree_name[arr[i]]);
      dash="-";
    }
    *p++=']'; *p=0;
    return buf;
  }
  void print_chord(int offset) {
    char buf[31]; buf[30]=0;
    int degree=offset-abc_key;
    if (degree<0) degree+=12;
    snprintf(buf,30,"%s%s",maj_keys[offset],name);
    printf("   %-16s%-7s",chordt_list(0),buf);
    if (degree>1) snprintf(buf,30,"(%s)",degree_name[degree]);
    else buf[0]=0;
    printf(" %-4s",buf);
    printf("  scale:%-24s",chordt_list(1));
    if (scale_name) { 
      snprintf(buf,30," '%s'",scale_name);
      printf("%-20s",buf);
    }
  }
} 
// degree      1     2     3  4     5     6     7
// semi-tones  0  1  2  3  4  5  6  7  8  9  10 11
// C scale     C     D     E  F     G     A     B

   chords[]= { { "7",   4,set_char(4,0,4,7,10),   7,set_char(7,0,2,4,5,7,8,10),   "Lydian Dominant" },
               { "7",   4,set_char(4,0,4,7,10),   7,set_char(7,0,2,4,5,7,9,10),   "Mixolydian" },
               { "7",   4,set_char(4,0,4,7,10),   6,set_char(6,0,4,5,6,7,10),     "Blues" },
               { "M7",  4,set_char(4,0,4,7,11),   7,set_char(7,0,2,4,6,7,9,11),   "Lydian" },
               { "M7",  4,set_char(4,0,4,7,11),   7,set_char(7,0,2,4,5,7,9,11),   "Major" },
               { "m7",  4,set_char(4,0,3,7,10),   7,set_char(7,0,2,3,5,7,9,10),   "Dorian" },
               { "m7",  4,set_char(4,0,3,7,10),   6,set_char(6,0,3,5,6,7,10),     "Blues" },
               { "m7b5",4,set_char(4,0,3,6,10),   7,set_char(7,0,2,3,5,6,8,10),   "Locrian #2" },
               { "dim7",4,set_char(4,0,3,6,9),    8,set_char(8,0,2,3,5,6,8,9,11), "Whole-Half" },
               { "aug7",4,set_char(4,0,4,8,11),   7,set_char(7,0,2,4,6,8,9,11),   "Lydian Augmented" },
               { "m/M7",4,set_char(4,0,3,7,11),   7,set_char(7,0,2,3,5,7,9,11),   "Melodic Minor" },
               { "7b5", 4,set_char(4,0,4,6,10),   6,set_char(6,0,2,4,6,8,10),     "Whole Tone" },
               { "7#5", 4,set_char(4,0,4,8,10),   6,set_char(6,0,2,4,6,8,10),     "Whole Tone" },
               { "7b9", 5,set_char(5,0,4,7,10,11),8,set_char(8,0,1,3,4,6,7,9,10), "Half-Whole" },
               { "7#9", 5,set_char(5,0,4,7,10,13),8,set_char(8,0,1,3,4,6,7,8,10), "Super Locrian" },
               { 0 }
             };


struct InfoView {
  BgrWin *infoview;
  int cur_meas2,  // edited measure * 2
      sel_meas,   // first of selected measures
      len;
  Info *info;
  InfoView():
      cur_meas2(-1),
      sel_meas(-1),
      len(100),
      info(new Info[len]) {
  }
  void reset() {
    cur_meas2=-1;
    sel_meas=-1;
    for (int i=0;i<len;++i) info[i].clear();
  }
  void mouse_down(int x,int y,int but);
  Info *get_info(int ind) {
    if (ind>=len) {
      int old_len=len;
      do len*=2; while (ind>=len);
      Info *new_arr=new Info[len];
      for (int i=0;i<old_len;++i)
        new_arr[i]=info[i];
      delete[] info;
      info=new_arr;
    }
    return info + ind;
  }
  void highlight(bool erase,bool txt=true) {
    if (cur_meas2<0) return;
    int x1=cur_meas2*meter*sect_len/2 - leftside * sect_len + 1,
        x2=x1 + meter * sect_len/2 - 2;
    if (x2>0 && x1<scv_w) {
      Uint32 c=erase ? 0xffffffff : 0xf0f0a0ff;
      if (cur_meas2%2==0) {
        boxColor(infoview->win,x1,iv_h/3+1,x2,2*iv_h/3,c);
        if (txt) {
          const char *text=get_info(cur_meas2/2)->chord;
          if (text) draw_ttf->draw_string(infoview->win,text,Point(x1+2,iv_h/3+1));
        }
      }
      else {
        boxColor(infoview->win,x1,2*iv_h/3+1,x2,iv_h-1,c);
        if (txt) {
          const char *text=get_info(cur_meas2/2)->chord2;
          if (text) draw_ttf->draw_string(infoview->win,text,Point(x1+2,2*iv_h/3+1));
        }
      }
      infoview->blit_upd(rp(x1,iv_h/3,x2,iv_h-1)); 
    }
  }
} iv;

struct SubSection {
  unsigned int
      cat       :2,  // ePlay, ePlay_x, eSilent
      stacc     :1,
      sel       :1,  // selected?
      signs_mode:2;  // 0, eHi, eLo
  int del_start :4,  // delay at section start
      del_end   :4;
  SubSection() { memset(this,0,sizeof(SubSection)); }
};

bool enab(int col) { return inst_checkb[col]->value(); }
bool p_enab(int col) { return perc_checkb[col]->value(); }

Info *inform(int ind) { // used in ps-out.cpp
  static Info nul;
  if (ind>=iv.len) return &nul;
  return iv.info+ind;
}

struct ScSection {
  SubSection *buf;
  ScSection():buf(0) { }
  ScSection(ScSection* sec,const int dim):buf(0) {
    if (sec && sec->buf) copy(sec,dim);
  }
  // NB! no destructor
  void check_buf(const int dim) {
    if (!buf) buf=new SubSection[dim];
  }
  void set(int col,bool play,bool stacc,int s_m) {
    check_buf(inst_max);
    if (play) {
      buf[col].cat=ePlay;
      buf[col].stacc=stacc;
      buf[col].signs_mode=s_m;
    }
    else
      buf[col].cat=eSilent;
  }
  void p_set(int col,bool play) {
    check_buf(perc_max);
    buf[col].cat= play ? ePlay : eSilent;
  }
  SubSection& get(int col,int dim) {
    check_buf(dim);
    return buf[col];
  }
  void reset() { delete[] buf; buf=0; }
  void zero(int col) {
    memset(buf+col,0,sizeof(SubSection));
  }
  void reset(int col,const int dim) {
    memset(buf+col,0,sizeof(SubSection));
    for (int i=0;i<dim;++i) {
      if (buf[i].cat==ePlay) return;
    }
    delete[] buf; buf=0;
  }
  bool more1_tc(bool (*en)(int col),const int dim) {  // more then 1 color occupied?
    bool b=false;
    for (int col=0;col<dim;++col) {
      if (buf[col].cat==ePlay && en(col)) {
        if (b) return true;
        b=true;
      }
    }
    return false;
  }
  bool occ_color(bool (*en)(int col),int col) {
    if (!buf) return false;
    if (en) return en(col) && buf[col].cat==ePlay;
    return buf[col].cat==ePlay;
  }
  bool occ(bool (*en)(int col),int* c,const int dim) {  // at least 1 color occupied?
    if (buf) {
      for (int col=0;col<dim;++col) {
        if (buf[col].cat==ePlay && en(col)) { if (c) *c=col; return true; }
      }
    }
    return false;
  }
  bool is_csel(int col) {
    return buf[col].cat==ePlay && buf[col].sel;
  }
  bool is_sel(const int dim) {
    for (int c=0;c<dim;++c) {
      if (buf[c].cat==ePlay && buf[c].sel) return true;
    }
    return false;
  }
  void copy(ScSection *from,int col,const int dim) { // sel also copied
    check_buf(dim);
    buf[col]=from->buf[col];
  }
  void copy(ScSection *from,const int dim) {
    check_buf(dim);
    for (int i=0;i<dim;++i) buf[i]=from->buf[i];
  }
  void set_sel(bool b,const int dim) {   // unselect if !b
    for (int c=0;c<dim;++c) buf[c].sel=b;
  }
  void set_csel(bool b,int col) { // unselect if !b
    buf[col].sel=b;
  }
  int get_signs_mode(const int dim) {
    for (int i=0;i<dim;++i) {
      if (enab(i) && buf[i].signs_mode) return buf[i].signs_mode;
    }
    return 0;
  }
  void draw_sect(bool isp,int lnr,int snr);
  void draw_playsect(int x,int y,int sign);
  void draw_staff_playsect(int x,int y);
  void draw_percsect(int x,int y);
  void draw_ap_sect(int c,int x,int y);
  void draw_ghost(bool isp,int lnr,int snr,bool erase);
  void draw_ghost(int lnr,int snr,bool erase);
  void draw_perc_ghost(int lnr,int snr,bool erase);
  void set_delay(int act_nr,int key);
};

struct Score {
  const char* name;
  const int sclinmax;
  int len,        // number of sections
      lst_sect;   // last written section
  ScSection *block;
  Score(int lmax);
  ScSection* get_section(int lnr,int snr);
  void reset() {
    lst_sect=-1;
    for (int snr=0;snr<len;++snr)
      for (int lnr=0;lnr<sclinmax;++lnr)
        get_section(lnr,snr)->reset();
  }
};

struct SectData { // used by struct Selected
  int snr,
      lnr;
  ScSection sect;
  SectData(int _lnr,int _snr,ScSection* sec,const int dim):
      snr(_snr),lnr(_lnr),sect(sec,dim) { }
  bool operator==(SectData& sd) { return lnr==sd.lnr && snr==sd.snr; }
};

struct Selected {
  SLinkedList<SectData> sd_list;
  const int ip_max;
  Selected(const int dim):ip_max(dim) { }
  void insert(int lnr,int snr,ScSection* sec) {
    if (!sd_list.lis) {
      unselect->style.param=4;  // rose background
      unselect->draw_blit_upd();
    }
    sd_list.insert(SectData(lnr,snr,sec,ip_max));
  }
  void remove(int lnr,int snr,ScSection *sec) {
    SLList_elem<SectData> *p=sd_list.find(SectData(lnr,snr,0,0));
    delete[] p->d.sect.buf;
    sd_list.remove(p);
    if (!sd_list.lis) {
      unselect->style.param=0;
      unselect->draw_blit_upd();
    }
    sec->set_sel(false,ip_max); // all colors deselected
  }
  void reset() {
    sd_list.reset();
    if (unselect) { // is 0 at startup
      unselect->style.param=0;
    }
  }
  void restore_sel();
  void modify_sel(struct ScoreView*,int mes,const int dim);
  int min_snr() {
    SLList_elem<SectData> *sd;
    int snr=sd_list.lis->d.snr;
    for (sd=sd_list.lis;sd;sd=sd->nxt)
      if (snr>sd->d.snr) snr=sd->d.snr;
    return snr;
  }
  void print_hint();
};

struct ScoreView {
  BgrWin *scoreview;
  const bool is_perc;    // percussion score?
  const int
     sclin_max,
     staff_sclinmax,
     &act_nr,         // = act_inst_nr or act_perc_nr
     ip_max;          // = inst_max or perc_max
  int state,             // set when mouse down
      cur_lnr, cur_snr,  // set when mouse down
      up_down_key,       // state of up- or downkey
      prev_snr,          // previous visited section nr
      delta_lnr, delta_snr,
      left_snr;          // new snr of leftmost selected section
  Point cur_point,       // set when mouse down
        prev_point;      // set when mouse moved
  ScSection proto;
  Score *score;
  Selected selected;
  ScoreView(bool ip,int lmax,int mx,int &act):
    scoreview(0),
    is_perc(ip),
    sclin_max(lmax),
    staff_sclinmax(lmax*7/12+2),
    act_nr(act),
    ip_max(mx),
    state(eIdle),
    score(new Score(sclin_max)),
    selected(ip_max) {
  }
  void mouse_down(int x,int y,int but);
  void mouse_moved(int x,int y,int but);
  void mouse_up(int x,int y,int but);
  void select_down(ScSection*,const int lnr,int snr,int but);
  void select_percdown(ScSection*,const int lnr,int snr,int but);
  void edit_down(ScSection* sec,int lnr,int snr,int but);
  void edit_percdown(ScSection* sec,int lnr,int snr,int but);
  void sel_column(int snr,int col=-1);
  void write_note(FILE *conf,int col,int lnr,int& snr);
  void write_p_note(FILE *conf,int col,int lnr,int snr);
  int linenr(int y,int& up_downkey);
  void draw_upd_repeat(int old_rend,int new_rend) {
    int x;
    repeat_end=new_rend;
    if (old_rend>=0) {
      x=(old_rend-leftside)*sect_len;
      if (x>=0 && x<scv_w) {
        filledTrigonColor(scoreview->win,x,2,x,12,x-5,7,0xffffffff);
        scoreview->blit_upd(rp(x-5,2,6,10));
      }
    }
    if (new_rend>=0) {
      x=(new_rend-leftside)*sect_len;
      if (x>=0 && x<scv_w) {
        filledTrigonColor(scoreview->win,x,2,x,12,x-5,7,0xff0000ff);
        scoreview->blit_upd(rp(x-5,2,6,10));
      }
    }
  }
} sv(false,73,inst_max,act_inst_nr), // score lines: 6 octaves + 1 note
  pv(true,25,perc_max,act_perc_nr);  // perc score lines: 2 octaves + 1 note

const int
  scv_h=(sv.sclin_max-1)*sclin_dist+top_yoff+scv_yoff,  // scoreview height
  pv_h=(pv.sclin_max-1)*sclin_dist+2*scv_yoff,   // perc scoreview height
  ap_ldist=(pv_h-2*ap_yoff)/(perc_max-1);  // alt perc view line dist

struct LineColor {
  Uint32 col[12],
         *s_col;
  LineColor() {
    int i,
        topl=11;
                    // C B   A   G   F E   D
    int line_col[12]={ 2,1,0,1,0,1,0,1,1,0,1,0 };
    for (i=0;i<12;++i) {
      int c=line_col[i];
      col[i]= c==2 ? 0xff : c==1 ? 0xb0b0b0ff : 0;
    }
    s_col=new Uint32[sv.staff_sclinmax];
    for (i=0;i<sv.staff_sclinmax;i+=2) s_col[i]=0; // white
    for (i=1;i<topl;i+=2) s_col[i]=0xd0d0d0ff;  // light grey
    for (;i<topl+10;i+=2) s_col[i]=0xff;        // black
    for (;i<topl+12;i+=2) s_col[i]=0xd0d0d0ff;  // light grey
    for (;i<topl+22;i+=2) s_col[i]=0x50ff50ff;  // green
    for (;i<sv.staff_sclinmax;i+=2) s_col[i]=0xd0d0d0ff;  // light grey
  }
} linecol;

static int sectnr(int x) { return (x+2)/sect_len + leftside; }

struct Sinus {
  static const int dim=500;
  static const float basefreq=SAMPLE_RATE/dim; // 88.2 Hz
  float buf[dim];
  Sinus() {
    for (int i=0;i<dim;++i)
      buf[i]=300*sin(2*M_PI*i/dim);
  }
  float get_value(float ind_f1,float ind_f2,int chorus_y,char *hlin);
  float calc_ind(float &ind_f,float freq) {
    const float ind_f1=ind_f;
    if (int(ind_f)>=dim) ind_f-=dim;
    ind_f += freq/basefreq;
    return ind_f1;
  }
  float calc_ind(float &ind_f,float freq,int chorus_x) {
    const float ind_f1=ind_f;
    if (int(ind_f)>=dim) ind_f-=dim;
    ind_f += (freq*chorus_var[chorus_x] + chorus_add[chorus_x])/basefreq;
    return ind_f1;
  }
} clean_sinus;

struct NoisySinus {
  static const int nr_pts=256,  // points per sample
                   nr_samp=512,
                   dim=nr_pts*nr_samp,
                   pre_dim=dim/2;
  static const float fc=2 * M_PI / nr_pts, // freq cut
                     qres=0.05,  // Q, best value
                     basefreq=SAMPLE_RATE/nr_pts; // 170 Hz
  float buf[dim],
        pre_buf[pre_dim];
  NoisySinus() {
    float white,output,
          d1=0,d2=0,d3=0,d4=0,
          n1=0,n2=0,n3=0,n4=0;
    for (int i=-pre_dim;i<dim;++i) { // such that end and begin of buf are matching
      n1=n2; n2=n3; n3=n4; n4=(float(rand())/RAND_MAX - 0.5) * 100.;
      white= i<dim-pre_dim ? (n1+2*n2+2*n3+n4)/6. :  // pre-filter, not much effect
                             pre_buf[i-dim+pre_dim];

      output=bp_section(fc,d1,d2,white);
      output=bp_section(fc,d3,d4,output);

      if (i<0)
        pre_buf[i+pre_dim]=white; 
      else
        buf[i]=output;
    }
  }
  float bp_section(const float fcut,float& d1,float& d2,const float white) {
    d2+=fcut*d1;                    // d2 = lowpass output
    float highpass=white-d2-qres*d1;
    d1+=fcut*highpass;              // d1 = bandpass output
    return d1;
  }
  float get_val(float &ind_f,struct InstrumentData *harmd,float freq);
  float get(float &ind_f,float freq) { // ind_f: 0 -> dim
    int ind=int(ind_f);
    if (ind>=dim) { ind_f-=dim; ind=int(ind_f); }
    ind_f+=freq;
    return buf[ind];
  }
} noisy_sinus;

struct WaveBuffer {
  Uint8 *buf;
  int wav_size;
  bool local_dir;
  const char *fname; // assigned with strdup()
  void load_wav();
  WaveBuffer():
    buf(0),
    wav_size(0),
    local_dir(false),
    fname(0) { }
  void reset() {
    if (buf) { SDL_FreeWAV(buf); buf=0; }
    wav_size=0;
    local_dir=false;
    if (fname) { free(const_cast<char*>(fname)); fname=0; }
  }
  bool next_val(float freq,struct NoteBuffer *nb,float *val);
} wave_buffer[perc_max];


struct Note {
  float freq;
  char cat, // eNote, eSampled, ePause, eSleep
       triad_cat, // 0, e1_4, e2_4, e3_4
       note_col,
       midi_nr,    // MIDI freq nr;
       midi_instr,
       midi_ampl,
       samp_ind,  // index of gpat_data[]
       samp_nr;   // gus sample nr
  bool slashed,
       noisy;
  int dur,
      dur1,    // for ps_out
      attack,  // eNote: startup, eHarm: attack
      decay,
      gap,     // time till next note
      remain,  // remaining time, will be re-assigned
      start,   // actual start time
      start_snr; // section nr
  void set(int col,int lnr,int cat,int snr,int start,bool slashed);
  void set_timing(int d) {
    dur=dur1=d; 
    remain=attack=decay=gap=0;
  }
  void calc_triadcat() {
    int exp_start=start_snr*subdiv,
        diff_start=start-exp_start;
    const int delta=subdiv/3;
    triad_cat=0;
    switch (dur) {
      case 2*delta:
        if (diff_start==0) triad_cat=e1_2;
        else if (diff_start==2*delta) triad_cat=e2_2;
        else if (diff_start==delta) triad_cat=e3_2;
        break;
      case 4*delta:
        if (diff_start==0) triad_cat=e1_4;
        else if (diff_start==delta) triad_cat=e2_4;
        else if (diff_start==-delta) triad_cat=e3_4;
        break;
    }
  }
  bool calc_samp_ind(int s_ind);
} *NOTES[voice_max];

struct NoteBuffer {
  int busy,     // note duration + decay
      note_lnr, // line nr of a note
      note_snr, // start of a note
      nlen,     // dimension of notes[]
      end_snr,  // ending snr of last note
      cur_note;
  char loop_state1, // 0: starting, 1: loop_start -> loop_end, 2: loop_end -> loop_start
       loop_state2;
  float ind_f1,ind_f2,
        ind_f1_m,ind_f2_m,
        ind_f_lfo,
        prev_val,  // for detecting zero crossing
        ampl;      // current amplitude
  Note *notes;
  NoteBuffer():
      nlen(20),
      loop_state1(0),loop_state2(0),
      prev_val(0),
      ampl(0),
      notes(new Note[nlen]) {
    reset();
    ind_f_lfo=0;
  }
  void reset() {
    busy=prev_val=0;
    loop_state1=0; loop_state2=0;
    ind_f1=ind_f1_m=ind_f2=ind_f2_m=0;
  }
  void renew() {
    int old_len=nlen;
    nlen*=2;
    Note *new_notes=new Note[nlen];
    for (int i=0;i<old_len;i++) new_notes[i]=notes[i];
    delete[] notes;
    notes=new_notes;
  }
  void report(int n);
};

struct NoteBuffers {
  NoteBuffer nbuf[voice_max];
  int voice_end;
  NoteBuffers() { reset(); }
  void reset() {
    voice_end=-1;
    for (int i=0;i<voice_max;i++) { nbuf[i].reset(); nbuf[i].cur_note=-1; }
  }
  bool fill_note_bufs(int play_start,int play_stop,int task);
  void find_free_voice(int col,int start_del,int& v,int& pause,int cur_meas,bool mono_ph);
  void scale_timing() {
    for (int v=0;v<=voice_end;++v) { // all timing params scaled
      Note *n=nbuf[v].notes;
      if (n->cat!=eSleep)
        for (;n-nbuf[v].notes<=nbuf[v].cur_note;++n) {
          n->dur*=tscale;
          n->remain=n->dur;
          switch (n->cat) {
            case eNote: case eGusPatch: case eHarm:
              n->attack*=tscale;
              n->decay*=tscale;
              n->gap*=tscale;
          }
        }
    }
  }
  void restore_notes() {
    for (int v=0;v<=voice_end;++v) {
      Note *n=nbuf[v].notes;
      NOTES[v]=n;
      if (n->cat!=eSleep) {
        for (;n-nbuf[v].notes<=nbuf[v].cur_note;++n) n->remain=n->dur;
      }
    }
  }
} nbufs;

float sinus2(float& x) {
  if (x>1.) x-=2.; else if (x<-1.) x+=2.;
  if (x>0.5 || x<-0.5) return 0;
  float x2=x*2.;
  if (x2>0.) return 4*x2*(1-x2);
  if (x2<0.) return 4*x2*(1+x2);
  return 0;
}

float sinus(float& x) {
  if (x>1.) x-=2.; else if (x<-1.) x+=2.;
  if (x>0.) return 4*x*(1-x);
  if (x<0.) return 4*x*(1+x);
  return 0;
}
/*
float saw(float x) {
  const float pt=0.1,
              mult=(1.-pt)/pt;
  if (x>-pt && x<pt) return mult*x;
  if (x<0) return -x-1;
  return -x+1;
}
*/
struct Lfo {
  float val;
  void nxt_value(float freq,float &ind_f) {
    ind_f += freq * freq_scale;
    val=sinus(ind_f);
  }
} lfosc;

struct InstrumentData { // no constructor, don't change order of var's
  const char *name;
  Int2 fm_startup, // d of sliders
       fm_ctrl,
       mod_mod,
       chorus;
  int detune,
      start_amp,
      startup,
      attack,
      decay,
      clip,
      patch_but;
  bool mphonic,
       sin2,
       shortdur,
       gus_aalias,
       noisy;
  char hlin[num_harms],
       click_hlin[num_harms];
  bool custom;
  void slider_cmd(int cmd,char*& text_x,char*& text_y,int val,int val_y);
  void nxt_val(float freq,NoteBuffer *nb,float *b1,float *b2) {
    float fm,
          fm_mod1=index_arr[fm_startup.y],
          fm_mod2=index_arr[fm_ctrl.y];
      freq *= freq_scale;
      if (b1) {
      float freq1=freq * mf_arr[fm_startup.x];
      nb->ind_f1_m += freq1 * freq_arr[fm_startup.x];
      fm=sinus(nb->ind_f1_m);
      nb->ind_f1 += freq1 * (1.0+fm*fm_mod1);
      *b1=sin2 ? sinus2(nb->ind_f1) : sinus(nb->ind_f1);
    }
    if (b2) {
      float freq2=freq * mf_arr[fm_ctrl.x];
      if (detune)
        nb->ind_f2_m += (freq2 + det_arr[detune]*freq_scale) * freq_arr[fm_ctrl.x];
      else nb->ind_f2_m += freq2 * freq_arr[fm_ctrl.x];
      fm=sinus(nb->ind_f2_m);
      if (mod_mod.x) { // mod mod enabled
        lfosc.nxt_value(mmf_arr[mod_mod.y],nb->ind_f_lfo);
        nb->ind_f2 += freq2 * (1.0+fm*(fm_mod2*(1.0+lfosc.val*mm_arr[mod_mod.x])));
      }
      else {
        nb->ind_f2 += freq2 * (1.0+fm*fm_mod2);
      }
      *b2=sin2 ? sinus2(nb->ind_f2) : sinus(nb->ind_f2);
    }
  }
  void draw_line(int,bool upd);
  int x2ind(int x) { return (x+ldist/2)/ldist; }
  void print_patch();
} fm_data[patch_max],
  bi_patches[] = {
  { "high reed",      Int2(4,3),Int2(2,3),Int2(0,0),Int2(0,0),0,2,1,0,2,0,0,F,F,F,F,F,{ 0,20,20,20 },{ 0 },F },  // 0
  { "low reed",       Int2(2,3),Int2(3,4),Int2(0,0),Int2(0,0),1,2,3,0,2,0,0,F,T,F,F,F,{ 0,20,20,20 },{ 0 },F },  // 1
  { "pipe",           Int2(2,2),Int2(4,3),Int2(2,4),Int2(0,0),0,1,3,0,2,0,0,F,F,F,F,F,{ 0,20,20,20 },{ 0 },F },  // 2
  { "brass",          Int2(2,3),Int2(0,4),Int2(0,0),Int2(0,0),2,2,3,0,2,0,0,F,F,F,F,F,{ 0,20,20,20 },{ 0 },F },  // 3
  { "bell",           Int2(3,4),Int2(7,7),Int2(5,3),Int2(0,0),0,2,1,0,5,0,0,F,F,T,F,F,{ 0,20,20,20 },{ 0 },F }, // 4
  { "high strings",   Int2(1,2),Int2(3,5),Int2(1,1),Int2(0,0),2,0,2,0,2,0,0,F,T,F,F,F,{ 0,20,20,20 },{ 0 },F },  // 5
  { "low strings",    Int2(2,4),Int2(5,4),Int2(1,1),Int2(0,0),2,1,4,0,2,0,0,F,T,F,F,F,{ 0,20,20,20 },{ 0 },F },  // 6
  { "piano",          Int2(2,1),Int2(3,0),Int2(0,0),Int2(0,0),0,1,1,0,5,0,0,F,T,T,F,F,{ 0,20,20,20 },{ 0 },F },  // 7
  { "organ",          Int2(5,4),Int2(5,3),Int2(2,2),Int2(1,1),0,1,3,0,3,0,2,F,F,F,F,F,
                                                                  { 0,40,40,40,0,0,40,0,0,50 },{ 0 },F },  // 8
  { "noisy organ",    Int2(5,4),Int2(5,3),Int2(2,2),Int2(0,0),0,1,3,0,3,0,2,F,F,F,F,T,
                                                           { 0,40,40,40,0,0,40,0,0,50,0,30,0,20 },{ 0 },F }, // 9
  { "el. organ",       Int2(5,4),Int2(5,3),Int2(2,2),Int2(1,1),0,1,3,3,4,0,2,F,F,F,F,F,{ 0,40,40,40,0,0,17,0,0,14,0,0,0,0 },{ 0,0,0,0,0,0,0,0,0,0,54,0,0 },F },
  { "soft guitar",    Int2(2,1),Int2(3,2),Int2(0,0),Int2(0,0),0,1,1,0,3,0,0,F,T,T,F,F,{ 0,20,20,20 },{ 0 },F },  // 11
  { "hard guitar",    Int2(1,2),Int2(3,4),Int2(4,3),Int2(0,0),0,3,2,0,2,0,0,F,T,F,F,F,{ 0,20,20,20 },{ 0 },F },  // 12
  { "soft bass",      Int2(1,2),Int2(2,3),Int2(2,2),Int2(1,1),0,2,1,0,3,2,2,F,F,F,F,F,{ 0,52,35,22,12,8,8 },{ 0 },F },  // 13
  { "hard bass",      Int2(1,5),Int2(2,5),Int2(1,2),Int2(0,0),2,3,4,0,2,0,0,F,T,F,F,F,{ 0,20,20,20 },{ 0 },F },  // 14
  { "flute",  Int2(2,2),Int2(4,3),Int2(2,4),Int2(0,0),0,1,3,0,2,0,2,F,F,F,F,F,{ 0,28,52,26,6,12,6,6 },{ 0 },F },
  { 0 }
};

InstProperty iprop[inst_max]={ { "soprano sax","sopr",2,0,0,0,56,87,   0, 65-1,30 },  // patch:0 midi: soprano
                               { "alto sax","alto",9,12,0,0,49,80,     0, 66-1,100 }, //       0       alto
                               { "tenor sax","tenor",14,12,0,0,44,75,  1, 67-1,30 },  //       1       tenor
                               { "bariton sax","barit",21,24,0,0,37,68,1, 68-1,100 }, //       1       bariton
                               { "flute","flute",0,0,0,0,60,96,        2, 74-1,30 },  //       2       flute
                               { "trumpet","trump",2,0,0,0,55,82,      3, 57-1,100 }, //       3       trumpet
                               { "trombone","tromb",0,12,1,0,40,72,    3, 58-1,30 },  //       3       trombone
                               { "piano","piano",0,0,0,0,0,0,          7, 1-1,100 },  //       7       grand piano
                               { "el. piano","epiano",0,0,0,0,0,0,      10,5-1,30 },   //       10      electric piano
                               { "organ","organ",0,0,0,0,0,0,          8, 17-1,100 }, //       8       drawbar organ
                               { "guitar","guitar",12,12,0,0,40,77,    11,27-1,30 },  //       11      jazz guitar
                               { "bass guitar","bass",12,24,1,0,28,62, 13,35-1,100 }  //       13      bass guitar
                             };

PercProperty pprop[perc_max]={ { "closed hihat",42,64,"g",eUp },
                               { "open hihat",46,64,"e",eUp },
                               { "ride cymbal",51,64,"=g",eUp },  
                               { "crash symbal",49,64,"^D",eDown },
                               { "snare",38,64,"c",eDown },
                               { "hi-mid tom",48,64,"A",eDown },
                               { "bass drum",36,64,"F",eDown },
                               { "low floor tom",41,64,"D",eDown }
                             };

struct SampleData {
  Sint16 *buffer;
  Uint16 sample_rate;
  Uint32 data_size,  // nr short int's
         loop_start,
         loop_end;
  Uint8 loop_mode;   // 0: normal, 1: pingpong, 2: up+down
  float root_freq;
  SampleData():
    buffer(0) { }
  ~SampleData() { delete[] buffer; }
  void next_val(float freq,NoteBuffer *nb,float *val,Int2 chorus);
  float nextval(float freq,char& loop_state,float& ind_f);
};

struct GusPatData {
  int nr_samp;
  SampleData *samples;
  const char *gpat_fname;
  bool aalias,
       loc_samp_dir;
  GusPatData():nr_samp(0),samples(0),gpat_fname(0),aalias(true),loc_samp_dir(false) { }
  void reset() {
    delete[] samples; // buffers deleted
    nr_samp=0; samples=0; gpat_fname=0; aalias=true; loc_samp_dir=false;
  }
  bool set_gpat(int patnr,bool loc_dir,const char *fn,bool info_only);
} gpat_data[patch_max];

float interpolate(float ind_f,short *data) {
  float integral,
        fractional;
  fractional=modff(ind_f,&integral);
  Uint32 ind=integral;
  return data[ind] * (1.-fractional) + data[ind+1] * fractional;
}

float SampleData::nextval(float freq,char& loop_state,float& ind_f) {
  float fscale = sample_rate/(root_freq*SAMPLE_RATE),
        v;
  if (loop_mode==2 && loop_state!=0)  // up+down
    v=(interpolate(ind_f,buffer)+interpolate(loop_start+loop_end-ind_f,buffer)) / 2;
  else
    v=interpolate(ind_f,buffer);
  switch (loop_mode) {
    case 0:  // normal loop
    case 2:  // up+down
      ind_f += freq*fscale;
      if (ind_f>loop_end)
        ind_f -= loop_end-loop_start;
      break;
    case 1:  // pingpong
      if (loop_state==2) {
        ind_f -= freq*fscale;
        if (ind_f<loop_start) loop_state=1;
      }
      else {
        ind_f += freq*fscale;
        if (ind_f>loop_end) loop_state=2;
        else if (loop_state==0 && ind_f > loop_start) loop_state=1;
      }
      break;
  }
  return v;
}

void SampleData::next_val(float freq,NoteBuffer *nb,float *val,Int2 chorus) { // linear interpolating
  float v1,v2;
  v1=nextval(freq,nb->loop_state1,nb->ind_f1);
  if (chorus.y) {
    v2=nextval(freq*chorus_var[chorus.x]+chorus_add[chorus.x],nb->loop_state2,nb->ind_f2);
    float mix=chorus_mix[chorus.y];
    *val=(v1*(1.-mix) + v2*mix) * 0.6;
  }
  else *val=v1;
}

bool WaveBuffer::next_val(float freq,NoteBuffer *nb,float *val) {
  int ind=int(nb->ind_f1);
  Sint16 *wav=reinterpret_cast<Sint16*>(buf);
  if (ind>=0 && ind<wav_size) {
    nb->ind_f1+=freq;
    *val=wav[ind];// * rel_perc_amp;
    return true;
  }
  return false;
}

struct Instrument {
  BgrWin *bgw;
  CheckBox *custom,
           *mphonic,
           *sin2,
           *gus_aalias,
           *loc_samp_dir,
           *gus_info,
           *shortdur,
           *noisy;
  HVSlider *fm_startup,
           *fm_ctrl,
           *mod_mod,
           *chorus;
  HSlider *detune,
          *startup,
          *attack,
          *decay,
          *clip;
  VSlider *start_amp;
  CmdMenu *fm_opt,
          *sel_patch,
          *gus_patches;
  BgrWin *bgw_harm;
  Instrument(Rect);
  bool collect_bi_patches();
  bool collect_gus_files(int inst_nr);
  void patch_slider_data(int pat_nr) {
    InstrumentData *dat=fm_data+pat_nr;
    GusPatData *gpd=gpat_data+pat_nr;
    custom->d=&dat->custom;      
    mphonic->d=&dat->mphonic;      
    sin2->d=&dat->sin2;    
    gus_aalias->d=&gpd->aalias;    
    noisy->d=&dat->noisy;
    loc_samp_dir->d=&gpd->loc_samp_dir;    
    shortdur->d=&dat->shortdur;    
    fm_ctrl->d=&dat->fm_ctrl; fm_ctrl->cmd(fm_ctrl,0,false);
    mod_mod->d=&dat->mod_mod; mod_mod->cmd(mod_mod,0,false);
    chorus->d=&dat->chorus; chorus->cmd(chorus,0,false);
    detune->d=&dat->detune; detune->cmd(detune,0,false);
    startup->d=&dat->startup; startup->cmd(startup,0,false);
    attack->d=&dat->attack; attack->cmd(attack,0,false);
    decay->d=&dat->decay; decay->cmd(decay,0,false);
    clip->d=&dat->clip; clip->cmd(clip,0,false);
    fm_startup->d=&dat->fm_startup; fm_startup->cmd(fm_startup,0,false);
    start_amp->d=&dat->start_amp; start_amp->cmd(start_amp,0,false);
    patch_nam->draw_mes(dat->name);
    patch_tab_ctr->act_lbut=patch_tabs[dat->patch_but];
    gus_patches->src->label.str= gpd->gpat_fname ? gpd->gpat_fname : "(no gus patch)";
    swap_mode(dat->patch_but);
  }
  void swap_mode(int pat_mode) {
    bool gus_mode= pat_mode==1,
         harm_mode= pat_mode==2,
         def_mode=!(gus_mode || harm_mode);
    sin2->hidden=fm_startup->hidden=fm_ctrl->hidden=mod_mod->hidden=detune->hidden=
      startup->hidden=start_amp->hidden=!def_mode;
    gus_patches->src->hidden=gus_aalias->hidden=loc_samp_dir->hidden=gus_info->hidden=!gus_mode;
    bgw_harm->hidden=attack->hidden=noisy->hidden=clip->hidden=!harm_mode;
    chorus->hidden=def_mode;
    if (sdl_running) { // false at start-up
      bgw->title=0;
      bgw->draw_blit_recur();
      bgw->upd();
      for (int i=0;i<3;++i) patch_tabs[i]->draw_blit_upd();
    }
  }
} *instrument;

InstrCtrlData ctr_data[inst_max];

int midinr_to_lnr(int mnr) {     // freqs[]: lnr=0 -> midi-nr=60+36
  int lnr=60+36-mnr;
  if (lnr<0 || lnr>=sv.sclin_max) return -1;
  return lnr;
}

int lnr_to_midinr(int lnr) { // see freqs[]: lnr=36 for mid_c
  return 60+36-lnr;
}

void lnr_to_staff_lnr(int lnr,int& s_lnr,int& 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 mnr=lnr_to_midinr(lnr),
      ind=mnr%12;
  // middle C: s_lnr=21
  //           midi: 60, 60/12*7 = 35, 21=56-35
  s_lnr=56 - mnr/12*7;
  switch (signs_mode) {
    case 0: s_lnr -= ar_0[ind]; break;
    case eHi: s_lnr -= ar_s[ind]; break;
    case eLo: s_lnr -= ar_f[ind]; break;
  }
  if (s_lnr<0 || s_lnr>=sv.staff_sclinmax) { alert("lnr_to_staff_lnr: lnr=%d?",lnr); s_lnr=0; sign=0; }
  else
    switch (signs_mode) {
      case 0: sign=s_0[ind]; break;
      case eHi: sign=s_s[ind]; break;
      case eLo: sign=s_f[ind]; break;
    }
}

void draw_range() {
  Rect *sva=&sv.scoreview->tw_area;
  Rect rect(sva->x+sva->w+2,sva->y,6,sva->h);
  top_win->clear(rect);
  if (!d_staff) { // else no range display
    int hi=iprop[act_inst_nr].hi,
        lo=iprop[act_inst_nr].lo;
    if (hi || lo) {
      int x=rect.x,
          lnr_hi=midinr_to_lnr(hi),
          lnr_lo=midinr_to_lnr(lo),
          yhi=rect.y+lnr_hi*sclin_dist+top_yoff,
          ylo=rect.y+lnr_lo*sclin_dist+top_yoff,
          col=col2inst_color[act_inst_nr];
      if (lnr_hi<0 || lnr_lo<0)
        alert("bad range %d,%d (expected %d-%d)",lo,hi,lnr_to_midinr(sv.sclin_max),lnr_to_midinr(0));
      else rectangleColor(top_win->win,x,yhi,x+5,ylo,col);
    }
  }
  if (sdl_running) update(&rect);
}

struct InstrCtrl {
  BgrWin *bgw,
         *combine_win;
  Button *name;
  CheckBox *custom,
           *add_chords,
           *slashed_notes;
  CmdMenu *inst_options;
  HSlider *patch_nr,
          *ampl,
          *midi_ampl,
          *pan;
  InstrCtrl(Rect rect);
  static const int y_dist=14;
  void inst_slider_data(int inst_nr) {
    InstrCtrlData *dat=ctr_data+inst_nr;
    InstProperty *prop=iprop+inst_nr;
    ampl->d=&dat->ampl; ampl->cmd(ampl,0,false);
    midi_ampl->d=&dat->midi_ampl; midi_ampl->cmd(midi_ampl,0,false);
    pan->d=&dat->pan; pan->cmd(pan,0,false);
    add_chords->d=&dat->add_chords;
    custom->d=&dat->custom;
    slashed_notes->d=&dat->slashed_notes;
    patch_nr->d=&prop->patch_nr;
    if (sdl_running) // 0 at startup
      patch_nr->cmd(patch_nr,1,false); // fire = 1: update patch_info[act_inst_nr]
  }
  void draw(int inst_nr) {
    bgw->title=0; // to inhibit re-writing the title
    bgw->draw_blit_recur();
    bgw->upd();
    midi_inst->draw_mes(gm_instr[iprop[inst_nr].midi_nr].name);
    draw_range();
  }
  void draw_cwin(BgrWin* bgr) {  // draw combine_win
    bgr->clear();
    rectangleColor(bgr->win,0,0,bgr->area.w-1,bgr->area.h-1,0x808080ff);
    for (int n=0;n<inst_max;++n) {
      hlineColor(bgr->win,2,inst_max*y_dist-4,n*16+7,0x808080ff);
      vlineColor(bgr->win,n*y_dist+7,2,inst_max*16-2,0x808080ff);
      filledCircleColor(bgr->win,i_combine[n]*y_dist+7,n*16+7,2,0xa00000ff);
      char text[10];
      sprintf(text,"%d",n);
      draw_ttf->draw_string(combine_win->win,text,Point(n*y_dist+5,inst_max*16-2));
    }
  }
  void mouse_down(int x,int y) {
    int i_nr=(y-2)/16,
        comb_nr=(x-2)/y_dist;
    i_combine[i_nr]=comb_nr;
    combine_win->draw_blit_recur();
    combine_win->upd();
  }
} *inst_ctr;

struct PercData {
  bool custom,
       loc_wav_dir;
  int ampl,
      midi_ampl,
      pan;
  PercData():
    custom(false),
    loc_wav_dir(false),
    ampl(5),
    midi_ampl(5),
    pan(2) { }
} perc_data[perc_max];

struct PercCtrl {
  BgrWin *bgw;
  Button *name;
  CmdMenu *sel_wavf,
          *perc_options;
  HSlider *wav_ampl,
          *wav_midi_ampl,
          *wav_pan;
  CheckBox *custom,
           *loc_wav_dir,
           *just_listen;
  PercCtrl(Rect rect);
  bool collect_wave_files();
  void perc_slider_data(int perc_nr) {
    PercData *dat=perc_data+perc_nr;
    custom->d=&dat->custom;
    loc_wav_dir->d=&wave_buffer[perc_nr].local_dir;
    const char* fn=wave_buffer[perc_nr].fname;
    sel_wavf->src->label.str= fn ? fn : "(no wave file)";
    wav_ampl->d=&dat->ampl; wav_ampl->cmd(wav_ampl,0,false);
    wav_midi_ampl->d=&dat->midi_ampl; wav_midi_ampl->cmd(wav_midi_ampl,0,false);
    wav_pan->d=&dat->pan; wav_pan->cmd(wav_pan,0,false);
  }
  void draw() {
    bgw->title=0; // to inhibit re-writing the title
    bgw->draw_blit_recur();
    bgw->upd();
  }
} *perc_ctr;

struct Scope {
  int pos,
      scope_start;
  void update(Sint16 *buffer,int i);
} scope;

struct Wave {
  SDL_AudioSpec spec;
  Uint8 *sound;
  Wave():sound(0) { }
  Uint32 soundlen,
         soundpos;
} wave;

void bgw_clear(BgrWin* bgw) { bgw->clear(); }

void draw_cwin(BgrWin* bgw) { inst_ctr->draw_cwin(bgw); }

void draw_scw(BgrWin *bgw) {
  static const char *keys[keys_max]= {  // like maj_keys[], with spaces
    " C","Db"," D","Eb"," E"," F","Gb"," G","Ab"," A","Bb"," B" };
  int ch_dist=scw_w/keys_max,
            x=mv_key_nr*ch_dist;
  bgw->clear();
  boxColor(bgw->win,x,0,x+ch_dist-1,bgw->area.h-1,0xffffffff);
  for (int i=0;i<keys_max;++i)
    draw_ttf->draw_string(bgw->win,keys[i],Point(i*ch_dist,2));
  if (abc_key!=mv_key_nr) {
    x=abc_key*ch_dist;
    rectangleColor(bgw->win,x,0,x+ch_dist-1,bgw->area.h-1,0x909090ff);
  }
}

void draw_harm_bgw(BgrWin *bgw) {
  bgw->clear();
  if (!bgw->hidden) bgw->parent->border(bgw);
  for (int i=1;i<num_harms;++i) fm_data[iprop[act_inst_nr].patch_nr].draw_line(i,false);
}

void m_notes(Uint32 col) { if (mod_notes) mod_notes->set_color(col); }
void m_sets(Uint32 col) { if (mod_settings) mod_settings->set_color(col); }

void harm_down(Id id,int x,int y,int but) {
  InstrumentData *harm=fm_data+iprop[act_inst_nr].patch_nr;
  int amp=harm_h-y,
      ind=harm->x2ind(x);
  if (ind>0 && ind<num_harms) {
    switch (but) {
      case SDL_BUTTON_LEFT:
        if (amp<5) harm->hlin[ind]=0;
        else harm->hlin[ind]=amp;
        break;
      case SDL_BUTTON_MIDDLE:
        return;
      case SDL_BUTTON_RIGHT:
        if (amp<5) harm->click_hlin[ind]=0;
        else harm->click_hlin[ind]=amp;
        break;
    }
    harm->draw_line(ind,true);
    m_sets(red);
  }
}

void draw_midc_arrow1() {
  int x=sv.scoreview->tw_area.x-12,
      y=sv.scoreview->tw_area.y+top_yoff+36*sclin_dist; // mid C for scoreview
  Rect r(x,y-3,11,8);
  aatrigonColor(top_win->win,x,y-3,x+10,y,x,y+3,0xff);
  if (sdl_running) update(&r);
}

void draw_midc_arrow2() {
  int x=pv.scoreview->tw_area.x-12,
      y=pv.scoreview->tw_area.y+scv_yoff+(36-perc_shift)*sclin_dist; // mid C for percview
  aatrigonColor(top_win->win,x,y-3,x+10,y,x,y+3,0xff);
}

void draw_topw() {
  top_win->clear();
  patch_nam->draw_label(); patch_nam->draw_mes(fm_data[iprop[0].patch_nr].name);
  for (int i=0;i<inst_max;++i)
    patch_info[i]->draw_mes("%-2d patch %d",i,iprop[i].patch_nr);
  for (int i=0;i<perc_max;++i) {
    if (wave_buffer[i].fname)
      perc_info[i]->draw_mes("%d  *",i);
    else perc_info[i]->draw_mes("%d",i);
  }
  midi_inst->draw_label(); midi_inst->draw_mes(gm_instr[iprop[0].midi_nr].name);
  title_mes->draw_label(); title_mes->draw_mes(tune_title);
  txtmes2->draw_label(); txtmes2->draw_mes(" 1 ");
  top_win->border(iv.infoview,1);
  top_win->border(sv.scoreview,1);
  top_win->border(pv.scoreview,1);
  top_win->border(scope_win,1);
  draw_midc_arrow1(); draw_midc_arrow2();
  draw_range();
  m_notes(green);
  draw_ttf->draw_string(top_win->win,"notes modified?",Point(mod_notes->rect.x+18,mod_notes->rect.y-3));
  m_sets(green);
  draw_ttf->draw_string(top_win->win,"settings modified?",Point(mod_settings->rect.x+18,mod_settings->rect.y-3));
  Rect r(ch_hint->tw_area);
  r.x-=54;
  rectangleColor(top_win->win,r.x-2,r.y-2,r.x+r.w+55,r.y+r.h+1,0xa0a0a0ff);
  Message mes(top_win,0,"keys:",Point(r.x,r.y));
  mes.draw("a, i");
}

void draw_raised(BgrWin *bgw) {
  bgw->draw_raised(0,bgw->bgcol,true);
}

InstrumentData *col2instr(int c) {
  int pnr=iprop[c].patch_nr;
  return fm_data+pnr;
}

void extb_cmd(Id id,bool is_act) {
  if (!is_act)
    return;
  switch (id.id1) {
    case 'i_nr': // instr nr
      act_inst_nr=id.id2;
      inst_ctr->inst_slider_data(act_inst_nr);
      inst_ctr->draw(act_inst_nr);
      break;
    case 'p_nr':
      act_perc_nr=id.id2;
      perc_ctr->perc_slider_data(act_perc_nr);
      perc_ctr->draw();
      if (d_alt) pv.scoreview->draw_blit_upd(); // update horizontal line
      break;
    case 'patc':
      fm_data[iprop[act_inst_nr].patch_nr].patch_but=id.id2;
      instrument->swap_mode(id.id2); // 0:fm synth, 1:gus mode, 2:harmonics mode
      m_sets(red);
      break;
    default: alert("extb_cmd?");
  }
}

void hslider_cmd(HSlider *sl,int fire,bool rel) {
  static char* dummy=0;
  switch (sl->id.id1) {
    case 'tmpo':
      set_text(sl->text,"%d",tempo*10);
      break;
    case 'nupq':
      set_text(sl->text,"%d",nupq);
      break;
    case 'metr':
      meter=meter_arr[sl->value()];
      if (fire) {
        iv.infoview->draw_blit_upd();
        sv.scoreview->draw_blit_upd();
        pv.scoreview->draw_blit_upd();
      }
      set_text(sl->text,"%d",meter);
      break;
    case 'shif':
      if (!rel) {
        scales_win->draw_blit_upd();
        if (!d_staff) sv.scoreview->draw_blit_upd();
      }
      if (rel && d_staff) {
        signs_view->draw_blit_upd();
      }
      break;
    case 'ampl':
    case 'mamp':
      if (!rel)
        set_text(sl->text,"%.2f",amp_arr[sl->value()]);
      break;
    case 'ipan':
      iprop[act_inst_nr].midi_pan=midi_pan_arr[sl->value()];
      if (!rel)
        set_text(sl->text,"%s",pan_name[sl->value()]);
      break;
    case 'ppan':
      pprop[act_perc_nr].midi_pan=midi_pan_arr[sl->value()];
      if (!rel)
        set_text(sl->text,"%s",pan_name[sl->value()]);
      break;
    case 'wamp':
    case 'mwam':
      if (!rel)
        set_text(sl->text,"%.2f",amp_arr[sl->value()]);
      break;
    case 'patn':
      if (!rel) {
        if (fire) {
          patch_info[act_inst_nr]->draw_mes("%-2d patch %d",act_inst_nr,sl->value());
          instrument->patch_slider_data(sl->value());
        }
        set_text(sl->text,"%d",sl->value());
      }
      break;
    case 'fmsu':
    case 'fmdt':
    case 'su':
    case 'dcay':
    case 'clip':
    case 'att':
      if (!rel) col2instr(act_inst_nr)->slider_cmd(sl->id.id1,sl->text,dummy,sl->value(),0);
      break;
    default:
      alert("hslider_cmd?");
  }
  switch (sl->id.id1) {
    case 'shif': break;
    default: if (rel) m_sets(red);
  }
}

void vslider_cmd(VSlider *sl,int fire,bool rel) {
  static char* dummy=0;
  switch (sl->id.id1) {
    case 'sta':
      if (!rel) col2instr(act_inst_nr)->slider_cmd(sl->id.id1,sl->text,dummy,sl->value(),0);
      break;
    default:
      alert("vslider_cmd?");
  }
  if (rel) m_sets(red);
}

InstrCtrlData::InstrCtrlData():
    ampl(5),
    midi_ampl(5),
    pan(2),
    custom(false),
    add_chords(false),
    slashed_notes(false) { }

Info::Info():annot(0),chord(0),chord2(0) { }
bool Info::occ() { return annot || chord || chord2; }
void Info::clear() { annot=0; chord=chord2=0; }

void NoteBuffers::find_free_voice(int col,int start_del,int& v,int& pause,int cur_meas,bool mono_ph) {
  int i;
  if (mono_ph) {
    for (i=0;i<voice_max;++i) {
      if (nbuf[i].cur_note<0) continue;
      Note *note=nbuf[i].notes+nbuf[i].cur_note;
      if (debug) printf("f_f_voice (mono_ph): ncat=%d ncol=%d col=%d busy=%d decay=%d\n",note->cat,note->note_col,col,nbuf[i].busy,note->decay);
      if ((note->cat==eNote || note->cat==eGusPatch || note->cat==eHarm) && note->note_col==col) {
        int gap=-nbuf[i].busy + start_del;
        if (debug) printf("mono_ph: gap=%d\n",gap);
        if (gap==0) {
          note->gap=1;
          note->dur-=1;
          pause=0; v=i;
          if (voice_end<v) voice_end=v;
          return;
        }
        if (gap>0 && note->decay>gap) {
          note->gap=gap;
          nbuf[i].busy+=gap;
          pause=0; v=i;
          if (voice_end<v) voice_end=v;
          return;
        }
        if (gap<0)
          alert("overlapping notes: %.1f units, %s, measure %d",-float(gap)/subdiv,iprop[col].name,cur_meas+1);
      }
    }
  }
  for (i=0;i<voice_max;++i) {
    Note *note= nbuf[i].cur_note>=0 ? nbuf[i].notes+nbuf[i].cur_note : 0;
    if (note) {
      if (nbuf[i].busy+note->gap<=0) {
        pause=start_del-nbuf[i].busy-note->gap;
        nbuf[i].busy+=note->gap;
        v=i;
        if (voice_end<v) voice_end=v;
        return; // free voice found
      }
    }
    else {
      pause=start_del-nbuf[i].busy;
      v=i;
      if (voice_end<v) voice_end=v;
      return; // first note
    }
  }
  alert("voices > %d at measure %d",voice_max,cur_meas);
  v=-1;
}

void insert_midi_note(int time,int time_end,int lnr,int col) {
  int n,
      snr_start=time/subdiv,
      snr_end=time_end/subdiv,
      del_start=time%subdiv,
      del_end=time_end%subdiv;
  if (time_end-time<=1) {
    alert("zero note skipped, time=%d",time/subdiv);
    if (debug) printf("ins_midi_note zero length: %d %d %d %d\n",time,time_end,lnr,col);
    return;
  }
  ScSection *to;

  if (debug) printf("ins_midi_note: time=%d t_end=%d lnr=%d col=%d\n",time,time_end,lnr,col);
  if (snr_start>0) {  // keep notes separate
    ScSection *prev=sv.score->get_section(lnr,snr_start-1);
    if (prev->occ_color(0,col)) prev->buf[col].stacc=true;
  }
  for (n=snr_start;;++n) {
    to=sv.score->get_section(lnr,n);
    to->set(col,true,false,0);
    if (n==snr_start) to->buf[col].del_start=del_start;
    if (n>=snr_end-1) { 
      to->buf[col].del_end=del_end;
      if (sv.score->lst_sect<n) sv.score->lst_sect=n;
      break;
    }
  }
}

void insert_midi_perc(int time,int col) {
  int snr_start=time/subdiv,
      del_start=time%subdiv;
  ScSection *to=pv.score->get_section(36-perc_shift,snr_start);
  if (debug) printf("ins_midi_perc: time=%d col=%d\n",time,col);
  to->p_set(col,true);
  if (pv.score->lst_sect<snr_start) pv.score->lst_sect=snr_start;
  to->buf[col].del_start=del_start;
}

Score::Score(int lmax):
    sclinmax(lmax),
    len(40),
    lst_sect(-1),
    block(new ScSection[len*sclinmax]) {
}

ScSection* Score::get_section(int lnr,int snr) {
  if (snr>=len) {
    int old_len=len;
    do len*=2; while (snr>=len);
    ScSection *new_block=new ScSection[len*sclinmax];
    for (int snr1=0;snr1<old_len;++snr1)
      for (int lnr1=0;lnr1<sclinmax;++lnr1) {
        new_block[lnr1*len+snr1].buf=block[lnr1*old_len+snr1].buf;
      }
    delete[] block;
    block=new_block;
    if (sdl_running) sv_scroll->set_range(max(sv.score->len,pv.score->len)*sect_len+20);
  }
  return block + lnr*len + snr;
}

bool Note::calc_samp_ind(int s_ind) {
  samp_ind=s_ind;
  GusPatData *pd=gpat_data+samp_ind;
  if (!pd->samples) {
    if (!pd->gpat_fname) { alert("calc_samp_ind: %s: no filename",iprop[note_col].name); return false; }
    if (!pd->set_gpat(samp_ind,pd->loc_samp_dir,pd->gpat_fname,false)) return false;
  }
  float best=10.;
  int choice=0;
  for (samp_nr=0;samp_nr < pd->nr_samp;++samp_nr) { // find best match
    float q=freq / pd->samples[samp_nr].root_freq;
    if (q<1.) q=1./q;
    if (best>q) { best=q; choice=samp_nr; }
    if (debug) printf("calc_samp_ind: freq/root_freq=%.1f best=%.1f choice=%d\n",q,best,choice);
  }
  samp_nr=choice;
  return true;
}

void Note::set(int col,int lnr,int _cat,int snr,int _start,bool _slashed) {
  note_col=col; freq=freqs[lnr]; cat=_cat; triad_cat=0;
  start_snr=snr; start=_start; slashed=_slashed;
  midi_nr=lnr_to_midinr(lnr);
  noisy=fm_data[iprop[note_col].patch_nr].noisy;
}

struct MidiNote {
  int ev_time;
  Note *note;
  bool start; // note on?
  MidiNote(Note *n,int ev,bool st):ev_time(ev),note(n),start(st) { }
  bool operator<(MidiNote &other) {
    return ev_time < other.ev_time || (ev_time==other.ev_time && !start);
  }
  bool operator==(MidiNote &other) { return false; }   // always insert
};

SLinkedList<MidiNote> midi_events[inst_max],
                      midi_perc_events;

void MidiOut::make_midi() {
  Note *note;
  int voice,
      col,
      nr_tracks=0,
      nr;
  for (voice=0;voice<=nbufs.voice_end;++voice) {
    for (note=NOTES[voice];note;++note) {
      switch (note->cat) {
        case ePause:
          break;
        case eSleep:
          goto next_voice;
        case eSampled:
          midi_perc_events.ord_insert(MidiNote(note, note->start, true));
          break;
        case eNote:
        case eGusPatch:
        case eHarm:
          midi_events[note->note_col].ord_insert(MidiNote(note, note->start, true));
          midi_events[note->note_col].ord_insert(MidiNote(note, note->start+note->dur, false));
          break;
      }
    }
    next_voice:;
  }
  for (col=0;col<inst_max;++col)
    if (midi_events[col].lis) ++nr_tracks;
  if (midi_perc_events.lis)
    ++nr_tracks;
  init2(nr_tracks+1); // track nr 1 used for initialisation
  write_track1();
  close_track();
  SLList_elem<MidiNote> *mn;
  for (nr=0,col=0;col<inst_max;++col) {
    mn=midi_events[col].lis;
    if (mn) {
      init_track(++nr,col);
      for (;mn;mn=mn->nxt) {
        note=mn->d.note;
        note_onoff(mn->d.start, col, note->midi_instr,
                   false, mn->d.ev_time, note->midi_nr, note->midi_ampl);
      }
      close_track();
      midi_events[col].reset();
    }
  }
  mn=midi_perc_events.lis;
  if (mn) {
    init_perc_track(++nr);
    for (;mn;mn=mn->nxt) {
      note=mn->d.note;
      note_onoff(mn->d.start, note->note_col, note->midi_instr,
                 true, mn->d.ev_time, note->midi_nr, note->midi_ampl);
    }
    close_track();
    midi_perc_events.reset();
  }
  close();
  dialog->dialog_label("midi file created");
}

bool PostscriptOut::create_postscript(int transpose_mode) {
  int i,icnt=0,pcnt=0;
  s_inst_voice=-1;
  bool test[inst_max];
  for (i=0;i<inst_max;++i) test[i]=false;
  for (i=0;i<inst_max;++i) 
    if (inst_checkb[i]->value()) test[i_combine[i]]=true;
  for (i=0;i<inst_max;++i) {
    if (test[i]) ++icnt;
    if (icnt==1 && s_inst_voice<0) s_inst_voice=i;
    if (icnt>1) break;
  }
  for (i=0;i<perc_max;++i)
    if (perc_checkb[i]->value()) { pcnt=1; break; }
  s_voice= icnt==1 && pcnt==0;
  s_perc= pcnt==1 && icnt==0;
  lst_ind=-1;
  set(transpose_mode,skip_first_rest,inv_perc_rests);

  for (int voice=0;voice<=nbufs.voice_end;++voice) {
    for (Note *note=NOTES[voice];note;++note) {
      switch (note->cat) {
        case ePause:
          break;
        case eSleep:
          goto next_voice;
        case eSampled:
          insert_perc(note->note_col,note->start,note->triad_cat);
          break;
        case eNote:
        case eGusPatch:
        case eHarm:
          if (note->slashed)
            insert_slashed(note->note_col,note->start,note->triad_cat);
          else {
            int transp= transpose_mode==1 ? iprop[note->note_col].transpose : iprop[note->note_col].transpose2;
            insert(i_combine[note->note_col],note->start,note->midi_nr+transp,note->dur1,note->triad_cat);
          }
          break;
      }
    }
    next_voice:;
  }
  write_ps();
  reset_ps();
  dialog->dialog_label("abc file created");
  return true;
}

const char* samples_dir(bool local) {
  return local ? "samples" : "PROGDIR:samples";
}

void WaveBuffer::load_wav() {
  if (!fname) { alert("load_wav: fname = 0"); return; }
  char *path=new char[strlen(samples_dir(local_dir))+strlen(fname)+2];
#ifndef __AROS__
  sprintf(path,"%s/%s",samples_dir(local_dir),fname);
#else
  sprintf(path,"%s",samples_dir(local_dir));
  AddPart(path, fname, strlen(samples_dir(local_dir))+strlen(fname)+2 );
#endif
  SDL_AudioSpec wav_spec;
  Uint32 size;
  Uint8 *wav_buffer;
  if (!SDL_LoadWAV(path,&wav_spec, &wav_buffer, &size)) {
    alert("Problems:\n %s",SDL_GetError());
    return;
  }
  if (wav_spec.channels>1) { alert("channels = %d, should be 1",wav_spec.channels); return; }
  if (buf) SDL_FreeWAV(buf);
  delete[] path;
  buf=wav_buffer;
  wav_size=size/2;
}

int play_wave(void* data) {
  if ( SDL_OpenAudio(&wave.spec, 0) < 0 ) {
    alert(SDL_GetError());
    SDL_FreeWAV(wave.sound);
    return 0;
  }
  SDL_PauseAudio(0);
  while (!stop_req && SDL_GetAudioStatus() == SDL_AUDIO_PLAYING)
    SDL_Delay(100);
  SDL_CloseAudio();
  SDL_FreeWAV(wave.sound); wave.sound=0;
  i_am_playing=false;
  return 0;
}

void fill_stream(void *, Uint8 *stream, int len) {
  Uint8 *waveptr;
  int    waveleft;
  waveptr = wave.sound + wave.soundpos;
  waveleft = wave.soundlen - wave.soundpos;
  if (waveleft <= len) {
    SDL_memcpy(stream, waveptr, waveleft);
    stop_req=true;
  }
  else {
    SDL_memcpy(stream, waveptr, len);
    wave.soundpos += len;
    if (wave.soundpos>wave.soundlen) stop_req=true;
  }
}

void selw_cmd(Id id,int nr,int fire) {
  if (perc_ctr->just_listen->value()) {
    if (i_am_playing) { // restart
      wave.soundpos=0; return;
    }
    i_am_playing=true;
    char path[100];
#ifndef __AROS__
    snprintf(path,100,"%s/%s",samples_dir(true),fnames[nr]);
#else
    sprintf(path,"%s",samples_dir(true));
    AddPart(path, fnames[nr], 100 );
#endif
    if (wave.sound) { SDL_FreeWAV(wave.sound); wave.sound=0; }
    if (SDL_LoadWAV(path, &wave.spec, &wave.sound, &wave.soundlen) == 0) {
      alert(SDL_GetError());
      return;
    }
    wave.spec.callback = fill_stream;
    wave.soundpos=0;
    stop_req=false;
    SDL_CreateThread(play_wave,0);
  }
  else {
    wave_buffer[act_perc_nr].fname=perc_ctr->sel_wavf->src->label.str=strdup(fnames[nr]);
    perc_ctr->sel_wavf->close();
    wave_buffer[act_perc_nr].load_wav();
    perc_info[act_perc_nr]->draw_mes("%d  *",act_perc_nr);
  }
}

void sel_gpat_cmd(Id,int nr,int) {  // select gus patch
  int patnr=iprop[act_inst_nr].patch_nr;
  GusPatData *gpd=gpat_data+patnr;
  if (instrument->gus_info->value()) {
    GusPatData gpdata;
    gpdata.set_gpat(0,gpd->loc_samp_dir,fnames[nr],true);
    delete[] gpdata.samples;
  }
  else {
    gpd->set_gpat(patnr,gpd->loc_samp_dir,fnames[nr],false);
    instrument->gus_patches->src->label.str= gpd->gpat_fname ? gpd->gpat_fname : "(no gus patch)";
    instrument->gus_patches->close();
  }
}

bool GusPatData::set_gpat(int patnr,bool loc_dir,const char *fn,bool info_only) {
  char *path=new char[strlen(samples_dir(loc_dir))+strlen(fn)+2];
#ifndef __AROS__
  sprintf(path,"%s/%s",samples_dir(loc_dir),fn);
#else
  sprintf(path,"%s",samples_dir(loc_dir));
  AddPart(path, fn, strlen(samples_dir(loc_dir))+strlen(fn)+2 );
#endif
  FILE *pat=fopen(path,"r");
  if (!pat) { alert("patch file not found:\n  %s",path); return false; }
  char word[100];
  Uint8 dum8,
        bbp_format;
  if (fread(word,8,1,pat)!=1 || strncmp(word,"BB-PATCH",8)) { alert("%s not a bb patch file",fn); return false; }
  if (fread(&bbp_format,1,1,pat)!=1 || (bbp_format!=1 && bbp_format!=2)) { alert("unexpected format %d in %s",bbp_format,fn); return false; }
  if (fread(word,1,100,pat)!=100) { alert("comment?"); return false; }
  if (info_only) {
    if (info_to_term) printf("patch info:\n  # %s\n  format: %d\n",word,bbp_format);
    else alert("patch info:\n  # %s",word);
  }
  if (fread(&dum8,1,1,pat)!=1 || dum8>20) { alert("%d samples",dum8); return false; }
  nr_samp=dum8;
  if (debug) printf("set_gpat: comment:\"%s\" format=%d nr_samp=%d\n",word,bbp_format,nr_samp);
  if (!info_only) gpat_data[patnr].gpat_fname=strdup(fn);
  delete[] samples; // buffers deleted
  samples=new SampleData[nr_samp];
  for (int samp_nr=0;samp_nr<nr_samp;++samp_nr) {
    SampleData *sdat=samples+samp_nr;
    sdat->loop_mode=0; // in case bbp_format = 1
    Uint32 root_freq;
    Uint16 scale_freq=64,
           scale_fact=1024;
    if (fread(&sdat->data_size,4,1,pat)!=1 ||
        fread(&sdat->loop_start,4,1,pat)!=1 ||
        fread(&sdat->loop_end,4,1,pat)!=1 ||
        (bbp_format==2 && fread(&sdat->loop_mode,1,1,pat)!=1) ||
        fread(&sdat->sample_rate,2,1,pat)!=1 ||
        fread(&root_freq,4,1,pat)!=1) {
      alert("read problem");
      return false;
    }
    if (bbp_format==1) {
      if (fread(&scale_freq,2,1,pat)!=1 ||
          fread(&scale_fact,2,1,pat)!=1) {
        alert("read problem (format 1)");
        return false;
      }
    }
    if (debug) printf("samp_nr=%d s_rate=%d d_size=%u l_start=%u l_end=%u l_mode=%d root_f=%u sc_freq=%u sc_fact=%u\n",
      samp_nr,sdat->sample_rate,sdat->data_size,sdat->loop_start,sdat->loop_end,sdat->loop_mode,root_freq,scale_freq,scale_fact);
    if (sdat->data_size>200000) { alert("data size %u?",sdat->data_size); return false; }
    if (scale_freq<10 || scale_freq>200) { alert("scale freq %u?",scale_freq); return false; }
    static const char *lm[3]={ "normal","pingpong","up+down" };
    if (info_only) {
      if (info_to_term ) printf("  sample %d:\n    root-freq=%.1f loop-mode=%s loop-start=%d loop-end=%d\n",
        samp_nr,float(root_freq)/scale_fact,lm[sdat->loop_mode],sdat->loop_start,sdat->loop_end);
      else alert("  sample %d:\n    root-freq=%.1f loop-mode=%s",samp_nr,float(root_freq)/scale_fact,lm[sdat->loop_mode]);
    }
    sdat->buffer=new Sint16[sdat->data_size];
    if (fread(sdat->buffer,2,sdat->data_size,pat)!=sdat->data_size) { alert("buffer fill problem"); return false; }
    if (aalias)
      for (Uint32 i=0;i<sdat->data_size-3;++i) sdat->buffer[i]=(sdat->buffer[i]+2*sdat->buffer[i+1]+2*sdat->buffer[i+2]+sdat->buffer[i+3])/6;
    if (debug)
      printf("buffer data: %d %d %d %d ...\n",sdat->buffer[0],sdat->buffer[0],sdat->buffer[0],sdat->buffer[0]);
    sdat->root_freq=float(root_freq)/scale_fact;
    if (info_only) {
      delete[] sdat->buffer;
      sdat->buffer=0;
    }
  }
  return true;
}

int compare_fn(const void* a,const void* b) {
  const char *const *sa= reinterpret_cast<const char* const*>(a),
             *const *sb= reinterpret_cast<const char* const*>(b);
  return strcmp(*sa,*sb);
}

bool fill_fname_array(int &fname_nr,const char* samples_dir,const char* extension) {
  dirent *dir;
  bool file_found=false;
  char *ext;
  fname_nr=-1;
  DIR *dp=opendir(samples_dir);
  if (!dp) { alert("directory '%s' not found",samples_dir); return false; }
  while ((dir=readdir(dp))!=0) {
    if (!dir->d_ino) { alert("d_ino?"); continue; }
    ext=strrchr(dir->d_name,'.');
    if (!ext || strcmp(ext,extension)) continue;
    file_found=true;
    if (fname_nr==wavef_max-1) { alert("files > %d",wavef_max); return false; }
    ++fname_nr;
    if (fnames[fname_nr]) free(const_cast<char*>(fnames[fname_nr]));
    fnames[fname_nr]=strdup(dir->d_name);
    if (debug) printf("fill_fname_arr: %s nr=%d\n",fnames[fname_nr],fname_nr);
  }
  closedir(dp);
  if (file_found)
    qsort(fnames,fname_nr+1,sizeof(char*),compare_fn);
  return file_found;
}

bool PercCtrl::collect_wave_files() {
  int fname_nr;
  if (!fill_fname_array(fname_nr,samples_dir(loc_wav_dir->value()),".wav")) {
    alert("no files found in directory '%s'",samples_dir(loc_wav_dir->value()));
    return false;
  }
  int wid=50,
      nr;
  for (nr=0;nr<=fname_nr;++nr)
    wid=max(draw_ttf->text_width(fnames[nr]),wid);
  if (!sel_wavf->init(wid+6,fname_nr+1,selw_cmd)) return false;
  for (nr=0;nr<=fname_nr;++nr)
    sel_wavf->add_mbut(fnames[nr]);
  sel_wavf->buttons->maybe_z=false;  // overruling CmdMenu::init()
  return true;
}

bool Instrument::collect_gus_files(int inst_nr) {
  int fname_nr;
  bool loc_dir=gpat_data[iprop[inst_nr].patch_nr].loc_samp_dir;
  if (!fill_fname_array(fname_nr,samples_dir(loc_dir),".bb-pat")) {
    alert("no files found in directory '%s'",samples_dir(loc_dir));
    return false;
  }
  int wid=50,
      nr;
  for (nr=0;nr<=fname_nr;++nr)
    wid=max(draw_ttf->text_width(fnames[nr]),wid);
  if (!gus_patches->init(wid+6,fname_nr+1,sel_gpat_cmd)) return false;
  for (nr=0;nr<=fname_nr;++nr)
    gus_patches->add_mbut(fnames[nr]);
  gus_patches->buttons->maybe_z=false;  // overruling CmdMenu::init()
  return true;
}

void InstrumentData::slider_cmd(int cmd,char*& text_x,char*& text_y,int val,int val_y) {
  switch (cmd) {
    case 'mmod':
      set_text(text_x,"%.2f",mm_arr[val]);
      set_text(text_y,"%.1fHz",mmf_arr[val_y]);
      break;
    case 'chrs':
      set_text(text_x,"%.3f",chorus_var[val] + chorus_add[val]/mid_c);
      set_text(text_y,"%.1f",chorus_mix[val_y]);
      break;
    case 'fm':
      set_text(text_x,"%.2f/%.1f",freq_arr[val],index_arr[val_y]);
      break;
    case 'fmsu':
      set_text(text_x,"%.2f/%.1f",freq_arr[val],index_arr[val_y]);
      break;
    case 'su':
      set_text(text_x,"%d",su_arr[val]);
      break;
    case 'dcay':
    case 'att':
      set_text(text_x,"%d",dcay_arr[val]);
      break;
    case 'clip':
      if (val) set_text(text_x,"%.1f",clip_arr[val]/20000);
      else set_text(text_x,"(no)");
      break;
    case 'sta':
      set_text(text_x,"%.1f",sta_arr[val]);
      break;
    case 'fmdt':
      set_text(text_x,"%.1fHz",det_arr[val]);
      break;
  }
}

void sel_patch_cmd(Id id,int nr,int fire) {
  instrument->sel_patch->close();
  int patnr=iprop[act_inst_nr].patch_nr;
  fm_data[patnr]=bi_patches[nr];
  instrument->patch_slider_data(patnr);
  patch_nam->draw_mes(fm_data[patnr].name);
}

void gus_patch_cmd(Id id,int nr,int fire) {
  instrument->gus_patches->close();
}

bool Instrument::collect_bi_patches() {
  int nr,
      wid=50;
  for (nr=0;bi_patches[nr].name;++nr)
    wid=max(draw_ttf->text_width(bi_patches[nr].name),wid);
  const int dim=nr;
  if (!sel_patch->init(wid+6,dim,sel_patch_cmd)) return false;
  for (nr=0;nr<dim;++nr)
    sel_patch->add_mbut(bi_patches[nr].name);
  return true;
}

void hvslider_cmd(HVSlider *sl,int fire,bool rel) {
  switch (sl->id.id1) {
    case 'fm':
    case 'fmsu':
    case 'mmod':
    case 'chrs':
      if (!rel) col2instr(act_inst_nr)->slider_cmd(sl->id.id1,sl->text_x,sl->text_y,sl->value().x,sl->value().y);
      break;
    default:
      alert("hvslider_cmd?");
  }
  if (rel) m_sets(red);
}

void right_arrow(SDL_Surface *win,int par,int y_off) { aatrigonColor(win,6,6,12,10,6,14,0xff); }

void square(SDL_Surface *win,int par,int y_off) { rectangleColor(win,6,7,12,13,0xff0000ff); }

int ScoreView::linenr(int y,int& ud_key) {
  ud_key=0;
  if (is_perc) { 
    if (d_alt) return (y-ap_yoff-act_perc_nr*ap_ldist)*ap_scale+pv.sclin_max/2; // see: draw_percview()
    return (y-scv_yoff+2)/sclin_dist;
  }
  if (!d_staff) return (y-top_yoff+2)/sclin_dist;
  if (y<top_staff_yoff) return -1;
  static const int ind[7]={ 0,1,3,5,7,8,10 };
  int i=(y-top_staff_yoff+2)/sclin_dist,
      lnr=ind[i%7]+i/7*12;
  if (keystate[SDLK_UP]) { --lnr; ud_key=eHi; }
  else if (keystate[SDLK_DOWN]) { ++lnr; ud_key=eLo; }
  return lnr;
}

void ScoreView::write_note(FILE *conf,int col,int lnr,int& snr) {
  ScSection *sec=score->get_section(lnr,snr),
            *fst_sec=sec;
  if (sec->occ_color(0,col)) {
    int snr1=snr;
    for (;;++snr) {
      sec=score->get_section(lnr,snr);
      if (snr==score->lst_sect) {
        fprintf(conf," l%ds%dd%dc%ds0m%d",lnr,snr1,snr-snr1+1,col,sec->buf[col].signs_mode);
        break;
      }
      bool stacc=sec->buf[col].stacc;
      if (stacc) {
        if (!score->get_section(lnr,snr+1)->occ_color(enab,col)) stacc=false;
        fprintf(conf," l%ds%dd%dc%ds%dm%d",lnr,snr1,snr-snr1+1,col,stacc,sec->buf[col].signs_mode);
        break;
      }
      if (!score->get_section(lnr,snr+1)->occ_color(enab,col)) {
        fprintf(conf," l%ds%dd%dc%ds0m%d",lnr,snr1,snr-snr1+1,col,sec->buf[col].signs_mode);
        break;
      }
    }
    if (fst_sec->buf[col].del_start || fst_sec->buf[col].del_end) {
      fprintf(conf,"b%de%d",fst_sec->buf[col].del_start,fst_sec->buf[col].del_end);  // begin-delay, end-delay
    }
  }
}

void ScoreView::write_p_note(FILE *conf,int col,int lnr,int snr) {
  ScSection *sec=score->get_section(lnr,snr);
  if (sec->occ_color(0,col)) {
    fprintf(conf," l%ds%dc%d",lnr,snr,col);
    if (sec->buf[col].del_start || sec->buf[col].del_end)
      fprintf(conf,"b%de%d",sec->buf[col].del_start,sec->buf[col].del_end);
  }
}

bool save_project(FILE *conf) {
  int i;
  fputs("Settings\n",conf);
  fprintf(conf,"  format=1 meter=%d tempo=%d nupq=%d key=%d skip-fr=%d inv-p-rest=%d swing-instr=%d swing-perc=%d title=\"%s\" author=\"%s\"",
    meter_sl->value(),tempo,nupq,abc_key,skip_first_rest,inv_perc_rests,swing_instr,swing_perc,tune_title,author);
  fprintf(conf," combine=");
  for (i=0;;++i) {
    fprintf(conf,"%d",i_combine[i]);
    if (i<inst_max-1) putc(',',conf);
    else { putc('\n',conf); break; }
  }
  fputs("Patches\n",conf);
  for (i=0;i<patch_max;++i) {
    InstrumentData *fmd=fm_data+i;
    GusPatData *gpd=gpat_data+i;
    if (fmd->custom) {
      fprintf(conf,"  nr=%d name=\"%s\" fm_startup=%d,%d fm_ctrl=%d,%d mod_mod=%d,%d chorus=%d,%d",
        i,fmd->name,fmd->fm_startup.x,fmd->fm_startup.y,fmd->fm_ctrl.x,fmd->fm_ctrl.y,fmd->mod_mod.x,fmd->mod_mod.y,fmd->chorus.x,fmd->chorus.y);
      fprintf(conf," detune=%d start_amp=%d startup=%d attack=%d decay=%d clip=%d patch_but=%d",
        fmd->detune,fmd->start_amp,fmd->startup,fmd->attack,fmd->decay,fmd->clip,fmd->patch_but);
      fprintf(conf," mphonic=%d sin2=%d short=%d a-alias=%d noisy=%d harms=",
        fmd->mphonic,fmd->sin2,fmd->shortdur,gpd->aalias,fmd->noisy);
      for (int n=1;;++n) {
        fprintf(conf,"%d",fmd->hlin[n]); 
        if (n==num_harms-1) break;
        putc(',',conf);
      }
      fprintf(conf," click-harms=");
      for (int n=1;;++n) {
        fprintf(conf,"%d",fmd->click_hlin[n]); 
        if (n==num_harms-1) break;
        putc(',',conf);
      }
      if (gpd->gpat_fname) fprintf(conf," local-dir=%d gpat-fn=\"%s\"",gpd->loc_samp_dir,gpd->gpat_fname);
      fputc('\n',conf);
    }
  }
  fputs("Instruments\n",conf);
  for (i=0;i<inst_max;++i) {
    InstrCtrlData *icd=ctr_data+i;
    InstProperty *prop=iprop+i;
    if (icd->custom) {
      fprintf(conf,"  nr=%d name=\"%s\" shortname=\"%s\" ",i,prop->name,prop->shortname);
      fprintf(conf,"ampl=%d midi-ampl=%d pan=%d chords=%d slashed=%d patch_nr=%d transpose=%d transp2=%d range=%d,%d ",
              icd->ampl,icd->midi_ampl,icd->pan,icd->add_chords,icd->slashed_notes,prop->patch_nr,prop->transpose,prop->transpose2,prop->lo,prop->hi);
      fprintf(conf,"clef=%d clef2=%d gm-prog-nr=%d\n",prop->clef,prop->clef2,prop->midi_nr+1);
    }
  }
  fputs("Percussion\n",conf);
  for (i=0;i<perc_max;++i) {
    PercData *pd=perc_data+i;
    PercProperty *prop=pprop+i;
    WaveBuffer *wbuf=wave_buffer+i;
    if (pd->custom) {
      fprintf(conf,"  nr=%d name=\"%s\" ampl=%d midi_ampl=%d pan=%d gmidi-nr=%d abc-sym=\"%s,%d\"",
        i,prop->name,pd->ampl,pd->midi_ampl,pd->pan,prop->perc_nr,prop->abc_sym,prop->abc_stem);
      if (wbuf->fname) fprintf(conf," local-dir=%d file=\"%s\"",wbuf->local_dir,wbuf->fname);
      fputc('\n',conf);
    }
  }
  if (sv.score->lst_sect<0 && pv.score->lst_sect<0)
    return true;
  fputs("Tune\n ",conf);
  if (sv.score->lst_sect>=0) {
    for (int lnr=0;lnr<sv.sclin_max;++lnr) {
      for (int col=0;col<inst_max;++col) {
        for (int snr=0;snr<=sv.score->lst_sect;++snr)
          sv.write_note(conf,col,lnr,snr); // may increase snr
      }
    }
  }
  if (pv.score->lst_sect>=0) {
    for (int lnr=0;lnr<pv.sclin_max;++lnr) {
      for (int col=0;col<perc_max;++col) {
        for (int snr=0;snr<=pv.score->lst_sect;++snr)
          pv.write_p_note(conf,col,lnr,snr);
      }
    }
  }
  putc('\n',conf);
  bool found=false;
  for (i=0;i<iv.len;++i) {
    Info *inf=iv.info+i;
    if (!inf->annot && !inf->chord && !inf->chord2) continue;
    if (!found) { found=true; fputs("Annotations\n ",conf); }
    fprintf(conf," m%da%c",i,
                 inf->annot ? inf->annot : '-');
    if (inf->chord) fprintf(conf,"c\"%s\"",inf->chord);
    else fputs("c-",conf);
    if (inf->chord2) fprintf(conf,"c2\"%s\"",inf->chord2);
    else fputs("c2-",conf);
  }
  if (found) putc('\n',conf);
  return true;
}

void InstrumentData::print_patch() {
  printf("  { \"%s\",  Int2(%d,%d),Int2(%d,%d),Int2(%d,%d),Int2(%d,%d)",
    name,fm_startup.x,fm_startup.y,fm_ctrl.x,fm_ctrl.y,mod_mod.x,mod_mod.y,chorus.x,chorus.y);
  printf(",%d,%d,%d,%d,%d,%d,%d",
    detune,start_amp,startup,attack,decay,clip,patch_but);
  printf(",%c,%c,%c,F,%c,{ 0,",
    mphonic?'T':'F',sin2?'T':'F',shortdur?'T':'F',noisy?'T':'F');
  for (int n=1;;++n) {
    printf("%d",hlin[n]); 
    if (n==num_harms-1) break;
    putchar(',');
  }
  printf(" },{ ");
  for (int n=1;;++n) {
    printf("%d",click_hlin[n]); 
    if (n==num_harms-1) break;
    putchar(',');
  }
  puts(" },F },");
}

int wav_threadfun(void*) {
  scope.scope_start=0;
  Uint8 buf[1024];
  IBsize=1024/4;
  stop_req=false;
  while (i_am_playing && !stop_req) {
    audio_callback(buf,1024);
    if (!dump_wav((char*)buf,1024)) break;
  }
  close_dump_wav();
  i_am_playing=false;
  return 0;
}

bool write_wave(const char *wavf) {
  if (i_am_playing) return false;
  if (!init_dump_wav(wavf,2,SAMPLE_RATE)) return false;
  i_am_playing=true;
  nbufs.reset();
  if (!nbufs.fill_note_bufs(leftside,0,0)) {
    i_am_playing=false;
    return false;
  }
  nbufs.scale_timing();
  wav_thread=SDL_CreateThread(wav_threadfun,0);
  return true;
}

bool write_midi(const char *midf) {
  if (i_am_playing) return false;
  // tempo = 12 -> 120 beats/minute -> midi tempo event value = 500000;
  if (!midi_out.init(midi_outfile,subdiv,12*500000/tempo/nupq))
    return false;
  if (!nbufs.fill_note_bufs(leftside,0,eMidiOut))
    return false;
  midi_out.make_midi();
  midi_out.close();
  for (int col=0;col<inst_max;++col)
    midi_events[col].reset();
  midi_perc_events.reset();
  return true;
}

bool write_abc(const char *abcf,int transpose_mode) {
  if (i_am_playing) return false;
  if (!nbufs.fill_note_bufs(0,0,ePsOut))  // not start at leftside, else measure nr incorrect
    return false;
  ps_out.reset_ps();
  return ps_out.create_postscript(transpose_mode);
}

int s2keynr(const char *s) {
  for (int k=0;k<keys_max;++k)
    if (!strcmp(s,maj_keys[k])) return k;
  alert("s2keynr: unknown key %s",s);
  return -1;
}

const char *new_ext(const char *fn,const char *ext) {
  char *newf=new char[strlen(fn)+strlen(ext)+1];
  strcpy(newf,fn);
  char *p=strrchr(newf,'.');
  if (p) *p=0;
  return strcat(newf,ext);
}

static void import_midi(const char *midi_f) {
  const char *mapf=new_ext(midi_f,".gm-map");

  FILE *gm_map=fopen(mapf,"r");
  if (gm_map) {
    midi_in.read_mapf=true;
    fclose(gm_map);
  }
  else {
    midi_in.read_mapf=false;
  }
  sv.selected.reset(); pv.selected.reset();
  unselect->draw_blit_upd();
  sv.score->reset(); pv.score->reset(); iv.reset();

  if (midi_in.read_mf(midi_f,mapf)) { // read_mf() sets meter and nupq
    if (midi_in.read_mapf) {
      dialog->dialog_label("midi file read");
      for (int i=0;;++i) { // calculate meter
        if (meter_arr[i]==meter) {
          metersl_val=i;
          meter_sl->cmd(meter_sl,0,false); // draw infoview and scoreview's
          break;
        }
        if (i==5) { alert("meter=%d?",meter); break; }
      }
      nupq_sl->cmd(nupq_sl,0,false);
    }
    else {
      static char mes[50];
      snprintf(mes,50,"edit %s",mapf);
      dialog->dialog_label(mes,cLightBlue);
    }
  }
  else {  // clear infoview and scoreview's
    iv.infoview->draw_blit_upd();
    sv.scoreview->draw_blit_upd();
    pv.scoreview->draw_blit_upd();
  }
}

void reset_all() {
  project_format=0;
  sv.selected.reset(); pv.selected.reset();
  unselect->draw_blit_upd();
  sv.score->reset(); pv.score->reset(); iv.reset();
  for (int i=0;i<patch_max;++i) { fm_data[i].custom=false; gpat_data[i].reset(); }
  for (int i=0;i<inst_max;++i) { i_ord[i]=i; ctr_data[i].custom=false; }
  for (int i=0;i<perc_max;++i) { p_ord[i]=i; perc_data[i].custom=false; wave_buffer[i].reset(); }
}

void update_all() {
  tempo_sl->cmd(tempo_sl,0,false);
  meter_sl->cmd(meter_sl,1,false); // fire: set meter, draw slider, draw scoreview's
  nupq_sl->cmd(nupq_sl,0,false);
  key_sl->draw_blit_upd();
  scales_win->draw_blit_upd();
  set_swing->draw_blit_upd(); set_swing_perc->draw_blit_upd();
  title_mes->draw_mes(tune_title);
  inst_ctr->inst_slider_data(act_inst_nr);
  inst_ctr->draw(act_inst_nr);
  for (int i=0;i<inst_max;++i) {
    inst_tabs[i]->draw_blit_upd();
    patch_info[i]->draw_mes("%-2d patch %d",i,iprop[i].patch_nr);
  }
  perc_ctr->perc_slider_data(act_perc_nr);
  perc_ctr->draw();
  for (int i=0;i<perc_max;++i) {
    perc_tabs[i]->draw_blit_upd();
  }
  m_notes(green);
  m_sets(green);
}

void dial_cmd(const char* text,int cmd_id) {
  switch(cmd_id) {
    case 'chor': {
        if (iv.cur_meas2<0) break;
        Info *inf=iv.info+iv.cur_meas2/2;
        dialog->dialog_label("chord");
        iv.cur_meas2=-1;
        if (!inf->chord || strcmp(text,inf->chord)) {
          inf->chord= text[0] ? strdup(text) : 0;
          m_notes(red);
        }
        iv.infoview->draw_blit_upd();
      }
      break;
    case 'cho2': {
        if (iv.cur_meas2<0) break;
        Info *inf=iv.info+iv.cur_meas2/2;
        dialog->dialog_label("2nd chord");
        iv.cur_meas2=-1;
        if (!inf->chord2 || strcmp(text,inf->chord2)) {
          inf->chord2= text[0] ? strdup(text) : 0;
          iv.infoview->draw_blit_upd();
          m_notes(red);
        }
      }
      break;
    case 'pnam':
      patch_nam->draw_mes(fm_data[iprop[act_inst_nr].patch_nr].name=strdup(text));
      dialog->dialog_label("patch renamed");
      break;
    case 'cpat': {
        int nr1=iprop[act_inst_nr].patch_nr,
            nr2=atoi(text);
        if (nr1==nr2) break;
        if (nr2<0 || nr2>=patch_max) { alert("patch nr 0 - %d",patch_max-1); break; }
        fm_data[nr1]=fm_data[nr2];
        patch_tab_ctr->act_lbut=patch_tabs[fm_data[nr2].patch_but];
        instrument->patch_slider_data(nr2);
        dialog->dialog_label("patch copied");
      }
      break;
    case 'tran':
      iprop[act_inst_nr].transpose=atoi(text);
      dialog->dialog_label("transpose value");
      break;
    case 'tra2':
      iprop[act_inst_nr].transpose2=atoi(text);
      dialog->dialog_label("transpose2 value");
      break;
    case 'pmnr':
      pprop[act_perc_nr].perc_nr=atoi(text);
      dialog->dialog_label("midi perc nr");
      break;
    case 'abcs': {
        pprop[act_perc_nr].abc_sym=strdup(text);
        char *p=strchr(const_cast<char*>(pprop[act_perc_nr].abc_sym),',');
        if (p) {
          *p=0; ++p;
          int n=eUp;
          if (!strcmp(p,"up") || atoi(p)==8) n=eUp;
          else if (!strcmp(p,"do") || atoi(p)==4) n=eDown;
          else alert("expected 'up' or 'do'");
          pprop[act_perc_nr].abc_stem=n;
        } 
        dialog->dialog_label("abc symbol");
      }
      break;
    case 'clef': {
        int cl=0;
        if (!strcmp(text,clefname[0])) cl=0;
        else if (!strcmp(text,clefname[1])) cl=1;
        else { alert("clef?"); break; }
        iprop[act_inst_nr].clef=cl;
        dialog->dialog_label("clef");
      }
      break;
    case 'cle2': {
        int cl=0;
        if (!strcmp(text,clefname[0])) cl=0;
        else if (!strcmp(text,clefname[1])) cl=1;
        else { alert("clef2?"); break; }
        iprop[act_inst_nr].clef2=cl;
        dialog->dialog_label("clef 2");
      }
      break;
    case 'mins': {
        int ind=atoi(text)-1;
        if (ind<0 || ind>=128) { alert("unexpected midi instr nr"); break; }
        iprop[act_inst_nr].midi_nr=ind;
        midi_inst->draw_mes(gm_instr[ind].name);
        dialog->dialog_label("midi instr");
      }
      break;
    case 'rang':
      sscanf(text,"%d,%d",&iprop[act_inst_nr].lo,&iprop[act_inst_nr].hi);
      dialog->dialog_label("midi-nr range");
      draw_range();
      break;
    case 'load': {
        if (strcmp(cfg_file,text)) {
          cfg_file=strdup(text);
        }
        FILE *conf=fopen(cfg_file,"r");
        if (conf) {
          reset_all();
          if (read_project(conf)) {
            update_all();
            dialog->dialog_label("loaded");
          }
          else {
            alert("error in %s",cfg_file);
            fclose(conf);
          }
        }
        else alert("project file %s not opened",cfg_file);
      }
      break;
    case 'save': {
        if (strcmp(cfg_file,text)) {
          cfg_file=strdup(text);
          read_only=false;  // so from now the new file can be written
          mod_notes->show();
          mod_settings->show();
        }
        else if (read_only) {
          alert("read-only mode!");
          break;
        }
        FILE *out=fopen(cfg_file,"w");
        if (!out) { alert("%s not opened",cfg_file); break; }
        if (save_project(out)) {
          dialog->dialog_label("saved");
          m_notes(green);
          m_sets(green);
        }
        fclose(out);
      }
      break;
    case 'tit':
      if (strcmp(tune_title,text)) {
        tune_title=strdup(text);
        title_mes->draw_mes(tune_title);
      }
      dialog->dialog_label("title");
      break;
    case 'wdir':
      if (strcmp(working_dir,text)) {
        if (chdir(text)) { alert("change-directory failed"); break; }
        char buf[100];
        if (!getcwd(buf,100)) { alert("getcwd problem"); break; }
        working_dir=strdup(buf);
        if (!wdir_mes) {
          wdir_mes=new Message(top_win,0,"dir:",Point(200,2));
          wdir_mes->draw_label(true);
        }
        wdir_mes->draw_mes(working_dir);
      }
      dialog->dialog_label("working dir");
      break;
    case 'auth':
      if (strcmp(author,text)) {
        author=strdup(text);
      }
      dialog->dialog_label("author");
      break;
    case 'sk1':
      skip_first_rest= !strcmp(text,"yes");
      dialog->dialog_label("skip 1st rests");
      break;
    case 'ipr':
      inv_perc_rests= !strcmp(text,"yes");
      dialog->dialog_label("inv perc rests");
      break;
    case 'key': {
        int k=s2keynr(text);
        if (k>=0) {
          if (abc_key!=k) {
            abc_key=k;
            scales_win->draw_blit_upd();
          }
        }
        else break;
        dialog->dialog_label("key");
      }
      break;
    case 'wavo':
      if (strcmp(wav_outfile,text)) wav_outfile=strdup(text);
      if (write_wave(wav_outfile)) 
        dialog->dialog_label("wave file written");
      break;
    case 'mido':
      if (strcmp(midi_outfile,text)) midi_outfile=strdup(text);
      if (write_midi(midi_outfile)) 
        dialog->dialog_label("midi file written");
      break;
    case 'abco':
    case 'abc2':
      if (strcmp(abc_outfile,text)) abc_outfile=strdup(text);
      if (write_abc(abc_outfile,cmd_id=='abco' ? 1 : 2)) 
        dialog->dialog_label("abc file written");
      break;
    case 'imid':
      if (strcmp(midi_infile,text)) midi_infile=strdup(text);
      import_midi(midi_infile);
      break;
    case 'i_rn':
      inst_tabs[act_inst_nr]->label=iprop[act_inst_nr].name=strdup(text);
      inst_tabs[act_inst_nr]->draw_blit_upd();
      dialog->dialog_label("instrument renamed");
      break;
    case 'i_sh':
      iprop[act_inst_nr].shortname=strdup(text);
      dialog->dialog_label("short name");
      break;
    case 'p_rn':
      perc_tabs[act_perc_nr]->label=pprop[act_perc_nr].name=strdup(text);
      perc_tabs[act_perc_nr]->draw_blit_upd();
      dialog->dialog_label("perc instr renamed");
      break;
  }
}

void fmopt_cmd(Id id,int nr,int fire) {
  switch (nr) {
    case 0: // instr name
      dialog->dialog_label("patch name:",cAlert);
      dialog->dialog_def(fm_data[iprop[act_inst_nr].patch_nr].name,dial_cmd,'pnam');
      break;
    case 1: // copy patch
      dialog->dialog_label("copy patch:",cAlert);
      dialog->dialog_def(i2s(iprop[act_inst_nr].patch_nr),dial_cmd,'cpat');
      break;
    default: alert("fmopt_cmd?");
  }
  instrument->fm_opt->close();
}

void mouse_down(Id id,int x,int y,int but) {
  switch (id.id1) {
    case 'infv': iv.mouse_down(x,y,but); break;
    case 'scv': sv.mouse_down(x,y,but); break;
    case 'pv': pv.mouse_down(x,y,but); break;
    case 'cwin': if (but==SDL_BUTTON_LEFT) inst_ctr->mouse_down(x,y); break;
    default: alert("mouse_down?");
  }
}

void button_cmd(Id id);
/*
static void cross(SDL_Surface *win,Id id,int,int) {
  static SDL_Surface *cross_pm;
  if (!cross_pm) cross_pm=create_pixmap(cross_xpm);
  SDL_BlitSurface(cross_pm,0,win,rp(3,3,0,0));
}
*/
void instopt_cmd(Id id,int nr,int fire) {
  switch (nr) {
    case 0: // instr name
      dialog->dialog_label("instrument name:",cAlert);
      dialog->dialog_def(iprop[act_inst_nr].name,dial_cmd,'i_rn');
      break;
    case 1: // instr short name
      dialog->dialog_label("short name:",cAlert);
      dialog->dialog_def(iprop[act_inst_nr].shortname,dial_cmd,'i_sh');
      break;
    case 2:
      dialog->dialog_label("transpose:",cAlert);
      dialog->dialog_def(i2s(iprop[act_inst_nr].transpose),dial_cmd,'tran');
      break;
    case 3:
      dialog->dialog_label("alt transpose:",cAlert);
      dialog->dialog_def(i2s(iprop[act_inst_nr].transpose2),dial_cmd,'tra2');
      break;
    case 4:
      dialog->dialog_label("clef:",cAlert);
      dialog->dialog_def(clefname[iprop[act_inst_nr].clef],dial_cmd,'clef');
      break;
    case 5:
      dialog->dialog_label("alt clef:",cAlert);
      dialog->dialog_def(clefname[iprop[act_inst_nr].clef2],dial_cmd,'cle2');
      break;
    case 6:
      dialog->dialog_label("midi instr:",cAlert);
      dialog->dialog_def(i2s(iprop[act_inst_nr].midi_nr+1),dial_cmd,'mins');
      break;
    case 7: {
        dialog->dialog_label("range (midi-numbers):",cAlert);
        char buf[20];
        sprintf(buf,"%d,%d",iprop[act_inst_nr].lo,iprop[act_inst_nr].hi);
        dialog->dialog_def(buf,dial_cmd,'rang');
      }
      break;
    case 8: {
        BgrWin*& cw=inst_ctr->combine_win;
        if  (!cw) {
          Rect r(inst_checkb[0]->tw_area.x+27, inst_checkb[0]->tw_area.y, inst_max*InstrCtrl::y_dist+18, inst_max*16+12);
          cw=new BgrWin(top_win,r,0,draw_cwin,mouse_down,0,0,cLightGrey,'cwin');
          cw->keep_on_top();
          new Button(cw,Style(0,1),Rect(cw->area.w-18,3,14,13),"X",button_cmd,'hidc');
          cw->draw_blit_recur();
          cw->upd();
        }
        else cw->show();
      }
      break;
    default: alert("instopt_cmd?");
  }
  inst_ctr->inst_options->close();
  m_sets(red);
}

void popt_cmd(Id id,int nr,int fire) {
  switch (nr) {
    case 0: // perc name
      dialog->dialog_label("percussion name:",cAlert);
      dialog->dialog_def(pprop[act_perc_nr].name,dial_cmd,'p_rn');
      break;
    case 1: // midi nr
      dialog->dialog_label("midi nr:",cAlert);
      dialog->dialog_def(i2s(pprop[act_perc_nr].perc_nr),dial_cmd,'pmnr');
      break;
    case 2: { // abc stem direction
        dialog->dialog_label("abc symbol:",cAlert);
        char buf[10];
        snprintf(buf,10,"%s,%s",pprop[act_perc_nr].abc_sym,pprop[act_perc_nr].abc_stem==eUp ? "up" : "do");
        dialog->dialog_def(buf,dial_cmd,'abcs');
      }
      break;
  }
  perc_ctr->perc_options->close();
}

void menu_cmd(Id id,int nr,int fire) {
  switch (nr) {
    case 0:
      dialog->dialog_label("load file:",cAlert);
      dialog->dialog_def(cfg_file,dial_cmd,'load');
      break;
    case 1:
      dialog->dialog_label("save file:",cAlert);
      dialog->dialog_def(cfg_file,dial_cmd,'save');
      break;
    case 2:
      dialog->dialog_label("WAVE file:",cAlert);
      dialog->dialog_def(wav_outfile,dial_cmd,'wavo');
      break;
    case 3:
      dialog->dialog_label("MIDI file:",cAlert);
      dialog->dialog_def(midi_outfile,dial_cmd,'mido');
      break;
    case 4:
      dialog->dialog_label("ABC file:",cAlert);
      dialog->dialog_def(abc_outfile,dial_cmd,'abco');
      break;
    case 5:
      dialog->dialog_label("ABC file (tr2):",cAlert);
      dialog->dialog_def(abc_outfile,dial_cmd,'abc2');
      break;
    case 6:
      dialog->dialog_label("MIDI file:",cAlert);
      dialog->dialog_def(midi_infile,dial_cmd,'imid');
      break;
  }
  file_menu->close();
}

void setm_cmd(Id id,int nr,int fire) {
  switch (nr) {
    case 0:
      dialog->dialog_label("working dir:",cAlert);
      dialog->dialog_def(working_dir,dial_cmd,'wdir');
      break;
    case 1:
      dialog->dialog_label("tune title:",cAlert);
      dialog->dialog_def(tune_title,dial_cmd,'tit');
      break;
    case 2:
      dialog->dialog_label("tune author:",cAlert);
      dialog->dialog_def(author,dial_cmd,'auth');
      break;
    case 3:
      dialog->dialog_label("key:",cAlert);
      dialog->dialog_def(maj_keys[mv_key_nr],dial_cmd,'key');
      break;
    case 4:
      dialog->dialog_label("skip 1st rests:",cAlert);
      dialog->dialog_def(skip_first_rest ? "yes" : "no",dial_cmd,'sk1');
      break;
    case 5:
      dialog->dialog_label("inv perc rests:",cAlert);
      dialog->dialog_def(inv_perc_rests ? "yes" : "no",dial_cmd,'ipr');
      break;
  }
  set_menu->close();
  switch (nr) {
    case 0: break;
    default: m_sets(red);
  }
}

void draw_signs_view(BgrWin *signv) {
  static const char 
    *sharp[]={
      "5 6 2 1",
      "# c #0",
      ". c None",
      "..#.#",
      "#####",
      ".#.#.",
      ".#.#.",
      "#####",
      "#.#.."
    },
    *flat[]={
      "5 6 2 1",
      "# c #0",
      ". c None",
      ".#...",
      ".#...",
      ".###.",
      ".#..#",
      ".#.#.",
      ".##.."
  };
  static SDL_Surface
    *sharp_pm=create_pixmap(sharp),
    *flat_pm=create_pixmap(flat);
  signv->clear();
  for (int s_lnr=0;s_lnr<sv.sclin_max*7/12;++s_lnr) {
    int y=top_staff_yoff+s_lnr*sclin_dist-10;
    int sign=signs[mv_key_nr][s_lnr%7];
    if (sign)
      SDL_BlitSurface(sign==eHi ? sharp_pm : flat_pm,0,signv->win,rp((s_lnr%7 & 1)==0 ? 0 : 5,y,0,0));
      //draw_pixmap(signsview->win,Point((lnr%7 & 1)==0 ? 2 : 7,y),sign==eHi ? sharp_pm : flat_pm,5,6);
  }
}

void checkb_cmd(CheckBox *chb) {
  switch (chb->id.id1) {
    case 'nois':
      if (chb->value()) {
        InstrumentData *dat=fm_data+iprop[act_inst_nr].patch_nr;
        bool warn=false;
        for (int i=0;i<num_harms;++i) 
          if (dat->click_hlin[i]) warn=true;
        if (dat->chorus.y) warn=true;
        if (warn) alert("noisy: no click, no chorus");
      }
      break;
    case 'jlis':  // just listen
      stop_req=true;
      break;
    case 'inst':  // enable instr
      sv.scoreview->draw_blit_upd();
      break;
    case 'perc':
      pv.scoreview->draw_blit_upd();
      break;
    case 'drst':  // draw_staff
      d_staff=chb->value();
      if (d_staff) {
        draw_signs_view(signs_view); // key might be modified
        signs_view->show();
      }
      else {
        signs_view->hide();
        draw_midc_arrow1();
      }
      sv.scoreview->draw_blit_upd();
      draw_range();
      break;
    case 'altp':  // alt perc display
      d_alt=chb->value();
      pv.scoreview->draw_blit_upd();
      break;
    default: alert("checkb_cmd?"); 
  }
  switch (chb->id.id1) {
    case 'jlis': case 'inst': case 'perc': case'drst': case 'altp': break;
    default: m_sets(red);
  }
}

void disp_settings(SDL_Surface *win,int par,int y_off) { // for Settings menu
  switch (par) {
    case 0:
    case 1:
      break;
    case 2:
      draw_ttf->draw_string(win,author,Point(110,y_off));
      break;
    case 3:
      draw_ttf->draw_string(win,maj_keys[abc_key],Point(110,y_off));
      break;
    case 4:
      draw_ttf->draw_string(win,skip_first_rest ? "yes" : "no",Point(110,y_off));
      break;
    case 5:
      draw_ttf->draw_string(win,inv_perc_rests ? "yes" : "no",Point(110,y_off));
      break;
  }
}   

void disp_val(SDL_Surface *win,int par,int y_off) { // for instr Options menu
  char buf[21];
  buf[20]=0;
  switch (par) {
    case 0:
      strncpy(buf,iprop[act_inst_nr].name,20);
      break;
    case 1:
      strncpy(buf,iprop[act_inst_nr].shortname,20);
      break;
    case 2:
      snprintf(buf,20,"%d",iprop[act_inst_nr].transpose);
      break;
    case 3:
      snprintf(buf,20,"%d",iprop[act_inst_nr].transpose2);
      break;
    case 4:
      strncpy(buf,clefname[iprop[act_inst_nr].clef],20);
      break;
    case 5:
      strncpy(buf,clefname[iprop[act_inst_nr].clef2],20);
      break;
    case 6:
      snprintf(buf,20,"%d",iprop[act_inst_nr].midi_nr+1);
      break;
    case 7:
      snprintf(buf,20,"%d,%d",iprop[act_inst_nr].lo,iprop[act_inst_nr].hi);
      break;
    case 8:
      if (i_combine[act_inst_nr]==act_inst_nr) strcpy(buf,"-");
      else snprintf(buf,20,"%d",i_combine[act_inst_nr]);
      break;
    default:
      alert("disp_val?"); return;
  }
  draw_ttf->draw_string(win,buf,Point(110,y_off));
} 

void disp_pval(SDL_Surface *win,int par,int y_off) {
  char buf[21];
  buf[20]=0;
  switch (par) {
    case 0:
      snprintf(buf,20,"%s",pprop[act_perc_nr].name);
      break;
    case 1:
      snprintf(buf,20,"%d",pprop[act_perc_nr].perc_nr);
      break;
    case 2:
      snprintf(buf,20,"%s,%s",pprop[act_perc_nr].abc_sym,pprop[act_perc_nr].abc_stem==eUp ? "up" : "do");
      break;
    default:
      alert("disp_pval?"); return;
  }
  draw_ttf->draw_string(win,buf,Point(80,y_off));
} 
    
void button_cmd(Id id) {
  switch (id.id1) {
    case 'help':
      if (!help_win) help_win=help_window(top_win);
      else help_win->show();
      break;
    case 'selw':  // select wave file
      perc_ctr->collect_wave_files();
      m_sets(red);
      break;
    case 'selp':  // select bi patch
      instrument->collect_bi_patches();
      m_sets(red);
      break;
    case 'gpat':  // select gus patch
      instrument->collect_gus_files(act_inst_nr);
      m_sets(red);
      break;
    case 'parn': {
        CmdMenu *opt=instrument->fm_opt;
        if (!opt->init(80,2,fmopt_cmd)) break;
        opt->add_mbut("name...");
        opt->add_mbut("copy patch...");
      }
      break;
    case 'p_rn': // patch rename
      dialog->dialog_label("patch name:",cAlert);
      dialog->dialog_def(fm_data[iprop[act_inst_nr].patch_nr].name,dial_cmd,'parn');
      m_sets(red);
      break;
    case 'aopt': { // instr options
        CmdMenu *opt=inst_ctr->inst_options;
        if (!opt->init(180,9,instopt_cmd)) break;
        opt->add_mbut(Label("name...",disp_val));
        opt->add_mbut(Label("short name...",disp_val));
        opt->add_mbut(Label("transpose...",disp_val));
        opt->add_mbut(Label("alt transpose...",disp_val));
        opt->add_mbut(Label("clef...",disp_val));
        opt->add_mbut(Label("alt clef...",disp_val));
        opt->add_mbut(Label("midi instr...",disp_val));
        opt->add_mbut(Label("range (midi nr's)...",disp_val));
        opt->add_mbut(Label("combine",disp_val));
      }
      break;
    case 'popt': { // perc options
        CmdMenu *opt=perc_ctr->perc_options;
        if (!opt->init(200,3,popt_cmd)) break;
        opt->add_mbut(Label("name...",disp_pval));
        opt->add_mbut(Label("midi nr...",disp_pval));
        opt->add_mbut(Label("abc symbol...",disp_pval));
      }
      break;
    case 'menu':
      if (!file_menu->init(180,7,menu_cmd)) break;
      file_menu->add_mbut("load project...");
      file_menu->add_mbut("save project...");
      file_menu->add_mbut("export WAVE file...");
      file_menu->add_mbut("export MIDI file...");
      file_menu->add_mbut("export ABC file...");
      file_menu->add_mbut("export ABC file, alt transpose...");
      file_menu->add_mbut("import MIDI file, keep settings...");
      break;
    case 'setm':
      if (!set_menu->init(180,6,setm_cmd)) break;
      set_menu->add_mbut("working dir...");
      set_menu->add_mbut("title...");
      set_menu->add_mbut(Label("author...",disp_settings));
      set_menu->add_mbut(Label("key...",disp_settings));
      set_menu->add_mbut(Label("skip first rests...",disp_settings));
      set_menu->add_mbut(Label("invisible perc rests...",disp_settings));
      break;
    case 'i_a1': // instr checkb all 1
      for (int i=0;i<inst_max;++i) inst_checkb[i]->set_cbval(true,0);
      sv.scoreview->draw_blit_upd();
      break;
    case 'i_a0': // instr checkb all 0
      for (int i=0;i<inst_max;++i) inst_checkb[i]->set_cbval(false,0);
      sv.scoreview->draw_blit_upd();
      break;
    case 'p_a1': // perc checkb all 1
      for (int i=0;i<perc_max;++i) perc_checkb[i]->set_cbval(true,0);
      pv.scoreview->draw_blit_upd();
      break;
    case 'p_a0': // perc checkb all 0
      for (int i=0;i<perc_max;++i) perc_checkb[i]->set_cbval(false,0);
      pv.scoreview->draw_blit_upd();
      break;
    case 'ok':
      dialog->dok();
      break;
    case 'snam': // show names
      for (int i=0;i<inst_max;++i) {
        RExtButton *but=inst_tabs[i];
        if (!but->label.str || (iprop[i].name && strcmp(but->label.str,iprop[i].name))) {
          but->label=iprop[i].name;
          but->draw_blit_upd();
        }
      }
      for (int i=0;i<perc_max;++i) {
        RExtButton *but=perc_tabs[i];
        if (!but->label.str || (pprop[i].name && strcmp(but->label.str,pprop[i].name))) {
          but->label=pprop[i].name;
          but->draw_blit_upd();
        }
      }
      break;
    case 'play':
      if (i_am_playing) {
        play_but->label.draw_cmd=right_arrow;
        stop_req=true;
        //SDL_WaitThread(audio_thread,0);
      }
      else {
        i_am_playing=true;
        nbufs.reset();
        int stop=0;
        if (set_repeat->value() && repeat_end>0) {
          if (repeat_end<=leftside) {
            alert("repeat: stop < leftside");
            i_am_playing=false;
            break;
          }
          stop=repeat_end;
        }
        if (!nbufs.fill_note_bufs(leftside,stop,0)) {
          i_am_playing=false;
          break;
        }
        play_but->label.draw_cmd=square;
        nbufs.scale_timing();
        audio_thread=SDL_CreateThread(play_threadfun,0);
      }
      break;
    case 'uns':
    case 'dels':
    case 'rcol':
    case 'adto':
      sv.selected.modify_sel(&sv,id.id1,sv.ip_max);
      pv.selected.modify_sel(&pv,id.id1,pv.ip_max);
      break;
    case 'hint':
      if (keystate[SDLK_i]) {
        puts("Built-in chords:");
        for (Chord *ch=chords;ch->name;++ch) {
          ch->print_chord(abc_key);
          putchar('\n');
        }
      }
      else
        sv.selected.print_hint();
      break;
    case 'hidc': // hide comb win
      inst_ctr->combine_win->hide();
      break;
    default:
      alert("button_cmd?");
  }
}

InstrCtrl::InstrCtrl(Rect rect):
    combine_win(0) {
  bgw=new BgrWin(top_win,rect,"instruments",draw_raised,0,0,0,cForeground);
  inst_options=new CmdMenu(new Button(bgw,Style(2,1,18),Rect(6,10,54,18),"Options",button_cmd,'aopt'));

  custom=new CheckBox(bgw,0,Rect(68,10,0,0),"custom",0);
  patch_nr=new HSlider(bgw,1,Rect(6,50,124,0),0,patch_max-1,"patch nr",hslider_cmd,'patn');
  ampl=new HSlider(bgw,1,Rect(10,86,78,0),0,7,"amplitude",hslider_cmd,'ampl');
  midi_ampl=new HSlider(bgw,1,Rect(10,122,78,0),0,7,"midi ampl",hslider_cmd,'mamp');
  pan=new HSlider(bgw,1,Rect(90,122,40,0),0,4,"pan",hslider_cmd,'ipan');
  add_chords=new CheckBox(bgw,1,Rect(10,156,0,0),"add chords",0);
  slashed_notes=new CheckBox(bgw,1,Rect(10,172,0,0),"slashed notes",0);
}

PercCtrl::PercCtrl(Rect rect) {
  bgw=new BgrWin(top_win,rect,"percussion",draw_raised,0,0,0,cForeground);
  perc_options=new CmdMenu(new Button(bgw,Style(2,1,18),Rect(6,6,52,18),"Options",button_cmd,'popt'));

  custom=new CheckBox(bgw,0,Rect(68,6,0,0),"custom",0);
  just_listen=new CheckBox(bgw,0,Rect(6,27,0,0),"listen",checkb_cmd,'jlis');
  loc_wav_dir=new CheckBox(bgw,0,Rect(58,27,0,0),"./samples",0);
  const char* fn=wave_buffer[0].fname;
  sel_wavf=new CmdMenu(new Button(bgw,4,Rect(3,46,127,16),fn ? fn : "(no wave file)",button_cmd,'selw'));
  wav_ampl=new HSlider(bgw,1,Rect(10,84,76,0),0,7,"amplitude",hslider_cmd,'wamp');
  wav_midi_ampl=new HSlider(bgw,1,Rect(10,120,78,0),0,7,"midi ampl",hslider_cmd,'mwam');
  wav_pan=new HSlider(bgw,1,Rect(90,120,40,0),0,4,"pan",hslider_cmd,'ppan');
}

Instrument::Instrument(Rect rect) {
  bgw=new BgrWin(top_win,rect,"patches",draw_raised,0,0,0,cForeground,'ins');
  bgw->reloc_title(0,-16);

  fm_opt=new CmdMenu(new Button(bgw,Style(2,1,18),Rect(6,10,56,18),"Options",button_cmd,'parn'));

  sel_patch=new CmdMenu(new Button(bgw,Style(2,1),Rect(70,10,90,18),"Built-in patch",button_cmd,'selp'));

  fm_startup=new HVSlider(bgw,1,Rect(4,46,68,70),0,Int2(0,0),Int2(8,7),"FM f1/mod1",hvslider_cmd,'fmsu');
  fm_ctrl=new HVSlider(bgw,1,Rect(74,46,68,70),0,Int2(0,0),Int2(8,7),"FM f2/mod2",hvslider_cmd,'fm');
  bgw_harm=new BgrWin(bgw,Rect(6,50,harm_w,harm_h),"harmonics",draw_harm_bgw,harm_down,0,0,cLightGrey);
  detune=new HSlider(bgw,1,Rect(144,54,60,0),0,5,"detune",hslider_cmd,'fmdt');
  start_amp=new VSlider(bgw,1,Rect(144,115,30,40),0,3,"start-amp",vslider_cmd,'sta');
  startup=new HSlider(bgw,1,Rect(4,132,60,0),0,5,"startup",hslider_cmd,'su');
  attack=new HSlider(bgw,1,Rect(4,132,60,0),0,5,"attack",hslider_cmd,'att');
  decay=new HSlider(bgw,1,Rect(70,132,60,0),0,5,"decay",hslider_cmd,'dcay');
  clip=new HSlider(bgw,1,Rect(136,132,50,0),0,4,"soft clip",hslider_cmd,'clip');
  mphonic=new CheckBox(bgw,0,Rect(210,6,0,0),"monophonic",0);
  sin2=new CheckBox(bgw,0,Rect(210,23,0,0),"short sinus",0);
  gus_aalias=new CheckBox(bgw,0,Rect(210,23,0,0),"anti-alias",0);
  noisy=new CheckBox(bgw,0,Rect(210,23,0,0),"noisy",checkb_cmd,'nois');
  shortdur=new CheckBox(bgw,0,Rect(210,40,0,0),"short notes",0);
  mod_mod=new HVSlider(bgw,1,Rect(204,85,92,50),34,Int2(0,0),Int2(7,4),"MM depth/freq",hvslider_cmd,'mmod');
  chorus=new HVSlider(bgw,1,Rect(190,74,80,40),30,Int2(0,0),Int2(3,2),"chorus freq/depth",hvslider_cmd,'chrs');
  gus_info=new CheckBox(bgw,0,Rect(10,34,0,0),"info only",0);
  loc_samp_dir=new CheckBox(bgw,0,Rect(10,50,0,0),"dir: ./samples",0);
  gus_patches=new CmdMenu(new Button(bgw,4,Rect(10,70,160,16),"(no gus patch file)",button_cmd,'gpat'));
  custom=new CheckBox(bgw,0,Rect(210,138,0,0),"custom",0);
}

void InstrumentData::draw_line(int ind,bool upd) {
  Rect rect((ind-1)*ldist+ldist/2,0,ldist,harm_h);
  Uint32 bgcol= ind%2 ? cWhite : cLightGrey;
  static Uint32 cDark=calc_color(0x707070);
  instrument->bgw_harm->clear(rect,bgcol,false);
  SDL_Surface *win=instrument->bgw_harm->win;
  int x=ind*ldist,
      h1=hlin[ind],
      h2=click_hlin[ind];
  if (ind==1 || ind%5==0)
    draw_ttf->draw_string(win,i2s(ind),Point(x-7,0));
  if (h1 && h2) {
    int h3=min(h1,h2);
    SDL_FillRect(win,rp(x-2,harm_h-h3,3,h3),cDark);
    if (h1>h2) SDL_FillRect(win,rp(x-2,harm_h-h1,3,h1-h3),cBlue);
    else SDL_FillRect(win,rp(x-2,harm_h-h2,3,h2-h3),cRed);
  }
  else if (hlin[ind]) {
    SDL_FillRect(win,rp(x-2,harm_h-h1,3,h1),cBlue);
  }
  else if (click_hlin[ind]) {
    SDL_FillRect(win,rp(x-2,harm_h-h2,3,h2),cRed);
  }
  if (upd) instrument->bgw_harm->blit_upd(&rect);
}

void NoteBuffer::report(int n) {
  if (notes->cat==eSleep)
    return;
  printf("Voice %d\n",n);
  static const char *cat_name[]={ "eSleep","eNote","eGusPatch","eHarm","eSampled","ePause" };
  for (Note *np=notes;np-notes<=cur_note;np++) {
    printf("  cat=%s ",cat_name[np->cat-eSleep]);
    switch (np->cat) {
      case eNote:
        printf("start_snr=%d dur=%d ",np->start_snr,np->dur);
        printf("freq=%0.2f col=%u attack=%d decay=%d gap=%d",
               np->freq,np->note_col,np->attack,np->decay,np->gap);
        break;
      case eGusPatch:
        printf("start_snr=%d dur=%d ",np->start_snr,np->dur);
        printf("freq=%0.2f col=%u decay=%d gap=%d",
               np->freq,np->note_col,np->decay,np->gap);
        break;
      case eHarm:
        printf("start_snr=%d dur=%d ",np->start_snr,np->dur);
        printf("freq=%0.2f col=%u attack=%d decay=%d gap=%d",
               np->freq,np->note_col,np->attack,np->decay,np->gap);
        break;
      case eSampled:
        printf("start_snr=%d dur=%d col=%u",np->start_snr,np->dur,np->note_col);
        break;
      case ePause:
        printf("dur=%d ",np->dur);
        break;
    }
    putchar('\n');
  }
  fflush(stdout);
}

void mouse_moved(Id id,int x,int y,int but) {
  switch (id.id1) {
    case 'scv': sv.mouse_moved(x,y,but); break;
    case 'pv': pv.mouse_moved(x,y,but); break;
  }
}

void mouse_up(Id id,int x,int y,int but) {
  switch (id.id1) {
    case 'scv': sv.mouse_up(x,y,but); break;
    case 'pv': pv.mouse_up(x,y,but); break;
  }
}

void draw_infoview(BgrWin *infv) {
  infv->clear();
  if (iv.sel_meas>=0) {
    int x=max(0,(iv.sel_meas * meter - leftside) * sect_len),
        w=scv_w-1-x;
    if (w>0) {
      infv->clear(Rect(x,0,w,iv_h),cLightGrey,false);
    }
  }
  int ind;
  hlineColor(infv->win,0,scv_w-1,iv_h/3,0xb0b0b0ff);
  iv.highlight(false,false);
  for (ind=leftside/meter;ind<iv.len;++ind) {
    int x=(ind * meter - leftside) * sect_len;
    if (x>=scv_w) break;
    if (x>0) vlineColor(infv->win,x,0,iv_h-1,0xb0b0b0ff);
    Info *inf=iv.info+ind;
    const char ch[]={ inf->annot,0 };
    if (x>=0) {
      if (inf->annot) draw_ttf->draw_string(iv.infoview->win,ch,Point(x+2,0));
      if (inf->chord) draw_ttf->draw_string(iv.infoview->win,inf->chord,Point(x+2,iv_h/3+1));
    }
    if (inf->chord2) {
      x += meter * sect_len / 2;
      if (x>=scv_w) break;
      if (x>=0) draw_ttf->draw_string(iv.infoview->win,inf->chord2,Point(x+2,2*iv_h/3+1));
    }
  }
}

void InfoView::mouse_down(int x,int y,int but) {
  const int snr=sectnr(x),
            ind=snr/meter;
  Info *inf=get_info(ind);
  if (but==SDL_BUTTON_LEFT) {
    if (y<iv_h/3) {   // annotation
      inf->annot=!inf->annot;
      char an_ch='A';
      for (int i=0;i<iv.len;++i)
        if (info[i].annot) info[i].annot=an_ch++;
      infoview->draw_blit_upd();
    }
    else {  // chord
      if (snr-ind*meter>2) {
        iv.highlight(true);
        dialog->dialog_label("2nd chord:",cAlert);
        dialog->dialog_def(inf->chord2,dial_cmd,'cho2');
        cur_meas2=ind*2+1;
        iv.highlight(false);
      }
      else {
        iv.highlight(true);
        dialog->dialog_label("chord:",cAlert);
        dialog->dialog_def(inf->chord,dial_cmd,'chor');
        cur_meas2=ind*2;
        iv.highlight(false);
      }
    }
  }
  else if (but==SDL_BUTTON_MIDDLE) {
    sel_meas=ind;
    iv.infoview->draw_blit_upd();
  }
  else if (but==SDL_BUTTON_RIGHT && sel_meas>=0) {
    int diff=ind-sel_meas,
        lst_ind=len-1;
    for (;lst_ind>sel_meas;--lst_ind)
      if (info[lst_ind].occ()) break;
    if (diff>0) {
      for (int i=lst_ind+diff;i>=sel_meas+diff;--i) {
        *get_info(i)=*get_info(i-diff);
        get_info(i-diff)->clear();
      }
      m_notes(red);
    }
    else if (diff<0) {
      for (int i=ind;i<=lst_ind+diff;++i) {
        *get_info(i)=*get_info(i-diff);
        get_info(i-diff)->clear();
      }
      m_notes(red);
    }
    sel_meas=-1;
    iv.infoview->draw_blit_upd();
  }
}

void ScoreView::mouse_down(int x,int y,int but) {
  state=eIdle;
  if (is_perc) {
    if (d_alt && (keystate[SDLK_m] || keystate[SDLK_c])) {
      alert("alt display: keys i,m,c not supported");
      return;
    }
  }
  else {
    if (d_staff && (keystate[SDLK_m] || keystate[SDLK_c])) {
      alert("staff display: keys i,m,c not supported");
      return;
    }
  }
  const int snr=sectnr(x);
  if (snr<0) return;
  cur_leftside=leftside;
  cur_point=prev_point=Point(x,y);
  if (keystate[SDLK_LCTRL]) {
    state=eScroll;
    SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
    return;
  }
  int lnr=linenr(y,up_down_key);
  if (lnr<0 && !is_perc) {
    if (but==SDL_BUTTON_LEFT)
      draw_upd_repeat(repeat_end,snr);
    else if (but==SDL_BUTTON_MIDDLE) {
      draw_upd_repeat(repeat_end,-1);
      repeat_end=-1;
    }
    return;
  }
  if (lnr>=sclin_max || lnr<0) {
    if (is_perc && d_alt)
      alert("alt display: pointer should be near the line");
    return;
  }
  ScSection *sec=0;
  if (is_perc && d_alt) {  // lnr might be inexact
    for (int n=max(0,lnr-4);;++n) {
      ScSection *s=score->get_section(n,snr);
      if (s->occ_color(p_enab,act_perc_nr)) {
        lnr=n; sec=s;
        break;
      }
      if (n>=min(sclin_max,lnr+4)) {
        lnr=perc_shift/2; // default, independent from y
        sec=score->get_section(lnr,snr);
        break;
      }
    }
  }
  else
    sec=score->get_section(lnr,snr);
  cur_lnr=lnr;
  prev_snr=cur_snr=snr;
  if (keystate[SDLK_m] || keystate[SDLK_c]) {
    if (!selected.sd_list.lis) return;
    state= keystate[SDLK_c] ? eCopying : eMoving;
    delta_lnr=delta_snr=left_snr=0;
    SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
  }
  else if (keystate[SDLK_i]) {
    char buf[100];
    sprintf(buf,"line %d note-unit %d",lnr,snr);
    if (info_to_term) puts(buf); else alert(buf);
    if (is_perc) {
      for (int c=0;c<ip_max;++c) {
        if (!sec->occ_color(0,c)) continue;
        sprintf(buf,"  perc %d (%s): sel=%d",c,pprop[c].name,sec->is_csel(c));
        if (sec->buf[c].del_start || sec->buf[c].del_end)
          sprintf(buf+strlen(buf)," delay=%d,%d",sec->buf[c].del_start,sec->buf[c].del_end);
        if (!p_enab(c)) strcat(buf," off");
        if (info_to_term) puts(buf); else alert(buf);
      }
    }
    else {
      for (int c=0;c<ip_max;++c) {
        if (!sec->occ_color(0,c)) continue;
        sprintf(buf,"  instr %d (%s): stacc=%d sel=%d",
           c,iprop[c].name,
           sec->buf[c].stacc,
           sec->is_csel(c));
        if (sec->buf[c].del_start || sec->buf[c].del_end)
          sprintf(buf+strlen(buf)," delay=%d,%d",sec->buf[c].del_start,sec->buf[c].del_end);
        if (!enab(c)) strcat(buf," off");
        if (info_to_term) puts(buf); else alert(buf);
      }
    }
    if (info_to_term) fflush(stdout);
  }
  else if (keystate[SDLK_p] || // paste sections from selected
           keystate[SDLK_n]) { // move sections from selected
    SLList_elem<SectData> *sd=selected.sd_list.lis;
    if (!sd) return;
    ScSection *sec1;
    int min_snr=selected.min_snr(); // leftmost selected section
    for (;sd;sd=sd->nxt) {  // update source sections
      ScSection *from=score->get_section(sd->d.lnr,sd->d.snr);
      if (keystate[SDLK_n]) {  // reset source section
        for (int col=0;col<ip_max && from->buf;++col)
          if (from->is_csel(col)) from->reset(col,ip_max);
      }
      else from->set_sel(false,ip_max); // unselect source section
      from->draw_sect(is_perc,sd->d.lnr,sd->d.snr);
    }
    for (sd=selected.sd_list.lis;sd;sd=sd->nxt) {
      int snr1=sd->d.snr + snr - min_snr;
      if (score->lst_sect<snr1) score->lst_sect=snr1;
      sec1=score->get_section(sd->d.lnr,snr1);
      for (int col=0;col<ip_max;++col)
        if (sd->d.sect.is_csel(col)) sec1->copy(&sd->d.sect,col,ip_max);
      sec1->draw_sect(is_perc,sd->d.lnr,snr1);
      sd->d.snr=snr1;
    }
  }
  else if (scv_mode==eEdit) {
    if (is_perc) edit_percdown(sec,lnr,snr,but);
    else edit_down(sec,lnr,snr,but);
  }
  else if (scv_mode==eSelect) {
    if (is_perc) select_percdown(sec,lnr,snr,but);
    else select_down(sec,lnr,snr,but);
  }
  else alert("ScV: mouse_down?");
}

void ScoreView::mouse_moved(int x,int y,int but) {
  if (keystate[SDLK_LCTRL] && state!=eScroll) {
    alert("unexpected ctrl key");
    state=eIdle; SDL_EventState(SDL_MOUSEMOTION,SDL_DISABLE);
    return;
  }
  ScSection *sec;
  switch (state) {
    case eIdle:
      break;
    case eScroll: {
        int dx= x-cur_point.x;
        int new_left=max(0,(cur_leftside*sect_len-dx)/sect_len);
        if (leftside!=new_left) {
          leftside=new_left;
          iv.infoview->draw_blit_upd();
          sv.scoreview->draw_blit_upd();
          pv.scoreview->draw_blit_upd();
          sv_scroll->set_xpos(leftside*sect_len);
        }
      }
      break;
    case eCollectSel:
    case eCollectSel1: {
        int snr=sectnr(x);
        if (snr<0 || snr>score->lst_sect) { state=eIdle; return; }
        if (snr>prev_snr)
          for (++prev_snr;;++prev_snr) {
            if (state==eCollectSel) sel_column(prev_snr); else sel_column(prev_snr,act_nr);
            if (prev_snr==snr) break;
          }
        else if (snr<prev_snr)
          for (--prev_snr;;--prev_snr) {
            if (state==eCollectSel) sel_column(prev_snr); else sel_column(prev_snr,act_nr);
            if (prev_snr==snr) break;
          }
      }     
      break;
    case eMoving:
    case eCopying: {
        SLList_elem<SectData> *sd;
        int lnr1, snr1,
            dx=(x-prev_point.x)/sect_len,
            dy=(y-prev_point.y)/sclin_dist;
        if (dy || dx) {
          prev_point.set(x,y);
          for (sd=selected.sd_list.lis;sd;sd=sd->nxt) { // erase old ghost notes
            lnr1=sd->d.lnr + delta_lnr;
            snr1=sd->d.snr + delta_snr + left_snr;
            
            if (lnr1>=0 && lnr1<sclin_max && snr1>=0 && snr1<score->len) {
              sec=score->get_section(lnr1,snr1);
              sec->draw_ghost(is_perc,lnr1,snr1,true);
            }
          }
          delta_lnr=(y-cur_point.y)/sclin_dist;
          delta_snr=(x-cur_point.x)/sect_len;
          for (sd=selected.sd_list.lis;sd;sd=sd->nxt) { // draw new ghost notes
            lnr1=sd->d.lnr + delta_lnr;
            snr1=sd->d.snr + delta_snr + left_snr;
            if (lnr1>=0 && lnr1<sclin_max && snr1>=0) {
              sec=score->get_section(lnr1,snr1);
              sec->draw_ghost(is_perc,lnr1,snr1,false);
            }
          }
        }
      }
      break;
    default: {
        int snr,
            new_snr=sectnr(x);
        if (new_snr<=prev_snr) return; // only tracking to the right
        for (snr=prev_snr+1;;++snr) {
          if (snr<0 || snr>=score->len) {
            state=eIdle; return;
          }
          ScSection *const sect=score->get_section(cur_lnr,snr);
          bool play=sect->occ_color(enab,act_nr);
          if (state==eTrackingCol) { 
            if (!play) {
              sect->set(act_nr,true,false,up_down_key);
              sect->draw_sect(false,cur_lnr,snr);
            }
            else { state=eIdle; break; }
          }
          else if (state==eErasingCol) {
            if (play) {
              if (sect->is_sel(ip_max))      // any color selected?
                selected.remove(cur_lnr,snr,sect);
              sect->reset(act_nr,ip_max);
              sect->draw_sect(false,cur_lnr,snr);
            }
            else { state=eIdle; break; }
          }
          else if (state==eTracking) {
            if (!sect->occ(enab,0,ip_max)) {
              sect->set(act_nr,true,false,up_down_key);
              sect->draw_sect(false,cur_lnr,snr);
            }
            else { state=eIdle; break; }
          }
          else if (state==eErasing) {  // state = eErasing
            if (sect->occ(enab,0,ip_max)) {
              if (sect->buf && sect->is_sel(ip_max))      // any color selected?
                selected.remove(cur_lnr,snr,sect);
              sect->reset();
              sect->draw_sect(false,cur_lnr,snr);
            }
            else { state=eIdle; break; }
          }
          else alert("mouse_moved:state %d?",state);
          if (snr==new_snr) {
            if (score->lst_sect<snr) score->lst_sect=snr;
            break;
          }
        }
        prev_snr=new_snr;
      }
      break;
  }
}

void ScoreView::mouse_up(int x,int y,int but) {
  switch (state) {
    case eIdle:
    case eErasing:
    case eErasingCol:
    case eCollectSel:
    case eCollectSel1:
    case eScroll:
      break;
    case eTracking:
    case eTrackingCol:
      if (but==SDL_BUTTON_MIDDLE) {
        ScSection *const sect=score->get_section(cur_lnr,prev_snr);
        sect->buf[act_nr].stacc=true;
        sect->draw_sect(false,cur_lnr,prev_snr);
      }
      break;
    case eMoving:
    case eCopying: {
        SLList_elem<SectData> *sd;
        ScSection *from,*to;
        int lnr1, snr1,
            to_lnr, to_snr;
        for (sd=selected.sd_list.lis;sd;sd=sd->nxt) { // erase ghost notes
          lnr1=sd->d.lnr + delta_lnr;
          snr1=sd->d.snr + delta_snr + left_snr;
          if (lnr1>=0 && lnr1<sclin_max && snr1>=0 && snr1<score->len) {
            ScSection *sec=score->get_section(lnr1,snr1);
            sec->draw_ghost(is_perc,lnr1,snr1,true);
          }
        }
        if (!keystate[SDLK_k]) {   // not keep last score?
          if (delta_lnr || delta_snr) {
            bool warn=false;
            for (sd=selected.sd_list.lis;sd;sd=sd->nxt) { // check
              to_lnr=sd->d.lnr + delta_lnr;
              to_snr=sd->d.snr + delta_snr + left_snr;
              if (to_lnr<0 || to_lnr>=sclin_max || to_snr<0) {
                warn=true;
                alert("notes in measure %d will disappear",sd->d.snr/meter);
              }
            }
            if (warn) {
              alert("moving/deleting undone");
              break;
            }
            for (sd=selected.sd_list.lis;sd;sd=sd->nxt) { // update source sections
              from=score->get_section(sd->d.lnr,sd->d.snr);
              if (state==eMoving) {
                for (int col=0;col<ip_max && from->buf;++col) // from->buf might become 0
                  if (from->is_csel(col)) from->reset(col,ip_max);
              }
              else from->set_sel(false,ip_max);
              from->draw_sect(is_perc,sd->d.lnr,sd->d.snr);
            }
            for (sd=selected.sd_list.lis;sd;) { // update destination sections
              to_lnr=sd->d.lnr + delta_lnr;
              to_snr=sd->d.snr + delta_snr + left_snr;
              to=score->get_section(to_lnr,to_snr);
              for (int col=0;col<ip_max;++col) {
                if (sd->d.sect.is_csel(col)) to->copy(&sd->d.sect,col,ip_max);
              }
              if (score->lst_sect<to_snr) score->lst_sect=to_snr;
              to->draw_sect(is_perc,to_lnr,to_snr);
              sd->d.lnr=to_lnr; // re-assign lnr,snr of copied section in Selected
              sd->d.snr=to_snr;
              sd=sd->nxt;
            }
          }
          m_notes(red);
        }
      }
      break;
    default:
      alert("mouse-up state %d?",state);
  }
  state=eIdle;
  SDL_EventState(SDL_MOUSEMOTION,SDL_IGNORE);
}

void draw_scoreview(BgrWin *scv) {
  scv->clear();
  int lnr,
      snr,
      xend=min((sv.score->len-leftside)*sect_len,scv_w),
      y_top=top_yoff+3,y_bot=scv_h-3;
  ScSection *sec;
  if (xend<=0)
    return;
  if (d_staff) { y_top=top_yoff+80; y_bot=scv_h-80; }
  for (snr=leftside;snr<sv.score->len;++snr) {
    int x=(snr-leftside)*sect_len;
    if (x>=xend-1) break;
    if (snr%meter==0) {
      if (x>0) vlineColor(scv->win,x,0,y_bot,0xb0b0b0ff);
      char nr[10];
      sprintf(nr,"%d",snr/meter+1);
      draw_ttf->draw_string(scv->win,nr,Point(x+2,0));
    }
    else if (meter%8==0) {
      if (x>0 && snr%4==0) vlineColor(scv->win,x,y_top,y_bot,0xe0e0e0ff);
    }
    else if (meter%12==0) {
      if (x>0 && snr%6==0) vlineColor(scv->win,x,y_top,y_bot,0xe0e0e0ff);
    }
  }
  sv.draw_upd_repeat(-1,repeat_end);
  if (d_staff) {
    for (int s_lnr=0;s_lnr<sv.sclin_max*7/12;++s_lnr) {
      int y=top_staff_yoff+s_lnr*sclin_dist;
      Uint32 lcol=linecol.s_col[s_lnr];
      if (lcol)
        hlineColor(scv->win,0,xend-1,y,lcol);
    }
  }
  else {
    for (lnr=0;lnr<sv.sclin_max;++lnr) {
      int y=top_yoff+lnr*sclin_dist;
      Uint32 lcol= linecol.col[(lnr+mv_key_nr)%12];
      if (lcol)
        hlineColor(scv->win,0,xend-1,y,lcol);
    }
  }
  for (lnr=0;lnr<sv.sclin_max;++lnr) {
    int y;
    for (snr=leftside;snr<=sv.score->lst_sect;++snr) {
      int x=(snr-leftside)*sect_len+1;
      if (x>xend-sect_len+1) break;
      sec=sv.score->get_section(lnr,snr);
      if (sec->buf) {
        if (d_staff) {
          int staff_lnr,
              sign;
          lnr_to_staff_lnr(lnr,staff_lnr,sign,sec->get_signs_mode(sv.ip_max));
          y=top_staff_yoff+staff_lnr*sclin_dist;
          sec->draw_playsect(x,y,sign);
        }
        else {
          y=top_yoff+lnr*sclin_dist;
          sec->draw_playsect(x,y,0);
        }
      }
    }
  }
}

void draw_percview(BgrWin *percv) {
  percv->clear();
  int lnr,
      snr,
      xend=min((pv.score->len-leftside)*sect_len,scv_w),
      y_top=3,y_bot=pv_h-3;
  ScSection *sec;

  if (xend<=0) return;
  for (snr=leftside+1;snr<pv.score->len;++snr) {
    int x=(snr-leftside)*sect_len;
    if (x>=xend-1) break;
    if (snr%meter==0) {
      vlineColor(percv->win,x,y_top,y_bot,0xb0b0b0ff);
    }
    else if (meter%8==0) {
      if (x>0 && snr%4==0)
        vlineColor(percv->win,x,y_top,y_bot,0xe0e0e0ff);
    }
    else if (meter%12==0) {
      if (x>0 && snr%6==0)
        vlineColor(percv->win,x,y_top,y_bot,0xe0e0e0ff);
    }
  }
  if (d_alt) {  // hor lines for alt percussion
    int y=ap_yoff+act_perc_nr*ap_ldist;
    hlineColor(percv->win,0,scv_w-1,y,0xb0b0b0ff);
  }
  else {  // hor lines for percussion
    for (lnr=0;lnr<pv.sclin_max;++lnr) {
      int y=scv_yoff+lnr*sclin_dist;
      Uint32 lcol=linecol.col[(lnr+perc_shift)%12];
      if (lcol) {
        hlineColor(percv->win,0,xend-1,y,lcol);
      }
    }
  }
  for (lnr=0;lnr<pv.sclin_max;++lnr) {
    for (snr=leftside;snr<=pv.score->lst_sect;++snr) {
      int x=(snr-leftside)*sect_len+1;
      if (x>xend-sect_len+1) break;
      sec=pv.score->get_section(lnr,snr);
      if (!sec->buf) continue;
      if (d_alt) {
        for (int i=0;i<perc_max;++i) {
          int y=ap_yoff + i*ap_ldist + (lnr-pv.sclin_max/2)/ap_scale;
          // inverse: lnr=(y-ap_yoff-c*ap_ldist)*ap_scale+pv.sclin_max/2;
          sec->draw_ap_sect(i,x,y);
        }
      }
      else {
        int y=scv_yoff+lnr*sclin_dist;
        sec->draw_percsect(x,y);  // light grey if subsection not enabled
      }
    }
  }
}

void ScSection::draw_playsect(int x,int y,int sign) {
  int col;
  if (occ(enab,&col,sv.ip_max)) {
    bool more1=more1_tc(enab,sv.ip_max),
         stacc=false;
    int c=more1 ? 0x909090ff : enab(col) ? col2inst_color[col] : 0xd0d0d0ff,
        x_r=x+sect_len-1;
    if (more1) {
      for (int i=0;i<sv.ip_max;++i) {
        if (buf[i].stacc && enab(i)) { stacc=true; break; }
      }
    }
    else { 
      stacc=buf[col].stacc;
      const float delta_slen=float(sect_len)/subdiv;
      if (buf[col].del_start) x += int(buf[col].del_start*delta_slen);
      if (buf[col].del_end) x_r += int(buf[col].del_end*delta_slen);
    }
    if (stacc) {
      int x1=x_r-4;
      boxColor(sv.scoreview->win,x,y-2,x1,y+2,c);
      filledTrigonColor(sv.scoreview->win,x1+1,y-2,x1+5,y,x1+1,y+2,c);
      if (is_sel(sv.ip_max)) {
        int y_t= sign==eHi ? y : y-1,
            y_b= sign==eLo ? y : y+1;
        boxColor(sv.scoreview->win,x+2,y_t,x_r-3,y_b,0xffffffff); // erase middle
      }
    }
    else {
      boxColor(sv.scoreview->win,x,y-2,x_r,y+2,c);
      if (is_sel(sv.ip_max)) {
        int y_t= sign==eHi ? y : y-1,
            y_b= sign==eLo ? y : y+1;
        boxColor(sv.scoreview->win,x+2,y_t,x_r-2,y_b,0xffffffff);
      }
    }
    if (sign==eHi) hlineColor(sv.scoreview->win,x+3,x_r,y-2,0xffffffff);
    else if (sign==eLo) hlineColor(sv.scoreview->win,x+3,x_r,y+2,0xffffffff);
  }
  else if (buf)
    boxColor(sv.scoreview->win,x,y-2,x+sect_len-1,y+2,0xd0d0d0ff);
}

void ScSection::draw_percsect(int x,int y) {
  int col;
  const float delta_slen=float(sect_len)/subdiv;
  if (occ(p_enab,&col,pv.ip_max)) {
    bool more1=more1_tc(p_enab,pv.ip_max);
    Uint32 c=more1 ? 0x909090ff : p_enab(col) ? col2perc_color[col] : 0xd0d0d0ff;
    if (buf[col].del_start) x+= lrint(buf[col].del_start*delta_slen);
/*
    if (is_sel(pv.ip_max)) circleColor(pv.scoreview->win,x+3,y,3,c);
    else filledCircleColor(pv.scoreview->win,x+3,y,3,c);
*/
    filledCircleColor(pv.scoreview->win,x+3,y,3,c);
    if (is_sel(pv.ip_max))
      circleColor(pv.scoreview->win,x+3,y,1,0xffffffff);
  }
  else if (buf)
    filledCircleColor(pv.scoreview->win,x+3,y,3,0xd0d0d0ff);
}

void ScSection::draw_ap_sect(int col,int x,int y) {
  const float delta_slen=float(sect_len)/subdiv;
  if (occ_color(0,col)) {
    Uint32 c=p_enab(col) ? col2perc_color[col] : 0xd0d0d0ff;
    if (buf[col].del_start) x+= lrint(buf[col].del_start*delta_slen);
    filledCircleColor(pv.scoreview->win,x+3,y,3,c);
    if (is_sel(pv.ip_max))
      circleColor(pv.scoreview->win,x+3,y,1,0xffffffff);
  }
}

void ScSection::draw_sect(bool is_perc,int lnr,int snr) {
  int x=(snr-leftside)*sect_len+1;
  if (x<0 || x>scv_w-sect_len+1) return;
  int y,
      sign=0;
  Uint32 lcol;
  BgrWin *scoreview;
  if (is_perc) {
    scoreview=pv.scoreview;
    if (d_alt) {
      int ydist=lnr-pv.sclin_max/2;
      y=ap_yoff + act_perc_nr*ap_ldist + ydist/ap_scale; // see: draw_percview()
      lcol= ydist ? 0 : 0xb0b0b0ff;
    }
    else {
      y=scv_yoff+lnr*sclin_dist,
      lcol=linecol.col[(lnr+perc_shift)%12];
    }
  }
  else {
    scoreview=sv.scoreview;
    if (d_staff) {
      int staff_lnr;
      lnr_to_staff_lnr(lnr,staff_lnr,sign,sv.up_down_key);
      y=top_staff_yoff+staff_lnr*sclin_dist;
      lcol=linecol.s_col[staff_lnr];
    }
    else {
      y=top_yoff+lnr*sclin_dist;
      lcol=linecol.col[(lnr+mv_key_nr)%12];
    }
  }
  int x2=min(x+sect_len-1,scv_w-1); // the clipping of hline is buggy
  boxColor(scoreview->win,x,y-2,x2,y+2,0xffffffff); // erase section
  if (lcol)
    hlineColor(scoreview->win,x,x2,y,lcol); // needed if not occupied
  if (buf) {
    if (is_perc) {
      if (d_alt) draw_ap_sect(act_perc_nr,x,y);
      else draw_percsect(x,y);
    }
    else draw_playsect(x,y,sign);
  }
  scoreview->blit_upd(rp(x-sect_len,y-3,sect_len*3,7)); // note length maybe increased by del_end or negative del_start
}

void ScSection::draw_ghost(bool is_perc,int lnr,int snr,bool erase) {
  if (is_perc) draw_perc_ghost(lnr,snr,erase);
  else draw_ghost(lnr,snr,erase);
}

void ScSection::draw_ghost(int lnr,int snr,bool erase) {
  BgrWin *view=sv.scoreview;
  int x=(snr-leftside)*sect_len+1;
  if (x<0 || x>scv_w-sect_len+1 || buf) return;
  int y=top_yoff+lnr*sclin_dist;  // same as in draw_sect()
  Uint32 lcol=linecol.col[(lnr+mv_key_nr)%12];
  int x2=min(x+sect_len-1,scv_w-1);
  boxColor(view->win,x,y-2,x2-1,y+2,erase ? 0xffffffff : 0xa0a0a0ff);
  if (lcol && erase)
    hlineColor(view->win,x,x2,y,lcol);
  view->blit_upd(rp(x,y-2,sect_len,5));
}

void ScSection::draw_perc_ghost(int lnr,int snr,bool erase) {
  BgrWin *view=pv.scoreview;
  int x=(snr-leftside)*sect_len+1;
  if (x<0 || x>scv_w-sect_len+1 || buf) return;
  int y=scv_yoff+lnr*sclin_dist;  // same as in draw_sect()
  Uint32 lcol=linecol.col[(lnr+perc_shift)%12];
  filledCircleColor(view->win,x+3,y,3,erase ? 0xffffffff : 0xa0a0a0ff);
  if (lcol && erase) {
    int x2=min(x+sect_len-1,scv_w-1);
    hlineColor(view->win,x,x2,y,lcol);
  }
  view->blit_upd(rp(x,y-2,sect_len,5));
}

void ScSection::set_delay(int act_nr,int key) {
  SubSection &ssec=buf[act_nr];
  const int delta=subdiv/3;
  if (triad_mode->act_rbutnr()==0)  // dur = 2
    switch (key) {
      case 1: ssec.del_end=-delta; break;  
      case 2: ssec.del_start=2*delta; ssec.del_end=delta; break;
      case 3: ssec.del_start=delta; break;
      default: alert("expected: 1, 2 or 3");
    }
  else  // dur = 4
    switch (key) {
      case 1: ssec.del_end=delta; break;
      case 2: ssec.del_start=delta; ssec.del_end=2*delta; break;
      case 3: ssec.del_start=-delta; break;
      default: alert("expected: 1, 2 or 3");
    }
}

void ScoreView::sel_column(int snr,int col) { // default: col = -1
  ScSection *sec;
  for (int lnr=0;lnr<sclin_max;++lnr) {
    sec=score->get_section(lnr,snr);
    bool b;
    if (is_perc) {
      if (col<0) {
        b=sec->occ(p_enab,0,ip_max);
        if (b) {
          for (int c=0;c<ip_max;++c)
            if (sec->occ_color(p_enab,c)) { 
              sec->set_csel(true,c);
            }
          sec->draw_sect(true,lnr,snr);
          selected.insert(lnr,snr,sec);
        }
      }
      else {
        b=sec->occ_color(p_enab,act_nr);
        if (b) {
          sec->set_csel(true,col);
          sec->draw_sect(true,lnr,snr);
          selected.insert(lnr,snr,sec);
        }
      }
    }
    else {
      if (col<0) {
        b=sec->occ(enab,0,ip_max);
        if (b) {
          up_down_key=sec->get_signs_mode(sv.ip_max);  // NB! up_down_key is used by draw_sect()
          for (int c=0;c<ip_max;++c)
            if (sec->occ_color(enab,c)) { 
              sec->set_csel(true,c);
            }
          sec->draw_sect(false,lnr,snr);
          selected.insert(lnr,snr,sec);
        }
      }
      else {
        b=sec->occ_color(enab,act_nr);
        if (b) {
          up_down_key=sec->buf[act_nr].signs_mode;
          sec->set_csel(true,col);
          sec->draw_sect(false,lnr,snr);
          selected.insert(lnr,snr,sec);
        }
      }
    }
  }
}

void ScoreView::edit_down(ScSection* sec,int lnr,int snr,int but) {
  int key=0;
  if (but==SDL_BUTTON_LEFT || but==SDL_BUTTON_MIDDLE) {
    if (!enab(act_nr)) {
      alert("%s not enabled",iprop[act_nr].name);
      state=eIdle;
      return;
    }
    if (sec->occ_color(enab,act_nr)) {
      state=eErasingCol;
      if (sec->is_sel(ip_max))      // any color selected?
        selected.remove(lnr,snr,sec);
      sec->reset(act_nr,ip_max);
    }
    else if ((keystate[SDLK_1] && (key=1)) || (keystate[SDLK_2] && (key=2)) || (keystate[SDLK_3] && (key=3)) ) {
      sec->set(act_nr,true,but==SDL_BUTTON_MIDDLE,up_down_key);
      sec->set_delay(act_nr,key);
      //sec->draw_sect(false,lnr,snr);
    }
    else {
      if (ctr_data[act_nr].slashed_notes) {
        if (state!=eIdle) alert("slashed notes: state?");
        for (int n=0;;) {
          if (++n==nupq) {
            sec->set(act_nr,true,true,up_down_key);
            break;
          }
          sec->set(act_nr,true,false,up_down_key);
          sec->draw_sect(is_perc,lnr,snr);
          sec=score->get_section(lnr,++snr);
        }
      }
      else {
        state=eTrackingCol;
        sec->set(act_nr,true,false,up_down_key);
      }
    }
  }
  else if (but==SDL_BUTTON_RIGHT) {
    if (sec->occ(enab,0,ip_max)) {
      if (sec->is_sel(ip_max))      // any color selected?
        selected.remove(lnr,snr,sec);
      state=eErasing;
      sec->reset();
    }
    else {
      state=eTracking;
      sec->set(act_nr,true,false,up_down_key);
    }
  }
  else
    return;
  if (score->lst_sect<snr) score->lst_sect=snr;
  m_notes(red);
  sec->draw_sect(is_perc,lnr,snr);
  SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
}

void ScoreView::edit_percdown(ScSection* sec,int lnr,int snr,int but) {
  int key=0;
  state=eIdle;
  if (but==SDL_BUTTON_LEFT || but==SDL_BUTTON_MIDDLE) {
    if (!p_enab(act_nr)) {
      alert("%s not enabled",pprop[act_nr].name);
      return;
    }
    if (sec->occ_color(p_enab,act_nr)) {
      sec->reset(act_nr,ip_max);
    }
    else if ((keystate[SDLK_1] && (key=1)) || (keystate[SDLK_2] && (key=2)) || (keystate[SDLK_3] && (key=3)) ) {
      sec->p_set(act_nr,true);
      sec->set_delay(act_nr,key);
    }
    else {
      sec->p_set(act_nr,true);
    }
  }
  else if (but==SDL_BUTTON_RIGHT) {
    if (d_alt) { alert("alt display: R button not supported"); return; }
    if (sec->occ(enab,0,ip_max))
      sec->reset();
    else
      sec->p_set(act_nr,true);
  }
  else
    return;
  if (score->lst_sect<snr) score->lst_sect=snr;
  m_notes(red);
  sec->draw_sect(true,lnr,snr);
}

void ScoreView::select_down(ScSection *sec,const int lnr,int snr,int but) {
  if (but==SDL_BUTTON_MIDDLE) { // select all notes at right side
    for (;snr<=score->lst_sect;++snr) sel_column(snr);
  }
  else if (but==SDL_BUTTON_LEFT) {
    if (!enab(act_nr)) {
      alert("%s not enabled",iprop[act_nr].name);
      state=eIdle;
      return;
    }
    if (sec->occ_color(enab,act_nr)) { // select rest of note with act_nr
      bool b=sec->is_csel(act_nr);
      while (true) {
        if (b) {
          if (sec->is_csel(act_nr))
            selected.remove(lnr,snr,sec);  // all colors deselected
        }
        else {
          sec->set_csel(true,act_nr);
          selected.insert(lnr,snr,sec);
        }
        sec->draw_sect(is_perc,lnr,snr);
        if (sec->buf[act_nr].stacc) break;
        if (snr==score->lst_sect || snr==score->len-1) break;
        sec=score->get_section(lnr,++snr);
        if (!sec->occ_color(enab,act_nr)) break;
      }
    }
    else {
      state=eCollectSel1;
      SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
      sel_column(snr,act_nr);
    }
  }
  else if (but==SDL_BUTTON_RIGHT) {
    int scol;
    if (sec->occ(enab,&scol,ip_max)) {        // any color occupied?
      bool b=sec->is_sel(ip_max);  // if b, then at least 1 color selected
      while (true) {
        if (b) {
          if (sec->is_sel(ip_max))      // any color selected?
            selected.remove(lnr,snr,sec);
        }
        else {
          sec->set_sel(true,ip_max);        // all colors selected
          selected.insert(lnr,snr,sec);
        }
        sec->draw_sect(is_perc,lnr,snr);
        if (sec->buf[scol].stacc) break;  // stacc of first occupied color
        if (snr==score->lst_sect || snr==score->len-1) break;
        sec=score->get_section(lnr,++snr);
        if (!sec->occ(enab,0,ip_max)) break;    // no color occupied?
      }
    }
    else {
      state=eCollectSel;
      SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
      sel_column(snr);
    }
  }
}

void ScoreView::select_percdown(ScSection *sec,const int lnr,int snr,int but) {
  if (but==SDL_BUTTON_MIDDLE) { // select all notes at right side
    if (d_alt) { alert("alt display: M button not supported"); return; }
    for (;snr<=score->lst_sect;++snr) sel_column(snr);
  }
  else if (but==SDL_BUTTON_LEFT) {
    if (!p_enab(act_nr)) {
      alert("%s not enabled",pprop[act_nr].name);
      state=eIdle;
      return;
    }
    if (sec->occ_color(p_enab,act_nr)) { // select rest of note with act_nr
      bool b=sec->is_csel(act_nr);
      if (b) {
        if (sec->is_csel(act_nr))
          selected.remove(lnr,snr,sec);
      }
      else {
        sec->set_csel(true,act_nr);
        selected.insert(lnr,snr,sec);
      }
      sec->draw_sect(is_perc,lnr,snr);
    }
    else {
      state=eCollectSel1;
      SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
      sel_column(snr,act_nr);
    }
  }
  else if (but==SDL_BUTTON_RIGHT) {
    if (d_alt) { alert("alt display: R button not supported"); return; }
    int scol;
    if (sec->occ(p_enab,&scol,ip_max)) {        // any color occupied?
      bool b=sec->is_sel(ip_max);  // if b, then at least 1 color selected
      if (b) {
        if (sec->is_sel(ip_max))      // any color selected?
          selected.remove(lnr,snr,sec);
      }
      else {
        sec->set_sel(true,ip_max);        // all colors selected
        selected.insert(lnr,snr,sec);
      }
      sec->draw_sect(true,lnr,snr);
    }
    else {
      state=eCollectSel;
      SDL_EventState(SDL_MOUSEMOTION,SDL_ENABLE);
      sel_column(snr);
    }
  }
}

void sv_scroll_cmd(Id,int val,int rang) {
  if (leftside!=val/sect_len) {
    leftside=val/sect_len;
    iv.infoview->draw_blit_upd();
    sv.scoreview->draw_blit_upd();
    pv.scoreview->draw_blit_upd();
  }
}

void i_checkb_label(SDL_Surface *win,int nr,int y_off) {
  boxColor(win,16,5,win->w,9,col2inst_color[nr]);
}

void p_checkb_label(SDL_Surface *win,int nr,int y_off) {
  filledCircleColor(win,20,7,3,col2perc_color[nr]);
}

void draw_curve(SDL_Surface *win,Rect *r,int *buf,int dim,Uint32 color) {
  int val2=buf[0];
  pixelColor(win,r->x,val2+r->y,color);
  for (int i=0;i<dim-1;++i) {
    int val1=val2;
    val2=buf[i+1];
    if (abs(val1-val2)<=1)
      pixelColor(win,i+1+r->x,val2+r->y,color);
    else
      lineColor(win,i+r->x,val1+r->y,i+1+r->x,val2+r->y,color);
  }
}

void handle_user_event(int cmd,int par1,int par2) {
  switch (cmd) {
    case 'scop': {
        Rect *sr=&scope_win->area;
        top_win->clear(*sr,scope_win->bgcol,false);
        draw_curve(top_win->win,sr,scope_buf1,scope_dim,0xff);
        draw_curve(top_win->win,sr,scope_buf2,scope_dim,0xd0ff);
        update(sr);
        scope.scope_start=0;
      }
      break;
    case 'endr':
      play_but->label.draw_cmd=right_arrow;
      play_but->draw();
      play_but->blit_upd(0);
      break;
    case 'csnr': // current snr
      txtmes2->draw_mes(" %d ",par1/meter+1);
      break;
    default: puts("uev?");
  }
}

void handle_key_event(SDL_keysym *key,bool down) {
  if (down) SDL_EnableKeyRepeat(0,0);
  else
    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,SDL_DEFAULT_REPEAT_INTERVAL);
  switch (key->sym) {
    case SDLK_c:
    case SDLK_d:
    case SDLK_k:
    case SDLK_m:
    case SDLK_n:
    case SDLK_UP:
    case SDLK_DOWN:
    case SDLK_1: case SDLK_2: case SDLK_3:
    case SDLK_i:
    case SDLK_a:
      break;
    case SDLK_p:
      if (keystate[SDLK_LCTRL]) {
        if (down) fm_data[iprop[act_inst_nr].patch_nr].print_patch();
      }
      break;
    case SDLK_s:
      scv_mode= down ? eSelect : eEdit;
      break;
    case SDLK_LALT:
    case SDLK_LCTRL:
      break;
    default:
      if (!down) alert("unexpected key");
  }
}

void audio_callback(void*, Uint8 *stream, int len) {
  audio_callback(stream,len);
}

void upd_meas_display(int note_start_snr) {
  if (cur_snr < note_start_snr) {
    for (++cur_snr;;++cur_snr) {
      if (cur_snr % meter == 0)  // update measure nr display
        send_uev('csnr',cur_snr);
      if (cur_snr == note_start_snr) break;
    }
  }
}

bool test_remain(NoteBuffer* nbuf,Note*& note,int voice,float value,const int the_tempo) {
  int n=note->remain + note->gap;
  if (n > 0 || !(abs(value)<10 || (nbuf->prev_val<0 && value>0))) { // upwards zero crossing point, preventing clicks
    note->remain-=the_tempo;
    nbuf->prev_val=value;
    return false;
  }
  note = ++NOTES[voice];
  note->remain += n;
  nbuf->reset();
  return true;
}

float NoisySinus::get_val(float &ind_f,InstrumentData *harmd,float freq) {
  float value=get(ind_f,freq/basefreq) * harmd->hlin[1]; // base freq
  for (int har=2;har<num_harms;++har) {
    if (harmd->hlin[har]) value += buf[int(ind_f * har) % dim] * harmd->hlin[har];
  }
  return value;
}

float Sinus::get_value(float ind_f1,float ind_f2,int chorus_y,char *hlin) {
  float value=0,
        mix=chorus_mix[chorus_y];
  for (int har=1;har<num_harms;++har) {
    if (hlin[har]) {
      if (chorus_y) {
        float val1=buf[int(ind_f1 * har) % dim],
              val2=buf[int(ind_f2 * har) % dim];
        value += (val1*(1.-mix) + val2*mix) * hlin[har] * 0.6;
      }
      else 
        value += buf[int(ind_f1 * har) % dim] * hlin[har];
    }
  }
  return value;
}

float attack_val(Note *note,NoteBuffer *nbuf,float val) {
  float value;
  int n=note->dur - note->remain;
  if (n > note->attack) value=val;
  else {
    float mix=float(n)/note->attack,
          prev_ampl=nbuf->ampl;
    value=val * (mix<prev_ampl ? prev_ampl : mix);
  }
  return value;
}

float attack_val(Note *note,NoteBuffer *nbuf,float val1,float val2) {
  float value;
  int n=note->dur - note->remain;
  if (n > note->attack) value=val2;
  else {
    float mix=float(n)/note->attack,
          prev_ampl=nbuf->ampl;
      value=val2 * (mix<prev_ampl ? prev_ampl : mix);
      if (mix<0.1)
        value += 10 * mix * val1;
      else {
        mix=2. * mix - 1.;
        if (mix<0)
          value += val1;
        else
          value += val1 * (1.-mix);
      }
  }
  return value;
}

float attack_val(Note *note,NoteBuffer *nbuf,float start_amp,float val1,float val2) {
  float value;
  int n=note->dur - note->remain;
  if (n > note->attack) value=val2;
  else {
    float mix=float(n)/note->attack,
          prev_ampl=nbuf->ampl;
    value=val2 * (mix<prev_ampl ? prev_ampl : mix);
    mix=2. * mix - 1.;
    if (mix<0)
      value += val1 * (prev_ampl>start_amp ? prev_ampl : start_amp);
    else
      value += val1 * (prev_ampl>start_amp ? prev_ampl : start_amp) * (1.-mix);
  }
  return value;
}

void clip(float& val,int clip_index) {
  if (clip_index==0) return;
  const float level=40000.;
  val *= level/clip_arr[clip_index];
  if (val>0) {
    if (val>level) val=level;
    else val= 2*(val-val*val/2/level);
  }
  else {
    if (val<-level) val= -level;
    else val= 2*(val+val*val/2/level);
  }
}

void audio_callback(Uint8 *stream, int len) {
  // always: IBsize = len/4
  int buffer[IBsize*2],
      n,
      ind_buf;
  const int the_tempo=tempo*nupq*subdiv/6;  // why 6?
  float val1=0,val2=0,  // startup, steady
        value,
        freq;
  bool stop_requested=true;
  Note *note;
  InstrCtrlData *ctrl;
  InstrumentData *instr;
  GusPatData *gpd;
  SampleData *gsampd;
  PercData *perc;
  for (int i=0;i<IBsize*2;++i) buffer[i]=0;
  for (int voice=0;voice<=nbufs.voice_end;++voice) {
    NoteBuffer *const nbuf=nbufs.nbuf+voice;
    ind_buf=-1;
    note=NOTES[voice];
    loop_start:
    ++ind_buf;
    switch (note->cat) {
      case eSleep:
        break;
      case ePause:
        stop_requested=false;
        for (;ind_buf<IBsize;++ind_buf) {
          n=note->remain;
          if (n > 0) note->remain-=the_tempo;
          else {
            note = ++NOTES[voice];
            note->remain += n;
            nbuf->reset();
            nbuf->ampl=0;
          }
          goto loop_start;
        }
        break;
      case eNote:
        stop_requested=false;
        upd_meas_display(note->start_snr);
        ctrl=ctr_data + note->note_col;
        instr=fm_data + iprop[note->note_col].patch_nr;
        for (;ind_buf<IBsize;++ind_buf) {
          if (note->remain>=0) {
            instr->nxt_val(note->freq,nbuf,&val1,&val2);
            if (note->attack)
              value=attack_val(note,nbuf,sta_arr[instr->start_amp],val1,val2);
            else
              value=val2;
            value *= amp_arr[ctrl->ampl] * amp_mul;
          }
          else if (note->decay) {
            if (note->decay>= -note->remain) {
              instr->nxt_val(note->freq,nbuf,0,&val2);
              nbuf->ampl=1. + float(note->remain)/note->decay;
              nbuf->ampl *= nbuf->ampl; // quadratic decay
              value= val2 * nbuf->ampl * amp_arr[ctrl->ampl] * amp_mul;
            }
            else value=0;
          }
          else value=0;
          buffer[ind_buf*2]+=int(value*pan_arr[ctrl->pan]);
          buffer[ind_buf*2+1]+=int(value*(1.-pan_arr[ctrl->pan]));
          if (test_remain(nbuf,note,voice,value,the_tempo))
            goto loop_start;
        }
        break;
      case eGusPatch:
        stop_requested=false;
        upd_meas_display(note->start_snr);
        ctrl=ctr_data + note->note_col;
        gpd=gpat_data+iprop[note->note_col].patch_nr;
        instr=fm_data + iprop[note->note_col].patch_nr;
        gsampd=gpd->samples+note->samp_nr;
        for (;ind_buf<IBsize;++ind_buf) {
          if (note->remain>=0) {
            gsampd->next_val(note->freq,nbuf,&value,instr->chorus);
            value *= amp_arr[ctrl->ampl];
          }
          else if (note->decay) {
            if (note->decay>= -note->remain) {
              gsampd->next_val(note->freq,nbuf,&value,instr->chorus);
              nbuf->ampl=1. + float(note->remain)/note->decay;
              nbuf->ampl *= nbuf->ampl; // quadratic decay
              value *= nbuf->ampl * amp_arr[ctrl->ampl];
            }
            else value=0;
          }
          else value=0;
          buffer[ind_buf*2]+=int(value*pan_arr[ctrl->pan]);
          buffer[ind_buf*2+1]+=int(value*(1.-pan_arr[ctrl->pan]));
          if (test_remain(nbuf,note,voice,value,the_tempo))
            goto loop_start;
        }
        break;
      case eHarm:
        stop_requested=false;
        upd_meas_display(note->start_snr);
        instr=fm_data + iprop[note->note_col].patch_nr;
        ctrl=ctr_data + note->note_col;
        for (;ind_buf<IBsize;++ind_buf) {
          if (note->remain>=0) {
            if (note->noisy) {
              val2=noisy_sinus.get_val(nbuf->ind_f1,instr,note->freq);
              if (note->attack)
                value=attack_val(note,nbuf,val2);
              else value=val2;
            }
            else {
              float f1,f2;
              f1=clean_sinus.calc_ind(nbuf->ind_f1,note->freq);
              f2=instr->chorus.y ? clean_sinus.calc_ind(nbuf->ind_f2,note->freq,instr->chorus.x) : 0.;
              val2=clean_sinus.get_value(f1,f2,instr->chorus.y,instr->hlin);
              if (note->attack) {
                val1=clean_sinus.get_value(f1,f2,instr->chorus.y,instr->click_hlin);
                value=attack_val(note,nbuf,val1,val2);
              }
              else value=val2;
            }
            clip(value,instr->clip);
            value *= amp_arr[ctrl->ampl];
          }
          else if (note->decay) {
            if (note->decay>= -note->remain) {
              if (note->noisy) value=noisy_sinus.get_val(nbuf->ind_f1,instr,note->freq);
              else {
                float f1,f2;
                f1=clean_sinus.calc_ind(nbuf->ind_f1,note->freq);
                f2=instr->chorus.y ? clean_sinus.calc_ind(nbuf->ind_f2,note->freq,instr->chorus.x) : 0.;
                value=clean_sinus.get_value(f1,f2,instr->chorus.y,instr->hlin);
              }
              clip(value,instr->clip);
              nbuf->ampl=1. + float(note->remain)/note->decay;
              nbuf->ampl *= nbuf->ampl; // quadratic decay
              value *= nbuf->ampl * amp_arr[ctrl->ampl];
            }
            else value=0;
          }
          else value=0;
          buffer[ind_buf*2]+=int(value*pan_arr[ctrl->pan]);
          buffer[ind_buf*2+1]+=int(value*(1.-pan_arr[ctrl->pan]));
          if (test_remain(nbuf,note,voice,value,the_tempo))
            goto loop_start;
        }
        break;
      case eSampled:
        stop_requested=false;
        upd_meas_display(note->start_snr);
        perc=perc_data + note->note_col;
        freq=note->freq/mid_c;
        for (;ind_buf<IBsize; ++ind_buf) {
          if (wave_buffer[note->note_col].next_val(freq,nbuf,&value)) {
            value *= amp_arr[perc->ampl];
            buffer[ind_buf*2] += int(value * pan_arr[perc->pan]);
            buffer[ind_buf*2+1] += int(value * (1.-pan_arr[perc->pan]));
          }
          n=note->remain;
          if (n > 0) note->remain-=the_tempo;
          else {
            note = ++NOTES[voice];
            note->remain += n;
            nbuf->reset();
            goto loop_start;
          }
        }
        break;
      default:
        alert("unknown note cat %d",note->cat);
    }
  }
  Sint16 *out=reinterpret_cast<Sint16*>(stream);
  for (int i=0;i<len/2;i+=2) {
    out[i]=minmax(-30000,buffer[i],30000);
    out[i+1]=minmax(-30000,buffer[i+1],30000);
    scope.update(out,i);
  }
  if (stop_requested) {
    if (set_repeat->value()) {
      nbufs.restore_notes();
    }
    else stop_req=true;
  }
}

void init_audio() {
  SDL_AudioSpec *ask=new SDL_AudioSpec,
                *got=new SDL_AudioSpec;
  ask->freq=SAMPLE_RATE;
  ask->format=AUDIO_S16SYS;
  ask->channels=2;
  ask->samples=4096;
  ask->callback=audio_callback;
  ask->userdata=0;
  if (SDL_OpenAudio(ask, got) < 0) {
     alert("Couldn't open audio: %s",SDL_GetError());
     exit(1);
  }
  if (debug)
    printf("init_audio: samples=%d channels=%d freq=%d format=%d (LSB=%d) size=%d\n",
           got->samples,got->channels,got->freq,got->format,AUDIO_S16LSB,got->size);
  if (got->freq!=SAMPLE_RATE) alert("unexpected samplerate: %d",got->freq);
  IBsize=got->samples;
  SDL_PauseAudio(0);
}

int play_threadfun(void* data) {
  if (SDL_GetAudioStatus()!=SDL_AUDIO_PLAYING)
    init_audio();
  scope.scope_start=0;
  cur_snr=leftside;
  stop_req=false;
  while (!stop_req) {
    SDL_Delay(10);
  }
  SDL_CloseAudio();
  i_am_playing=false;
  send_uev('endr');
  return 0;
}

int same_color(int lnr,int snr,int col,int end,SubSection*& lst) {
  lst=0;
  int n;
  SubSection *ssec=0;
  for (n=0;;++n,++snr) {
    ScSection *sec=sv.score->get_section(lnr,snr);
    if (!sec->occ_color(enab,col)) { lst=ssec; return n; }
    ssec=sec->buf+col;
    if (ssec->cat==ePlay) ssec->cat=ePlay_x;
    else { lst=ssec; return n; }
    if (snr>=end || ssec->stacc) { lst=ssec; return n+1; }
  }
}

void Scope::update(Sint16 *buffer,int i) {
  if (scope_start<0) return;
  if (scope_start==0) {  // set by handle_user_event()
    if (i>=2 && ((buffer[i-2]<=0 && buffer[i]>0) || (buffer[i-1]<=0 && buffer[i+1]>0))) {
      scope_start=1;
      pos=0;
    }
    return;
  }
  if (i%16==0) {
    if (pos<scope_dim) {
      static const int div=32000/scope_h;
      scope_buf1[pos]=buffer[i]/div + scope_h;
      scope_buf2[pos]=buffer[i+1]/div + scope_h;
      ++pos;
    }
    else {
      scope_start=-1;
      send_uev('scop');
    }
  }
}

void Selected::modify_sel(ScoreView *sview,int mes,const int dim) {
  SLList_elem<SectData> *sd;
  ScSection *sec;
  int act_nr=sview->act_nr;
  for (sd=sd_list.lis;sd;) {
    sec=sview->score->get_section(sd->d.lnr,sd->d.snr);
    switch (mes) {
      case 'uns':
        delete[] sd->d.sect.buf;
        sec->set_sel(false,ip_max);
        sec->draw_sect(sview->is_perc,sd->d.lnr,sd->d.snr);
        break;
      case 'rcol':
        for (int c=0;c<dim;++c) {
          if (c!=act_nr && sec->is_csel(c)) { sec->buf[act_nr]=sec->buf[c]; sec->zero(c); }
        }
        sec->set_csel(true,act_nr);
        sd->d.sect.copy(sec,ip_max); // re-assign copied section in Selected
        sec->draw_sect(sview->is_perc,sd->d.lnr,sd->d.snr);
        m_notes(red);
        break;
      case 'adto':
        for (int c=0;c<dim;++c) {
          if (c!=act_nr && sec->is_csel(c)) {
            sec->buf[act_nr]=sec->buf[c];
            sec->set_csel(true,act_nr);
            sec->set_csel(false,c);
            sd->d.sect.copy(sec,ip_max); // re-assign copied section in Selected
            sec->draw_sect(sview->is_perc,sd->d.lnr,sd->d.snr);
            m_notes(red);
            break;
          }
        }
        break;
      case 'dels':
        delete[] sd->d.sect.buf;
        for (int c=0;c<dim;++c) {
          if (sec->is_csel(c)) sec->reset(c,dim);  // maybe sec->buf = 0
          if (!sec->buf) break;
        }
        sec->draw_sect(sview->is_perc,sd->d.lnr,sd->d.snr);
        m_notes(red);
        break;
      default: alert("modify_sel?");
    }
    sd=sd->nxt;
  }
  switch (mes) {
    case 'dels':
      sview->selected.reset();
      unselect->draw_blit_upd();
      break;
    case 'uns':
      sview->selected.reset();
      break;
  }
}

void Selected::print_hint() {
  if (!sd_list.lis) { alert("no notes selected"); return; }
  SLList_elem<SectData> *sd;
  int cnt;
  bool seld_notes[12],  // selected notes
       avail_notes[12]; // available scale notes
  for (int i=0;i<12;++i) 
    seld_notes[i]=false;
  for (cnt=0,sd=sd_list.lis;sd;sd=sd->nxt) {
    if (sd->d.sect.occ(enab,0,ip_max)) { ++cnt; seld_notes[lnr_to_midinr(sd->d.lnr)%12]=true; }
  }
  if (cnt<3) { alert("notes < 3"); return; }
  if (info_to_term) printf("chords (key %s):\n",maj_keys[abc_key]); else alert("chords (key %s):",maj_keys[abc_key]);
  for (Chord *ch=chords;ch->name;++ch) {
    for (int i=0;i<12;++i) 
      avail_notes[i]=false;
    for (int i=0;;++i) { // fill avail_notes[]
      int ind=ch->scale[i];
      if (ind<0) break;
      avail_notes[ind]=true;
    }
    for (int start=0;start<12;++start) {
      for (int i=0;i<12;++i) {
        ch->result[start][i]=true;
        if (seld_notes[i] && !avail_notes[(start+i)%12]) ch->result[start][i]=false;
      }
    }
  }
  bool stop=false;
  for (int badnote=0;badnote<3;++badnote) {
    for (Chord *ch=chords;ch->name;++ch) {
      for (int start=0;start<12;++start) {
        bool avoid[12];
        int mismatch=0;
        for (int i=0;i<12;++i) avoid[i]=false;
        for (int i=0;i<12;++i) {  // count nr mismatches
          if (!ch->result[start][i]) {
            avoid[i]=true;
            ++mismatch;
          }
        }
        if (mismatch==badnote) {
          if (!keystate[SDLK_a]) stop=true;
          int offset=(12-start)%12;
          if (info_to_term) {
            ch->print_chord(offset);
            if (badnote>0) {
              fputs(" avoid:",stdout);
              const char *comma="";
              for (int i=0;i<12;++i)
                if (avoid[i]) {
                  int deg=i-offset;
                  if (deg<0) deg+=12;
                  printf("%s%s",comma,degree_name[deg]);
                  comma=",";
                }
            }
            putchar('\n');
          }
          else {
            int degree=offset-abc_key;
            if (degree<0) degree+=12;
            alert("   %s %s%s (%s)",ch->chordt_list(0),maj_keys[offset],ch->name,degree_name[degree]);
          }
        }
      }
    }
    if (stop || !info_to_term) break; // avoid notes: only to terminal
  }
  if (info_to_term) fflush(stdout);
}

bool NoteBuffers::fill_note_bufs(int play_start,int play_stop,int task) { // task: 0, eMidiOut, ePsOut
  int samecol=0,
      v,   // voice
      lnr,snr,
      pause;
  bool result=false;
  ScSection *sec;
  SubSection *lst_ssect;
  Note *note;
  NoteBuffer *lst_nb=0;
  reset();
  for (snr=play_start;snr<=sv.score->lst_sect || snr<=pv.score->lst_sect;++snr) {
    if (play_stop>0 && snr>play_stop) {
      break;
    }
    for (lnr=0;lnr<sv.sclin_max && snr<=sv.score->lst_sect;++lnr) {
      sec=sv.score->get_section(lnr,snr);
      if (sec->occ(enab,0,sv.ip_max)) {
        for (int c=0;c<inst_max;++c) {
          if (!enab(c)) continue;
          if (sec->occ_color(enab,c)) {
            InstrumentData *idat=col2instr(c);
            const SubSection ssec=sec->buf[c];
            const int note_cat=idat->patch_but==1 ? eGusPatch : idat->patch_but==2 ? eHarm : eNote;
            int start_del=ssec.del_start;
            if (swing_instr && task!=ePsOut && start_del==0 && snr%nupq) start_del=nupq/2;
            find_free_voice(c,start_del,v,pause,snr/meter,idat->mphonic); // uses: busy
            if (v<0) break;
            NoteBuffer *const nb=nbuf+v;
            lst_nb=nb;
            samecol=same_color(lnr,snr,c,sv.score->lst_sect,lst_ssect);
            if (task==0 && fm_data[iprop[c].patch_nr].shortdur) samecol=1;
            if (debug) printf("fill_note_bufs: scol=%d c=%d snr=%d lnr=%d lst=%p\n",samecol,c,snr,lnr,lst_ssect);
            if (pause > 0) {
              if (++nb->cur_note >= nb->nlen) nb->renew();
              note=nb->notes + nb->cur_note;
              note->cat=ePause;
              note->set_timing(pause);
              nb->busy+=pause;
            }
            if (++nb->cur_note>=nb->nlen) nb->renew();
            note=nb->notes+nb->cur_note;
            note->set(
              c,
              lnr,
              note_cat,
              snr,
              snr*subdiv+start_del,
              ctr_data[c].slashed_notes
            );
            if (task==eMidiOut) {
              note->midi_instr=iprop[c].midi_nr;
              if (note->midi_instr<0) {
                alert("warning: unmapped midi instr: color %s",iprop[c].name);
                note->midi_instr=0;
              }
              // MIDI standard: volume = 40*log(value/127) dB
              int amp=ctr_data[c].midi_ampl * 21; // 21 = 1 / 7 * 150
              note->midi_ampl=minmax(1,amp,127);
              note->set_timing(samecol * subdiv - start_del + lst_ssect->del_end);
            }
            else if (task==ePsOut) {
              note->set_timing(samecol * subdiv - start_del + lst_ssect->del_end);
              note->calc_triadcat();
            }
            else {
              if (play_stop>0) {
                int dur;
                if (snr+samecol>play_stop) {
                  samecol=play_stop-snr;
                  dur=samecol * subdiv - start_del + lst_ssect->del_end - 1; // room for decay=1
                  note->set_timing(dur);
                  note->decay=note->gap=1;
                }
                else {
                  dur=samecol * subdiv - start_del + lst_ssect->del_end;
                  note->set_timing(dur);
                  note->decay=note->gap=dcay_arr[idat->decay];
                  int headroom=play_stop*subdiv - (snr*subdiv + note->decay);
                  if (headroom<0) note->decay=note->gap=note->decay+headroom;
                }
              }
              else {
                int dur=samecol * subdiv - start_del + lst_ssect->del_end;
                note->set_timing(dur);
                note->decay=note->gap=dcay_arr[idat->decay];
              }
              note->attack= note->cat==eHarm ? min(dcay_arr[idat->attack],max(note->dur-2,0)) :
                                               min(su_arr[idat->startup],max(note->dur-2,0));
              if (note_cat==eGusPatch) {
                if (!note->calc_samp_ind(iprop[c].patch_nr)) goto end;
              }
            }
            nb->busy+=note->dur;
          }
        }
      }
    }
    for (lnr=0;lnr<pv.sclin_max && snr<=pv.score->lst_sect;++lnr) { // sampled notes
      sec=pv.score->get_section(lnr,snr);
      if (sec->occ(p_enab,0,pv.ip_max)) {
        for (int c=0;c<perc_max;++c) {
          if (!p_enab(c)) continue;
          if (sec->occ_color(p_enab,c)) {
            SubSection ssec=sec->buf[c];
            int start_del=ssec.del_start,
                end_del=ssec.del_end;
            if (swing_perc && task!=ePsOut && start_del==0 && snr%nupq) start_del=nupq/2;
            find_free_voice(c,start_del,v,pause,snr/meter,false); // uses: busy
            if (v<0) break;
            NoteBuffer *const nb=nbuf+v;
            if (pause > 0) {
              if (++nb->cur_note >= nb->nlen) nb->renew();
              note=nb->notes + nb->cur_note;
              note->cat=ePause;
              note->set_timing(pause);
              nb->busy+=pause;
            }
            if (++nb->cur_note>=nb->nlen) nb->renew();
            note=nb->notes+nb->cur_note;
            lst_nb=nb;
            note->set(
              c,
              lnr+perc_shift,
              eSampled,
              snr,
              snr*subdiv+start_del,
              false
            );
            if (task==eMidiOut)
              note->set_timing(subdiv);
            else if (task==ePsOut) {
              int dur=subdiv - start_del + end_del;
              note->set_timing(dur);
              note->calc_triadcat();
            }
            else {
              int size=wave_buffer[c].wav_size;
              if (!size) {
                if (!wave_buffer[c].fname) {
                  alert("%s: no wave file",pprop[c].name);
                  goto end;
                }
                wave_buffer[c].load_wav();
                if (!(size=wave_buffer[c].wav_size)) {
                  alert("%s: no wave data",pprop[c].name);
                  goto end;
                }
              }
              if (size<0 || size>100*SAMPLE_RATE) {
                alert("wave data corrupted? (size=%d)",size);
                goto end;
              }
              const int the_tempo=tempo*nupq*subdiv/6; // same as in audio_callback()
              int dur=size * the_tempo * mid_c / freqs[lnr+perc_shift] / tscale;
              if (play_stop>0) {
                int too_long=snr*subdiv + dur - play_stop*subdiv;
                if (too_long>0) dur-=too_long;
              }
              note->set_timing(dur);
            }
            if (!(note->midi_instr=pprop[c].perc_nr)) {
              alert("warning: unmapped midi perc instr: %s",pprop[c].name);
            }
            static int p_amp_arr[perc_max]={ 1,30,46,62,78,94,110,127 }; // diff = 16
            note->midi_ampl=p_amp_arr[perc_data[c].midi_ampl];
            nb->busy+=note->dur;
          }
        }
      }
    }
    for (v=0;v<voice_max;++v) nbuf[v].busy-=subdiv;
  }
  if (!lst_nb) {
    alert("empty score");
    goto end;
  }

  NoteBuffer *nb1;
  for (v=0;v<=voice_end;++v) { // all note bufs end with cat=eSleep
    nb1=nbuf+v;
    if (++nb1->cur_note>=nb1->nlen) nb1->renew();
    nb1->notes[nb1->cur_note].cat=eSleep;
  }
  if (debug) {
    puts("-----------");
    for (v=0;v<=nbufs.voice_end;++v) nbuf[v].report(v);
  }
  for (v=0;v<=nbufs.voice_end;++v)
    NOTES[v]=nbufs.nbuf[v].notes;
  if (report_values)
    printf("voices: %d\n",nbufs.voice_end+1);
  result=true;
  end:
  restore_marked_sects(play_start);
  return result;
}

void restore_marked_sects(int play_start) {  // restore ePlay_x notes
  ScSection *sec;
  for (int lnr=0;lnr<sv.sclin_max;lnr++) {
    for (int snr=play_start;snr<=sv.score->lst_sect;++snr) {
      sec=sv.score->get_section(lnr,snr);
      if (sec->buf) {
        for (int c=0;c<inst_max;++c)
          if (sec->buf[c].cat==ePlay_x) sec->buf[c].cat=ePlay;
      }
    }
  }
}

bool read_project(FILE *conf) {
  char buf[31],buf1[31],buf2[31],
       ch,ch2,
       mode=0;
  int res,
      n1,n2;
  buf[30]=buf1[30]=buf2[30]=0; // to make scanf safe
  while (true) {
    get_keyword:
    if (1!=fscanf(conf,"%30s\n",buf)) return true;
    if (report_values) printf("project: [%s]\n",buf);
    if (!strcmp(buf,"Settings")) {
      while (true) {
        if (1!=fscanf(conf," %[^=]=",buf1)) return false;
        ch=getc(conf);
        if (ch=='"') {
          ch=getc(conf);
          if (ch=='"') { // scanf() cannot handle empty string
            buf2[0]=0; ch=getc(conf);
          }
          else {
            ungetc(ch,conf);
            if (2!=fscanf(conf,"%[^\"]\"%c",buf2,&ch)) return false;
          }
          if (!strcmp(buf1,"title")) tune_title=strdup(buf2);
          else if (!strcmp(buf1,"author")) author=strdup(buf2);
          else { alert("unexpected keyword '%s'",buf1); return false; }
        }
        else if (!strcmp(buf1,"combine")) {
          ungetc(ch,conf);
          for (int i=0;i<inst_max;++i) {
            res=fscanf(conf,"%d%c",&n2,&ch);
            if (res!=2) return false;
            if (i<inst_max) i_combine[i]=n2;
            else { alert("more then %d params for 'combine'",inst_max); return false; }
            if (ch==',') continue;
            if (ch=='\n' || ch==' ') break;
          }
          if (ch=='\n') break;
          if (ch!=' ') return false;
        }
        else {
          ungetc(ch,conf);
          if (2!=fscanf(conf,"%d%c",&n2,&ch)) return false;
          if (!strcmp(buf1,"format")) project_format=n2;
          else if (!strcmp(buf1,"meter")) { // meter assigned by meter_sl->cmd()
            if (n2>=7) metersl_val=2;
            else metersl_val=n2;
          }
          else if (!strcmp(buf1,"tempo")) tempo=n2;
          else if (!strcmp(buf1,"nupq")) nupq=n2;
          else if (!strcmp(buf1,"key")) abc_key=mv_key_nr=n2;
          else if (!strcmp(buf1,"skip-fr")) skip_first_rest=n2;
          else if (!strcmp(buf1,"inv-p-rest")) inv_perc_rests=n2;
          else if (!strcmp(buf1,"swing-instr")) swing_instr=n2;
          else if (!strcmp(buf1,"swing-perc")) swing_perc=n2;
          else { alert("unexpected setting: %s",buf1); return false; }
        }
        if (ch=='\n') break;
        if (ch!=' ') return false;
      }
    }
    else if (!strcmp(buf,"Patches")) {
      while (true) {
        if (1!=fscanf(conf," nr=%d ",&n1)) goto get_keyword;
        if (n1<0 || n1>=patch_max) { alert("bad nr %d",n1); return false; }
        if (1!=fscanf(conf,"name=\"%30[^\"]\" ",buf1)) return false;
        fm_data[n1].custom=true;
        if (strcmp(fm_data[n1].name,buf1)) fm_data[n1].name=strdup(buf1);
        while (true) {
          if (1!=fscanf(conf," %[^=]=",buf1)) return false;
          ch=getc(conf);
          if (ch=='"') {
            if (2!=fscanf(conf,"%[^\"]\"%c",buf2,&ch)) return false;
            if (!strcmp(buf1,"gpat-fn")) {
              if (!gpat_data[n1].gpat_fname || strcmp(gpat_data[n1].gpat_fname,buf2)) gpat_data[n1].gpat_fname=strdup(buf2);
            }
            else { alert("keyword '%s'?",buf1); return false; }
            if (ch=='\n') break;
            if (ch!=' ') return false;
          }
          else if ((!strcmp(buf1,"harms") && (mode=1)) || (!strcmp(buf1,"click-harms") && (mode=2))) {
            ungetc(ch,conf);
            for (int i=1;i<num_harms;++i) {
              res=fscanf(conf,"%d%c",&n2,&ch);
              if (res!=2) return false;
              if (i<num_harms) {
                if (mode==1) fm_data[n1].hlin[i]=n2; else fm_data[n1].click_hlin[i]=n2;
              }
              if (ch==',') continue;
              if (ch=='\n' || ch==' ') break;
            }
            if (ch=='\n') break;
            if (ch!=' ') return false;
          }
          else {
            ungetc(ch,conf);
            ch=' ';
            res=fscanf(conf,"%d%c",&n2,&ch);
            if (res!=2) return false;
            if (!strcmp(buf1,"fm_startup")) {
              Int2 &i2=fm_data[n1].fm_startup;
              i2.x=n2; res=fscanf(conf,"%d%c",&i2.y,&ch);
            }
            else if (!strcmp(buf1,"fm_ctrl")) {
              Int2 &i2=fm_data[n1].fm_ctrl;
              i2.x=n2; res=fscanf(conf,"%d%c",&i2.y,&ch);
            }
            else if (!strcmp(buf1,"mod_mod")) {
              Int2 &i2=fm_data[n1].mod_mod;
              i2.x=n2; res=fscanf(conf,"%d%c",&i2.y,&ch);
            }
            else if (!strcmp(buf1,"chorus")) {
              Int2 &i2=fm_data[n1].chorus;
              i2.x=n2; res=fscanf(conf,"%d%c",&i2.y,&ch);
            }
            else if (!strcmp(buf1,"detune")) fm_data[n1].detune=n2;
            else if (!strcmp(buf1,"startup")) fm_data[n1].startup=n2;
            else if (!strcmp(buf1,"attack")) fm_data[n1].attack=n2;
            else if (!strcmp(buf1,"decay")) fm_data[n1].decay=n2;
            else if (!strcmp(buf1,"clip")) fm_data[n1].clip=n2;
            else if (!strcmp(buf1,"start_amp")) fm_data[n1].start_amp=n2;
            else if (!strcmp(buf1,"mphonic")) fm_data[n1].mphonic=n2; 
            else if (!strcmp(buf1,"patch_but")) fm_data[n1].patch_but=n2; // patch mode
            else if (!strcmp(buf1,"gus")) { int& pb=fm_data[n1].patch_but; if (!pb && n2==1) pb=1; } // backwards compatible
            else if (!strcmp(buf1,"harm")) { int& pb=fm_data[n1].patch_but; if (!pb && n2==1) pb=2; }
            else if (!strcmp(buf1,"sawtooth") || !strcmp(buf1,"sin2")) fm_data[n1].sin2=n2; 
            else if (!strcmp(buf1,"local-dir")) gpat_data[n1].loc_samp_dir=n2; 
            else if (!strcmp(buf1,"chorus")) { fm_data[n1].chorus.y=n2; fm_data[n1].chorus.x=1; } // backwards compatible
            else if (!strcmp(buf1,"a-alias")) gpat_data[n1].aalias=n2; 
            else if (!strcmp(buf1,"short")) fm_data[n1].shortdur=n2; 
            else if (!strcmp(buf1,"noisy")) fm_data[n1].noisy=n2; 
            else {
              alert("%s: unexpected patch param %s",cfg_file,buf1);
              return false;
            }
            if (ch=='\n') break;
            if (ch!=' ' && ch!=',') return false;
          }
        }
      }
    }
    else if (!strcmp(buf,"Instruments")) {
      while (true) {
        int scan=fscanf(conf," nr=%d order=%d",&n2,&n1);
        if (scan==2) {
          if (n1<0 || n1>=inst_max) { alert("order parameter should be 0 - %d",inst_max-1); return false; }
          i_ord[n2]=n1;
        }
        else {
          n1=n2;
          if (scan!=1) goto get_keyword;
        }
        if (n1<0 || n1>=inst_max) { alert("bad nr %d",n1); return false; }
        InstrCtrlData *icd=ctr_data+n1;
        icd->custom=true;
        InstProperty *prop=iprop+n1;
        RExtButton *tab=inst_tabs[n1];
        while (true) {
          if (1!=fscanf(conf," %[^=]=",buf1)) return false;
          ch=getc(conf);
          if (ch=='"') {
            if (2!=fscanf(conf,"%[^\"]\"%c",buf2,&ch)) return false;
            if (!strcmp(buf1,"name")) {
              if (strcmp(prop->name,buf2)) prop->name=strdup(buf2);
              if (sdl_running) tab->label=prop->name;
            }
            else if (!strcmp(buf1,"shortname")) {
              if (strcmp(prop->shortname,buf2)) prop->shortname=strdup(buf2);
            }
            else { alert("keyword '%s'?",buf1); return false; }
          }
          else {
            ungetc(ch,conf);
            if (2!=fscanf(conf,"%d%c",&n2,&ch)) return false;
            if (!strcmp(buf1,"ampl")) icd->ampl=n2;
            else if (!strcmp(buf1,"midi-ampl") || !strcmp(buf1,"midi_ampl")) icd->midi_ampl=n2;
            else if (!strcmp(buf1,"pan")) { icd->pan=n2; prop->midi_pan=midi_pan_arr[n2]; }
            else if (!strcmp(buf1,"chords")) icd->add_chords=n2;
            else if (!strcmp(buf1,"slashed")) icd->slashed_notes=n2;
            else if (!strcmp(buf1,"patch_nr")) prop->patch_nr=n2;
            else if (!strcmp(buf1,"transpose")) prop->transpose=n2;
            else if (!strcmp(buf1,"transp2")) prop->transpose2=n2;
            else if (!strcmp(buf1,"range")) {
              prop->lo=n2;
              if (2!=fscanf(conf,"%d%c",&n2,&ch)) return false;
              prop->hi=n2;
            }
            else if (!strcmp(buf1,"clef")) prop->clef=n2;
            else if (!strcmp(buf1,"clef2")) prop->clef2=n2;
            else if (!strcmp(buf1,"gm-prog-nr")) iprop[n1].midi_nr=n2-1;
            else if (!strcmp(buf1,"gmidi-nr")) iprop[n1].midi_nr=n2; // backward comp.
            else { alert("unexpected parameter '%s'",buf1); return false; }
          }
          if (ch=='\n') break;
          if (ch!=' ') return false;
        }
      }
    }
    else if (!strcmp(buf,"Percussion")) {
      while (true) {
        int scan=fscanf(conf," nr=%d order=%d",&n2,&n1);
        if (scan==2) {
          if (n1<0 || n1>=perc_max) { alert("perc order parameter should be 0 - %d",perc_max-1); return false; }
          p_ord[n2]=n1;
        }
        else {
          n1=n2;
          if (scan!=1) goto get_keyword;
        }
        if (n1<0 || n1>=perc_max) { alert("bad nr %d",n1); return false; }
        PercData *pd=perc_data+n1;
        pd->custom=true;
        RExtButton *tab=perc_tabs[n1];
        while (true) {
          if (1!=fscanf(conf," %[^=]=",buf1)) return false;
          ch=getc(conf);
          if (ch=='"') {
            if (2!=fscanf(conf,"%[^\"]\"%c",buf2,&ch)) return false;
            if (!strcmp(buf1,"name")) {
              if (strcmp(pprop[n1].name,buf2)) pprop[n1].name=strdup(buf2);
              if (sdl_running) tab->label=pprop[n1].name;
            }
            else if (!strcmp(buf1,"file")) {
              wave_buffer[n1].fname=strdup(buf2);
            }
            else if (!strcmp(buf1,"abc-sym")) {
              char *p=strchr(buf2,',');
              if (p) { *p=0; ++p; pprop[n1].abc_stem= atoi(p)==eUp || atoi(p)==8 ? eUp : eDown; }
              if (strcmp(pprop[n1].abc_sym,buf2)) pprop[n1].abc_sym=strdup(buf2);
            }
          }
          else {
            ungetc(ch,conf);
            if (2!=fscanf(conf,"%d%c",&n2,&ch)) return false;
            if (!strcmp(buf1,"ampl")) pd->ampl=n2;
            else if (!strcmp(buf1,"midi_ampl")) pd->midi_ampl=n2;
            else if (!strcmp(buf1,"pan")) { pd->pan=n2; pprop[n1].midi_pan=midi_pan_arr[n2]; }
            else if (!strcmp(buf1,"gmidi-nr")) pprop[n1].perc_nr=n2;
            else if (!strcmp(buf1,"local-dir")) wave_buffer[n1].local_dir=n2;
            else { alert("unexpected parameter '%s'",buf1); return false; }
          }
         if (ch=='\n') break;
          if (ch!=' ') return false;
        }
      }
    }
    else if (!strcmp(buf,"Tune")) {
      int lnr,snr,col,dur,stacc,start_del,end_del,s_mode=0;
      // no reset!
      // if (sdl_running) { selected.reset(); unselect->draw_blit_upd(); score->reset(); }
      while (true) {
        start_del=end_del=0;
        if (3!=fscanf(conf," l%ds%d%c",&lnr,&snr,&ch)) {
          alert("note syntax error");
          return false;
        }
        if (ch=='d') {  // instr note?
          if ((project_format==0 && 4!=fscanf(conf,"%dc%ds%d%c",&dur,&col,&stacc,&ch)) ||
              (project_format==1 && 5!=fscanf(conf,"%dc%ds%dm%d%c",&dur,&col,&stacc,&s_mode,&ch))) {
            alert("note syntax error");
            return false;
          }
          if (stacc!=0 && stacc!=1) return false;
          if (ch=='b') {  // begin-delay, end-delay
            if (3!=fscanf(conf,"%de%d%c",&start_del,&end_del,&ch)) {
              alert("note syntax error (chars b,e)");
              return false;
            }
          }
          if (debug) printf("lnr=%d snr=%d col=%d dur=%d stacc=%d s_mode=%d s_del=%d e_del=%d ch=[%c]\n",
                             lnr,snr,col,dur,stacc,s_mode,start_del,end_del,ch);
          col=i_ord[col];  // instr order modified?
          for (int i=0;i<dur;++i) {
            SubSection &subs=sv.score->get_section(lnr,snr+i)->get(col,inst_max);
            subs.cat=ePlay;
            subs.stacc= i==dur-1 ? stacc : false;
            subs.signs_mode=s_mode,
            subs.del_start= i==0 ? start_del : 0;
            subs.del_end= i==dur-1 ? end_del : 0;
          }
          if (sv.score->lst_sect<snr+dur-1)
            sv.score->lst_sect=snr+dur-1;
        }
        else if (ch=='c') {  // percussion note?
          if (2!=fscanf(conf,"%d%c",&col,&ch)) {
            alert("perc note syntax error");
            return false;
          }
          if (ch=='b') {  // begin-delay, end-delay
            if (3!=fscanf(conf,"%de%d%c",&start_del,&end_del,&ch)) {
              alert("perc note syntax error (chars b,e)");
              return false;
            }
          }
          if (debug) printf("lnr=%d snr=%d col=%d start_del=%d end_del=%d ch=[%c]\n",lnr,snr,col,start_del,end_del,ch);
          col=p_ord[col];  // perc order modified?
          SubSection &subs=pv.score->get_section(lnr,snr)->get(col,perc_max);
          subs.cat=ePlay;
          subs.del_start=start_del;
          subs.del_end=end_del;
          if (pv.score->lst_sect<snr)
            pv.score->lst_sect=snr;
        }
        else {
          alert("unexpected char (%c)",ch);
          return false;
        }
        if (ch=='\n') break;
        if (ch!=' ') return false;
      }
    }
    else if (!strcmp(buf,"Annotations")) {
      while (true) {
        int ind;
        if (3!=fscanf(conf," m%da%cc%c",&ind,&ch,&ch2)) {
          alert("annotations syntax error");
          return false;
        }
        Info *inf=iv.get_info(ind);
        if (ch!='-') inf->annot=ch;
        if (ch2=='"') {
          if (1!=fscanf(conf,"%[^\"]\"",buf)) return false;
          inf->chord=strdup(buf);
        }
        if (1!=fscanf(conf,"c2%c",&ch)) return false;
        if (ch=='"') {
          if (2!=fscanf(conf,"%[^\"]\"%c",buf,&ch)) return false;
          inf->chord2=strdup(buf);
        }
        else
          ch=getc(conf);
        if (ch=='\n') break;
        if (ch!=' ') return false;
      }
    }
    else {
      alert("unknown keyword %s in %s",buf,cfg_file);
      return false;
    }
  }
  return true;
}

void set_icon() {
//  SDL_Surface *icon=create_pixmap(icon_xpm);
//  SDL_WM_SetIcon(icon,0);
//  SDL_WM_SetCaption("BigBand","BigBand");
}

int main(int argc,char** argv) {
  const char *project_file=0;
  for (int an=1;an<argc;an++) {
    if (!strcmp(argv[an],"-h")) {
      puts("BigBand - composing music for small ensemble");
      puts("Version: 1.0 (nov 2009)");
      puts("Usage:");
      puts("  bigband [options] [<project-file>]");
      puts("Options:");
      puts("  -h: help, exit");
      puts("  -db: print debug info");
      puts("  -ro: read only (<project-file> can't be overwritten)");
      puts("Manual (if installed): /usr/share/doc/bigband/bigband.html");
      printf("Wave samples and patches expected in ./%s or %s\n",samples_dir(true),samples_dir(false));
      exit(1);
    }
    if (!strcmp(argv[an],"-db")) debug=true;
    else if (!strcmp(argv[an],"-rp")) report_values=true;
    else if (!strcmp(argv[an],"-ro")) read_only=true;
    else if (argv[an][0]=='-') alert("unknown option %s (type 'bigband -h' for help)",argv[an]);
    else cfg_file=project_file=argv[an];
  }
  //alert_position.set(330,100);
  for (int i=0;i<patch_max;++i)
    fm_data[i]=bi_patches[i];
  for (int i=0;i<inst_max;++i)
    i_ord[i]=i_combine[i]=i;
  for (int i=0;i<perc_max;++i)
    p_ord[i]=i;
  top_win=new TopWin("BigBand",Rect(100,100,700,770),SDL_INIT_VIDEO|SDL_INIT_AUDIO,0,draw_topw,set_icon);
                     //SDL_INIT_VIDEO|SDL_INIT_AUDIO,0,draw_topw);
  cMesBg=calc_color(0xf0f0a0);
  cAlert=calc_color(0xffa0a0);
  cLightBlue=calc_color(0xc0ffff);
  cLightGrey=calc_color(0xe7e7e7);
  handle_uev=handle_user_event;
  handle_kev=handle_key_event;
  SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY,SDL_DEFAULT_REPEAT_INTERVAL);

  if (project_file) {
    FILE *conf=fopen(project_file,"r");
    if (conf) {
      if (!read_project(conf)) alert("error in %s",project_file);
      fclose(conf);
    }
    else alert("project file %s not opened",project_file);
  }

  Point top(6,4);
  file_menu=new CmdMenu(new Button(top_win,Style(2,1,18),Rect(top.x,top.y,40,18),"File",button_cmd,'menu'));
  set_menu=new CmdMenu(new Button(top_win,Style(2,1,18),Rect(top.x+50,top.y,55,18),"Settings",button_cmd,'setm'));
  new Button(top_win,Style(3,1),Rect(top.x+120,top.y,40,18),"Help",button_cmd,'help');

  top.set(10,40);
  tempo_sl=new HSlider(top_win,1,Rect(top.x-4,top.y,100,0),5,15,"tempo",hslider_cmd,'tmpo');
  tempo_sl->d=&tempo;
  tempo_sl->cmd(tempo_sl,0,false);
  meter_sl=new HSlider(top_win,1,Rect(top.x+100,top.y,65,0),0,6,"meter",hslider_cmd,'metr');
  meter_sl->d=&metersl_val;
  meter_sl->cmd(meter_sl,0,false);
  nupq_sl=new HSlider(top_win,1,Rect(top.x+170,top.y,35,0),2,4,"nupq",hslider_cmd,'nupq');
  nupq_sl->d=&nupq;
  nupq_sl->cmd(nupq_sl,0,false);
  key_sl=new HSlider(top_win,1,Rect(top.x+210,top.y,100,0),0,keys_max-1,"key",hslider_cmd,'shif');
  key_sl->d=&mv_key_nr;
  scales_win=new BgrWin(top_win,Rect(128,top.y+24,scw_w,16),0,draw_scw,0,0,0,cBackground);

  top.y+=74;
  instrument=new Instrument(Rect(top.x,top.y,300,160));
  patch_nam=new Message(top_win,0,"name:",Point(top.x+60,top.y-31));
  patch_tab_ctr=new ExtRButCtrl(Style(0,cForeground),extb_cmd);
  patch_tab_ctr->maybe_z=false;
  patch_tabs[0]=patch_tab_ctr->add_extrbut(top_win,Rect(top.x,top.y-15,70,16),"FM synth",Id('patc',0));
  patch_tabs[1]=patch_tab_ctr->add_extrbut(top_win,Rect(top.x+80,top.y-15,70,16),"GUS patch",Id('patc',1));
  patch_tabs[2]=patch_tab_ctr->add_extrbut(top_win,Rect(top.x+160,top.y-15,70,16),"Harmonics",Id('patc',2));
  patch_tab_ctr->set_rbut(patch_tabs[fm_data[0].patch_but],0);

  top.y+=180;
  inst_ctr=new InstrCtrl(Rect(top.x,top.y,134,192));
  inst_ctr->inst_slider_data(0);
  instrument->patch_slider_data(inst_ctr->patch_nr->value()); // set all sliders of Instrument* instrument

  midi_inst=new Message(top_win,0,"midi:",Point(top.x+80,top.y-15));
  top.x+=133;
  inst_tab_ctr=new ExtRButCtrl(Style(3,cForeground),extb_cmd);
  inst_tab_ctr->maybe_z=false;
  for (int i=0;i<inst_max;++i) {
    const char *name=ctr_data[i].custom ? iprop[i].name : 0;
    inst_tabs[i]=inst_tab_ctr->add_extrbut(top_win,Rect(top.x,top.y+i*16,80,16),name,Id('i_nr',i));
  }
  inst_tab_ctr->act_lbut=inst_tabs[0];
  top.x+=82;
  for (int i=0;i<inst_max;++i) {
    patch_info[i]=new Message(top_win,2,0,Point(top.x,top.y+i*16));
    inst_checkb[i]=new CheckBox(top_win,0,Rect(top.x+66,top.y+i*16,26,0),i_checkb_label,checkb_cmd,Id('inst',i));
    inst_checkb[i]->value()=true;
  }
  inst_ctr->patch_nr->cmd(inst_ctr->patch_nr,1,false);
  top.x+=50;
  top.y+=194;
  inst_all_1=new Button(top_win,0,Rect(top.x-30,top.y,30,0),"all 1",button_cmd,'i_a1');
  inst_all_0=new Button(top_win,0,Rect(top.x+2,top.y,30,0),"all 0",button_cmd,'i_a0');

  top.x=10; top.y+=26;
  perc_ctr=new PercCtrl(Rect(top.x,top.y,134,148));
  perc_ctr->perc_slider_data(0);

  perc_tab_ctr=new ExtRButCtrl(Style(3,cForeground),extb_cmd);
  perc_tab_ctr->maybe_z=false;
  top.x+=133;
  for (int i=0;i<perc_max;++i) {
    const char *name=perc_data[i].custom ? pprop[i].name : 0;
    perc_tabs[i]=perc_tab_ctr->add_extrbut(top_win,Rect(top.x,top.y+i*16,80,16),name,Id('p_nr',i));
  }
  perc_tab_ctr->act_lbut=perc_tabs[0];
  top.x+=82;
  for (int i=0;i<perc_max;++i) {
    perc_info[i]=new Message(top_win,2,0,Point(top.x,top.y+i*16));
    perc_checkb[i]=new CheckBox(top_win,0,Rect(top.x+26,top.y+i*16,30,0),p_checkb_label,checkb_cmd,Id('perc',i));
    perc_checkb[i]->value()=true;
  }
  perc_all_1=new Button(top_win,0,Rect(top.x-10,top.y+140,30,0),"all 1",button_cmd,'p_a1');
  perc_all_0=new Button(top_win,0,Rect(top.x+22,top.y+140,30,0),"all 0",button_cmd,'p_a0');

  top.x=10; top.y+=172;
  triad_mode=new RButWin(top_win,0,Rect(top.x,top.y,50,2*TDIST),"triad mode",false,0);
  triad_mode->add_rbut("3 in 2");
  triad_mode->add_rbut("3 in 4");
  triad_mode->set_rbutnr(1,0,false);

  new Button(top_win,1,Rect(top.x+80,top.y-4,0,0),"show names",button_cmd,'snam');
  set_repeat=new CheckBox(top_win,0,Rect(top.x+80,top.y+16,0,0),"repeat",0);

  new Button(top_win,0,Rect(top.x,top.y+48,20,0),"ok",button_cmd,'ok');
  dialog=new DialogWin(top_win,Rect(top.x+22,top.y+34,140,0));

  scope_win=new BgrWin(top_win,Rect(top.x+178,top.y+10,scope_dim,scope_h*2),"scope",bgw_clear,0,0,0,cMesBg);
  scope_win->reloc_title(0,-3);

  play_but=new Button(top_win,0,Rect(top.x+190+scope_dim,top.y+50,24,20),right_arrow,button_cmd,'play');

  top.set(334,18);
  txtmes2=new Message(top_win,Style(1,cWhite),"current measure:",Point(top.x,top.y));
  title_mes=new Message(top_win,0,"title:",Point(top.x+130,top.y));
  top.y+=36;
  iv.infoview=new BgrWin(top_win,Rect(top.x,top.y,scv_w,iv_h),"annotations, chords",draw_infoview,mouse_down,0,0,cWhite,'infv');
  top.y+=iv_h+22;
  sv.scoreview=new BgrWin(top_win,Rect(top.x,top.y,scv_w,scv_h),"score: instruments",draw_scoreview,mouse_down,mouse_moved,mouse_up,cWhite,'scv');
  signs_view=new BgrWin(top_win,Rect(top.x-signs_wid-1,top.y+top_yoff,signs_wid,scv_h-top_yoff),0,draw_signs_view,0,0,0,cBackground);
  signs_view->hidden=true;
  draw_staff=new CheckBox(top_win,0,Rect(top.x+220,top.y-18,0,0),"staff display",checkb_cmd,'drst');
  top.y+=scv_h+22;
  pv.scoreview=new BgrWin(top_win,Rect(top.x,top.y,scv_w,pv_h),"score: percussion",draw_percview,mouse_down,mouse_moved,mouse_up,cWhite,'pv');
  alt_draw_perc=new CheckBox(top_win,0,Rect(top.x+220,top.y-18,0,0),"alternative display",checkb_cmd,'altp');
  top.y+=pv_h+4;
  sv_scroll=new HScrollbar(top_win,Style(0,sect_len),Rect(top.x,top.y,scv_w,0),scv_w+30,sv_scroll_cmd);
  sv_scroll->set_range((max(sv.score->len,pv.score->len)+20)*sect_len);

  top.y+=20;
  set_swing=new CheckBox(top_win,0,Rect(top.x+50,top.y,0,0),"swing feel (instruments)",0);
  set_swing->d=&swing_instr;
  set_swing_perc=new CheckBox(top_win,0,Rect(top.x+50,top.y+16,0,0),"swing feel (percussion)",0);
  set_swing_perc->d=&swing_perc;
  (new CheckBox(top_win,0,Rect(top.x+50,top.y+40,0,0),"info to terminal",0))->d=&info_to_term;

  top.y+=64;
  mod_notes=new Lamp(top_win,Rect(top.x+50,top.y,0,0));
  mod_settings=new Lamp(top_win,Rect(top.x+50,top.y+16,0,0));
  if (read_only) mod_notes->hidden=mod_settings->hidden=true;

  top.y-=64;
  unselect=new Button(top_win,0,Rect(top.x+284,top.y,64,0),"unselect",button_cmd,'uns');
  new Button(top_win,0,Rect(top.x+284,top.y+18,64,0),"delete sel",button_cmd,'dels');
  new Button(top_win,0,Rect(top.x+284,top.y+36,64,0),"recolor sel",button_cmd,'rcol');
  new Button(top_win,0,Rect(top.x+284,top.y+54,64,0),"add to sel",button_cmd,'adto');
  ch_hint=new Button(top_win,0,Rect(top.x+284,top.y+74,64,0),"chord hint",button_cmd,'hint');

  get_events();
  return 0;
} 
