/* 
   * Author: EUGEN27771
   * Author URI: http://forum.cockos.com/member.php?u=50462
   * Co-author: IGOR
   * Co-author URI: http://forum.cockos.com/member.php?u=106943
   * Licence: GPL v3
   * Version: 1.01
*/

desc:WaveScopeStereo v2
options:no_meter
//options:want_all_kb
//import inc\Mouse3Full.jsfx-inc
//import inc\ColorMenu2.jsfx-inc

slider1:1<0.01,12,0.00001>-Range(in sec)
slider2:0<0,6,1>-exp-mlt factor for Range(for MidiPitch, Tempo mode)
slider3:1<1,1000000,0.01>-Vertical Zoom
slider4:3<0,3,1{ TimeLoop, MidiPitch, Tempo, Disable} >-Sync Mode:
slider5:0<0,1,1{ 1 axis, 2 axis}>-View Mode:
slider6:0<0,2,1{ L / R, Left, Right}>-Chan Mode:
//-- Colors ------------------
slider7:0<0,1,1{ Waveform 1, Waveform 2}>-Colors: 

//-- Test Slider -------------
slider10:0<-20000,20000,2>-Test Slider

in_pin:IN1
in_pin:IN2


@init

ext_nodenorm = 1; // no denorm noise
gfx_clear = 0x05100A; // main bg color 0xBBGGRR form

//------------------------------------------------
/*Range не должен быть слишком большим - оптимально около 4 секунд.
Чем больше - тем больше нагрузка на процессор в этой версии, если это важно.
В следующих не будет иметь значения вообще(мы над этим работаем:)), но времени мало.
Но запас в слайдере оставлен специльно(нужен лично мне).*/
// incr. max. range - more CPU usage! This will be fixed in future versions)
min_range_sec = 0.001;
max_range_sec = 12;

//-- Отступы(for test only),потом убрать ---------
_Ls = 18;
_Rs = 38;
_Up = 18;
_Dn = 18;

//**************************************************************************************************
//**************************************************************************************************
//**************************************************************************************************


/***************************************************************************************************
*** GetMouseState() and SetMouseLastState() functions **********************************************
***************************************************************************************************/
/* Global variables: 
   1) mouse_down, mouse_rdown, mouse_mdown, mouse_up, mouse_rup, mouse_mup, mouse_move, Ctrl, Shift, Alt.
   2) mouse_last_cap, mouse_last_x, mouse_last_y.   
   Can be used in code. */

//-- Get current mouse state -----------------------------------------
function GetMouseState()
(
  //-- Mouse btn has been pressed(anywhere) ------
  mouse_down  = (mouse_cap&1)  && !(mouse_last_cap&1);  // L mouse
  mouse_rdown = (mouse_cap&2)  && !(mouse_last_cap&2);  // R mouse
  mouse_mdown = (mouse_cap&64) && !(mouse_last_cap&64); // M mouse
  //-- Mouse btn has been released(anywhere) -----
  mouse_up  = (mouse_last_cap&1)  && !(mouse_cap&1);    // L mouse
  mouse_rup = (mouse_last_cap&2)  && !(mouse_cap&2);    // R mouse
  mouse_mup = (mouse_last_cap&64) && !(mouse_cap&64);   // M mouse
  //-- Mouse moved(anywhere) ---------------------
  mouse_move = (mouse_last_x != mouse_x) || (mouse_last_y != mouse_y);
  //-- Mouse dbl(used for mouseDblClick) ---------
  mouse_down ? (
    mouse_dbl = (mouse_down_x==mouse_x) && (mouse_down_y==mouse_y) && (mouse_captimer<12);
    mouse_captimer = 0;
  );
  
  //-- mouse press coordinates -------------------
  mouse_down  ? (mouse_down_x  = mouse_x; mouse_down_y  = mouse_y; );
  mouse_rdown ? (mouse_rdown_x = mouse_x; mouse_rdown_y = mouse_y; );
  mouse_mdown ? (mouse_mdown_x = mouse_x; mouse_mdown_y = mouse_y; );
  
  //-- modkeys state -----------------------------
  Ctrl  = mouse_cap&4;  // Ctrl
  Shift = mouse_cap&8;  // Shift
  Alt   = mouse_cap&16; // Alt
);


//-- Set(update) last state -------------------------------------------
function SetMouseLastState()
( 
  mouse_last_cap = mouse_cap; // upd last_cap
  mouse_last_x = mouse_x;     // upd last_x
  mouse_last_y = mouse_y;     // upd last_y
  mouse_wheel  = 0;           // reset mouse_wheel
  mouse_hwheel = 0;           // reset mouse_hwheel
  //--------------
  mouse_captimer < 12 ? mouse_captimer+=1; // upd "timer"(frame cnt)
  mouse_up ? mouse_dbl = 0;   // reset dbl when released 
);

/***************************************************************************************************
*** Get mouse state(with ref to the object) functions **********************************************
***************************************************************************************************/
/* pointINrect(), mouseINrect() must be called with arguments.
   All other functions must be called with "object" prefix.
   Functions use the object coordinates - MyObj.x, MyObj.y, MyObj.w, MyObj.h. 
   Example: MyButton.mouseClick() ? SomethingCode; */

//-- if point(p_x, p_y) in rect(x,y,w,h) area ----
function pointINrect(p_x,p_y, x,y,w,h) ( p_x>=x && p_x<=x+w && p_y>=y && p_y<=y+h; );
//-- if mouse cursor in rect(x,y,w,h) area -------
function mouseINrect(x,y,w,h) ( pointINrect(mouse_x, mouse_y, x,y,w,h); );
//-- if point(p_x, p_y) in object area -----------
function pointIN(p_x,p_y) instance(x,y,w,h) ( this.pointINrect(p_x,p_y, x,y,w,h) );
//-- if mouse cursor in object area --------------
function mouseIN() ( this.pointIN(mouse_x, mouse_y); );


//-- Left Mouse Button ---------------------------
function mouseDown()    ( mouse_down     && this.mouseIN(); );
function mouseUp()      ( mouse_up       && this.mouseIN(); );
function mouseClick()   ( this.mouseUp() && this.pointIN(mouse_down_x, mouse_down_y); );
function mouseDblClick() ( mouse_dbl && this.mouseClick(); );
//-- Rigth Mouse Button --------------------------
function mouseRDown()  ( mouse_rdown     && this.mouseIN(); );
function mouseRUp()    ( mouse_rup       && this.mouseIN(); );
function mouseRClick() ( this.mouseRUp() && this.pointIN(mouse_rdown_x, mouse_rdown_y); );
//-- Middle Mouse Button -------------------------
function mouseMDown()  ( mouse_mdown     && this.mouseIN(); );
function mouseMUp()    ( mouse_mup       && this.mouseIN(); );
function mouseMClick() ( this.mouseMUp() && this.pointIN(mouse_mdown_x, mouse_mdown_y); );



//**************************************************************************************************
//-- Draw ColMenu Field spinBox --------------------------------------------------------------------
//**************************************************************************************************
//------------------------------------------------
function ColMenu.New(x,y,w,h, r,g,b,a)
(
  this.x = x; this.y = y; this.w = w; this.h = h; // coords
  this.r = r; this.g = g; this.b = b; this.a = a; // color  
);

//------------------------------------------------
function ColMenu_Field(x,y,w,h, lbl, val)
  instance(cap_val, cap_y)
  local(cx,cy, str_y)
(
    val = floor(val*255 + 0.5); // value 0...255 form
    
    //--- Get mouse, set val --
    mouse_up ? this.isCaptured = 0;
    
    mouse_down && mouseINrect(x,y,w,h) ? (
        mouse_down_x > x+w-h+2 ? (
          mouse_down_y < y+h/2 ? val+=1 : val-=1;
          val = min( max(val, 0), 255);
        ) : (
          this.isCaptured = 1; 
          this.cap_val = val; 
          this.cap_y = mouse_y;
        );
    );
  
    this.isCaptured ? (
      val = cap_val + (cap_y-mouse_y);
      val = min( max(val, 0), 255);
    );
  
  
  gfx_a = 0.2; // frame clr
  gfx_rect(x,y,w,h,0); // frame
  gfx_a = 1;   // val, lbl clr
  //-- Draw(стрелки) ---------
  cx = x+w-8; cy = y+h/2;   // center(стрелки)
  gfx_x = cx-2; gfx_y = cy-2;
  gfx_lineto(cx, cy-4); gfx_lineto(cx+2, cy-2); //up
  gfx_x = cx-2; gfx_y = cy+2;
  gfx_lineto(cx, cy+4); gfx_lineto(cx+2, cy+2); //down
   
  //-- label, value ----------
  str_y = y + (h-gfx_texth)/2;
  gfx_x = x - 24;
  gfx_y = str_y;
  gfx_drawstr(lbl); // label
  gfx_x = x + 4; 
  gfx_y = str_y;
  gfx_drawnumber(val,0); // value(0...255 form) 

  val/255; // return value [0...1]
);
//------------------------------------------------
function ColMenu.Draw(obj*)
  instance(x,y,w,h, r,g,b,a, RR,GG,BB,AA)
  local(xx, yy, ww, hh)
( 
  gfx_set(0,0,0,1);
  gfx_rect(x,y,w,h,1);
  
  gfx_set(r,g,b,0.2);
  gfx_rect(x,y,w,h,0);
  
  // Координаты сделать изменяемыми, пока постоянные
  xx = x + 32; // fields x-offset
  yy = y + 10; // fields y-offset
  ww = 40; hh = 18; // fields w, h
  
  //-- Draw menu, change obj color -----
  gfx_setfont(1,"Calibri", 15); // fields font
  obj.r = RR.ColMenu_Field(xx, yy,    ww, hh, "R", obj.r);
  obj.g = GG.ColMenu_Field(xx, yy+20, ww, hh, "G", obj.g);
  obj.b = BB.ColMenu_Field(xx, yy+40, ww, hh, "B", obj.b);
  obj.a = AA.ColMenu_Field(xx, yy+60, ww, hh, "A", obj.a);
  
  //------------------------------------
  gfx_set(obj.r, obj.g, obj.b, obj.a);
  gfx_rect(x+90, y+10, w-100, h-22, 1);
  //------------------------------------
  
);


//**************************************************************************************************
//**************************************************************************************************
//**************************************************************************************************
// == Color functions =================================== //
/* Color(RGB) format 0xRRGGBB(as HEX), alfa(A) [0...255] */
//-- Set gfx r,g,b,a -------------------
function SetRGBA(RGB, A)
(
  gfx_r = ((RGB >> 16) & 0xFF) / 255;
  gfx_g = ((RGB >> 8)  & 0xFF) / 255;
  gfx_b = (RGB & 0xFF) / 255;
  gfx_a = A/255;    
);

//-- Set gfx r,g,b and a = 1 -----------
function SetRGBA(RGB)
(
  SetRGBA(RGB, 255);    
);

//-- set obj r,g,b,a -------------------
function SetColorFromRGBA(RGB, A) 
( 
  this.r = ((RGB >> 16) & 0xFF) / 255;
  this.g = ((RGB >> 8)  & 0xFF) / 255;
  this.b = (RGB & 0xFF) / 255;
  this.a = A/255;  
);

//-- set obj r,g,b and a = 1 -----------
function SetColorFromRGBA(RGB) 
( 
  SetColorFromRGBA(RGB, 255);  
);

// == DB2VAL - VAL2DB =================================== //
function DB2VAL(x) 
(  
  10^(x/20); // VAL 
);
//----------------------------
function VAL2DB(x) 
( 
  20*log10(x); // DB  
);

// == Some other ======================================== //
function round(x) global()
(
  x < 0 ? ceil(x - 0.5) : floor(x + 0.5);
);
//----------------------------
function minmax(x, min, max)
(
  min(max(x, min), max); 
);

/***************************************************************************************************
*** Waveform functions *****************************************************************************
***************************************************************************************************/
// == Set waveforms Colors ============================== //
function wave.SetColors()
  local(Asep, Amix)
(
  Asep = 130; // alfa for view_mode: 2 axis(and 1 axis + mono modes)
  Amix = 97;  // alfa for view_mode: 1 axis + "L/R"
  //-- set colors ------------
  //[W1=RGB=152/150/97 A=130] // Chan 1 
  this.ch1.r = 152 / 255;
  this.ch1.g = 150 / 255;
  this.ch1.b = 97  / 255;
  this.ch1.a = 130 / 255;
  //[W2=RGB=116/168/97 A=130] // Chan 2
  this.ch2.r = 116 / 255;
  this.ch2.g = 168 / 255;
  this.ch2.b = 97  / 255;
  this.ch2.a = 130 / 255;
  
  //-- set alfa multiplier ---
  this.ch12.a_mlt = Amix/Asep; // alfa mlt for 1 axis view_mode   
);

// == Clear sample buffer, reset spos =================== //
function wave.ResetBuffer() 
(
  memset(this.sbuf, 0, round(max_range_sec * srate) * 2); // clear max_sz
  //memset(this.sbuf, 0, this.sbuf_sz); // clear current sbuf_sz variant
  this.spos = 0;   // reset spos
  this.pause = 0;  // reset pause
  this.offset = 0; // reset offset
);

// == Init Wave ========================================= //
function wave.Init(x,y,w,h, sbuf, pbuf) 
(
  this.sbuf = sbuf; // samplebuffer mem offset, must NOT cross other used mem slots!
  this.pbuf = pbuf; // peakbuffer mem offset, must NOT cross other used mem slots!
  //------------------------------------
  wave.ResetBuffer(); // reset buffer
  //------------------------------------
  this.x = x; this.y = y; this.w = w; this.h = h; // set def coords
  wave.SetColors(); // set def colors
  //------------------------------------
  this.view_mode = 1; // view mode 0 = on one axis, 1 = on sep axis
  this.Vzoom = 1;     // vertical zoom
  this.div = 200;     // divfactor(samples per peak)
);

// == Input samples to sample buffer(sbuf) ============== //
function wave.SamplesToBuffer(input1, input2)
  instance(sbuf, sbuf_sz, spos)   
(
  !this.pause ? (
    spos >= sbuf_sz ? spos = 0;
    sbuf[spos]   = input1;
    sbuf[spos+1] = input2;
    //----------------------------------
    spos+=2; // upd spl pos counter
  );
);

//**************************************************************************************************
/*1) По диапазону из семлбуфера - нужно будет переделать. Если все по задумке, получится очень круто. 
Во-первых - нет смысла пересоздавать полностью все пики, нужно только участок! - 
если не изменяются режимы отображения волны и размеры окна - это сходу снизит расход ресурсов на любом,
даже очень длинном диапазоне в разы, в десятки раз, сложновато сделать четко, но оно стоит того. */

// == Create Peaks from samplebuffer ==================== //
function wave.CreatePeaks()
  instance(w, sbuf, pbuf, div, offset)
  local(s, s_offs, p, peak_cnt, next_peak, 
        cur_spl1, min_peak1, max_peak1,
        cur_spl2, min_peak2, max_peak2 ) 
( 
  s = s_offs = - round(offset) * 2; // spl cnt, regard spl offset
  peak_cnt = 0; // peak cnt
  p = 0;        // pos in pbuf 
  
  loop(w,
      // -- reset min-max peaks(v2-draw) ---------
      min_peak1 = min_peak2 = 777; max_peak1 = max_peak2 = -777; // 777 
      
      // -- Create peaks from buffer samples -----
      next_peak = s_offs + floor((peak_cnt+1) * div * 2 ); // next peak spos 
      while(s <= next_peak) (
        cur_spl1 = sbuf[s];    // ch1 spl
        cur_spl2 = sbuf[s+1];  // ch2 spl
        min_peak1 = min(min_peak1, cur_spl1);   // min peak1
        max_peak1 = max(max_peak1, cur_spl1);   // max peak1
        min_peak2 = min(min_peak2, cur_spl2);   // min peak2
        max_peak2 = max(max_peak2, cur_spl2);   // max peak2      
        s+=2; // upd cnt
      );
      
      // -- peaks to peak buffer(mem_set_values - v5.28 or later)
      mem_set_values(p, min_peak1, max_peak1, min_peak2, max_peak2); 
      
    peak_cnt+=1; // upd peak cnt
    p+=4; // upd pos in pbuf 
  
  );

);

// == Draw Peaks from peakbuffer ======================== //
function wave.draw_Peaks(chan, axis, hlf_h, r,g,b,a)
  instance(x,y,w,h, pbuf, Vzoom)
  local(min_peak, max_peak, last_min_peak, last_max_peak, 
        peak_x, Vrange, chan, p, aa)
(
  //-- Get samples from sbuf, create peaks, draw waves -----
  Vrange = hlf_h * Vzoom;  // Vrange - coeff
  peak_x = x; // first peak x-position
  //------------------------------------ 
  chan == 1 ? p = 0 : p = 2;  // start pos in pbuf(dependent of channel)
  // last peaks from from peak buffer(нужно, чтобы связать крайние точки).
  min_peak = pbuf[(w-1)*4 + p]   * Vrange; // last min peak 
  max_peak = pbuf[(w-1)*4 + p+1] * Vrange; // last max peak
  last_min_peak = axis - minmax(round(min_peak), -hlf_h, hlf_h);
  last_max_peak = axis - minmax(round(max_peak), -hlf_h, hlf_h); 
  
  //-- Draw waveform -------------------
  gfx_set(r,g,b); // peak color
  aa = min(a + 0.2, 1); // contour color alfa
 
  loop(w,      
        pbuf[p] < 777 ? ( // if division factor < 1, see CreatePeaks
          min_peak = pbuf[p] * Vrange;
          max_peak = pbuf[p+1] * Vrange;
          min_peak = axis - minmax(round(min_peak), -hlf_h, hlf_h);
          max_peak = axis - minmax(round(max_peak), -hlf_h, hlf_h);
          gfx_a = a; // peak vertical line color alfa
          gfx_line(peak_x, min_peak, peak_x, max_peak); // peak
        );
        gfx_a = aa; // contour color alfa
        gfx_line(peak_x-1, last_min_peak, peak_x, min_peak, 1); // contour1
        gfx_line(peak_x-1, last_max_peak, peak_x, max_peak, 1); // contour2
        // -- upd last peaks ---
        last_min_peak = min_peak;
        last_max_peak = max_peak;
      // -----------------------
      peak_x+=1; // next peak x-position
      p+=4; // next position in pbuf 
  );

);

// == Draw Frame ======================================== //
function wave.draw_Frame(r,g,b,a)
  instance(x,y,w,h)
( 
  gfx_set(r,g,b,a);
  gfx_rect(x-1, y, w+2, h+1, 0);
);


// == Draw db_scale ===================================== //
function wave.draw_DB_Scale(axis, hlf_h) // R side scale
  instance(x,y,w,h, Vzoom)
  local(r,g,b,a, Vrange, texth05, db_max,db_min,db, scale_x, l1db,l3db, xn,
        v,last_v, y1,y2, abs_db, last_db, xl)
( 
  // 115; 150; 80; a = 1; // db_scale values color
  // 110; 144; 76; a = 1; // db_scale lines color
  Vrange = hlf_h * Vzoom;
  texth05 = gfx_texth*0.5;
  db_max = floor(VAL2DB(1/Vzoom)); // верхнее значение db
  db_min = ceil(VAL2DB(gfx_texth/Vrange)); // V2 нижнее значение db
  db_min = db_min + (db_min%3); // окр. до трех(для отриц. чисел!)
  
  scale_x = x + w; // scale x-position(wave R side x-coord)
  l1db = 4;   // short(1db) line length
  l3db = 8;   // long(3db) line length
  xn = scale_x + l3db + 4;  // scale db_values x-coord
  
  // -- Draw dB values ---------------------------
  r = 0.451; g = 0.588; b = 0.314; a = 1; // db_scale values color
  gfx_set(r,g,b,a);
  //------------------------------------
  gfx_x = xn+1; gfx_y = axis - texth05;
  gfx_drawchar(0x221E); // v1 "-oo(0x221E)"
  //gfx_drawstr("inf"); // v2 "-inf"
  //------------------------------------
  db = db_max + (db_max%3);   // окр. до трех(для отриц. чисел!)
  last_v = hlf_h + gfx_texth;
  while(db >= db_min) (
    v = round(DB2VAL(db) * Vrange);
    y1 = axis - v; // Above the axis
    y2 = axis + v; // Below the axis
    
    last_v-v >= gfx_texth ? (
      abs_db = abs(db); // abs, now we do not draw "-" for this scale
      //----------------------
      gfx_a = 1;
      gfx_x = xn; gfx_y = y1 - texth05; // db_val x, y
      gfx_drawnumber(abs_db, 0); 
      gfx_x = xn; gfx_y = y2 - texth05; // db_val x, y
      gfx_drawnumber(abs_db, 0);
      gfx_a = 0.15; 
      gfx_line(x, y1, scale_x, y1);
      gfx_line(x, y2, scale_x, y2);
      last_v = v;
      last_db = db; // The last drawn value 
    );

    db-=3;
  ); 
  
  // -- Draw dash-lines on the scale -------------
  r = 0.431; g = 0.565; b = 0.298; a = 1; // db_scale lines color
  gfx_set(r,g,b,a);
  //------------------------------------
  gfx_line(scale_x, axis, scale_x + l3db, axis); // center line("-oo")
  //------------------------------------
  db = last_db;
  while(db <= db_max) (
    v = round(DB2VAL(db) * Vrange);
    y1 = axis - v; // Above the axis
    y2 = axis + v; // Below the axis
    
    db%3 ? (xl = scale_x + l1db) : (xl = scale_x + l3db);
    gfx_line(scale_x, y1, xl, y1); 
    gfx_line(scale_x, y2, xl, y2);
   
    db+=1; 
  );

);

// == Draw label ======================================== //
function wave.draw_Label(x,y,w,h, r,g,b,a, lbl)
  local(offs)
(
  gfx_set(0,0,0,0.4); // str shadow col
  offs = (h - gfx_texth) * 0.5; 
  gfx_x = x + 9; gfx_y = y + offs + 1;
  gfx_drawstr(lbl);
  
  gfx_set(r,g,b,a);   // lbl txt col
  gfx_x = x + 8; gfx_y = y + offs;
  gfx_drawstr(lbl);
  
  //gfx_a = 0.2; gfx_rect(x,y,w,h, 0);
);

// == Draw MousePos_db ================================== //
function wave.draw_MousePos_db(axis, axis1, axis2, hlf_h, r,g,b,a)
  instance(x,y,w,h, Vzoom, view_mode)
  local(Vrange, mouse_pos_db, cur_axis, str, str_w, str_h,
        lbl_x, lbl_y, lbl_w, lbl_h)
(
  Vrange = hlf_h * Vzoom;  // Vrange - coeff
  
  view_mode ? (  // cur_axis if view_mode = 1;
    mouse_y < axis ? cur_axis = axis1 : cur_axis = axis2;
  ) : (          // if view_mode = 0;
    cur_axis = axis;
  );
  
  // -- Mouse pos val ----------------------------
  this.mouseIN() ? (
    mouse_pos_db = VAL2DB( abs( (cur_axis - mouse_y)/Vrange) );
    mouse_pos_db = strcat( sprintf(#, "%.2f", mouse_pos_db), " dB"); // "%.2f" var
    gfx_measurestr(mouse_pos_db, str_w, str_h); // val w, val h
    lbl_x = x+w-str_w-24; 
    lbl_y = y+4+1;
    lbl_w = str_w+16; 
    lbl_h = 20;
    wave.draw_Label(lbl_x, lbl_y, lbl_w, lbl_h, r,g,b,a, mouse_pos_db); 
  );
);

// == Draw Info ========================================= //
/*В информацию желательно добавить возможность отображения range в секундах, герцах.
Можно добавить отобр. текущей ноты в MIDI Pitch mode. 
Можно добавить отображение range - множителя(для соотв. режимов).*/
function wave.draw_Info(r,g,b,a)
  instance(x,y,w,h, offset, div)
  local(str, str_w, str_h, lbl_x, lbl_y, lbl_w, lbl_h)
( 
  str = #; // temporary string
  lbl_y = y+4+1; // info labels y  
  lbl_h = 20;    // info labels h
  // -- Range --------------------------
  strcpy(str, "Range: ");
  strcat(str, sprintf(#, "%d", range_spls)); 
  strcat(str, " samples");
  gfx_measurestr(str, str_w, str_h);
  lbl_x = x + 8; 
  lbl_w = str_w + 16; 
  wave.draw_Label(lbl_x, lbl_y, lbl_w, lbl_h, r,g,b,a, str);
  
  // -- Pause --------------------------
  this.pause ? (
    strcpy(str, "Pause");
      // -- Offset ---------------------
      this.offset ? (
        //strcat(str, " Offset: ");
        strcat(str, ":  ");
        strcat(str, sprintf(#, "%d", offset)); //offs spls
        strcat(str, " samples");
      );
      gfx_measurestr(str, str_w, str_h);
      lbl_x += (lbl_w + 16); 
      lbl_w = str_w + 16;
      wave.draw_Label(lbl_x, lbl_y, lbl_w, lbl_h, r,g,b,a, str);
  );

);
// == Draw Cursor ======================================= //
function wave.draw_Cursor(r,g,b,a) //TEST
  instance(x,y,w,h, spos, div)
  local(last_curs_pos, curs_pos)
( 
  /*Разница между посл. поз курсора и текущей - и есть тот кусочек, 
  который нужно обновлять, то есть не пересчитывать пики полностью, 
  а только этот участок! Это + 1000% производительности.
  Главное очень четко все посчитать.*/ 
  !this.pause ? (
    gfx_set(r,g,b,a);
    //gfx_line(x + last_curs_pos, y, x + last_curs_pos, y + h);
    last_curs_pos = curs_pos = spos/div*0.5;
    gfx_line(x + curs_pos, y, x + curs_pos, y + h);
  );
);

// == Draw Wave ========================================= //
function wave.Draw()
  instance(x,y,w,h, ch1, ch2, ch12, Vzoom, div, view_mode, chan_mode, sync_mode)
  local(hlf_h, axis, axis1, axis2, chan_str, mouse_pos_db, str_w, str_h, a1, a2 )
( 
  view_mode = ViewMode.val; // wave view_mode
  chan_mode = ChanMode.val; // wave chan_mode
  sync_mode = SyncMode.val; // wave sync_mode 
  
  // -- Wave BG color --------
  gfx_set(0,0,0,1); // wave bg color
  gfx_rect(x,y,w,h,1);
  
  // -- center axis ----------
  hlf_h = h * 0.5;
  axis  = y + hlf_h; // Center axis
  // 110; 144; 76; a = 0.5; // axis color 
  gfx_set(0.431, 0.565, 0.298, 0.5);
  gfx_line(x, axis, x+w, axis);
  
  //--------------------------
  gfx_setfont(2,"Tahoma", 16); // db_scale font
  // -- dependent on the view_mode values --------
  view_mode ? (     // if view_mode = 2 axis
    axis1 = y + hlf_h - hlf_h/2; 
    axis2 = y + hlf_h + hlf_h/2;
    gfx_a-=0.2;
    gfx_line(x, axis1, x+w, axis1); // axis1
    gfx_line(x, axis2, x+w, axis2); // axis2
    hlf_h = hlf_h * 0.5;
    // -- db scale -----------
    wave.draw_DB_Scale(axis1, hlf_h); 
    wave.draw_DB_Scale(axis2, hlf_h); 
  ) : (             // if view_mode = 0;
    axis1 = axis2 = axis;
    // -- db scale -----------
    wave.draw_DB_Scale(axis, hlf_h); 
  );
  
  
  //-- Create waveform Peaks -----------
  wave.CreatePeaks();
  
  //------------------------------------
  //-- Set col alfa dependent of mode --
  view_mode == 0 && chan_mode == 0 ? ( // 1 axis and mix "1/2"
    a1 = ch1.a * ch12.a_mlt; a2 = ch2.a * ch12.a_mlt;
  ) : (
    a1 = ch1.a; a2 = ch2.a;
  );
      
  //-- Draw Peaks ----------------------
  chan_mode == 0 ? ( // 0 = "1/2" = "L/R"
    wave.draw_Peaks(1, axis1, hlf_h, ch1.r, ch1.g, ch1.b, a1); // chan1
    wave.draw_Peaks(2, axis2, hlf_h, ch2.r, ch2.g, ch2.b, a2); // chan2
  ) :
  chan_mode == 1 ? ( // 1 = "L"
    wave.draw_Peaks(1, axis1, hlf_h, ch1.r, ch1.g, ch1.b, a1); // chan1
  ) :
  chan_mode == 2 ? ( // 2 = "R"
    wave.draw_Peaks(2, axis2, hlf_h, ch2.r, ch2.g, ch2.b, a2); // chan2
  );
     
  
  //-- Draw frame ----------------------
  // 110; 144; 76; a = 1; // frame color
  wave.draw_Frame(0.431, 0.565, 0.298, 1);
  //-- Draw info -----------------------
  gfx_setfont(2,"Tahoma", 17); // info font
  //150; 200; 100; a = 1; // info color
  wave.draw_Info(0.588, 0.784, 0.392, 1);
  //-- Draw MousePos_db ----------------
  //150; 200; 100; a = 1;  // mouse_pos color
  wave.draw_MousePos_db(axis, axis1, axis2, hlf_h, 0.588, 0.784, 0.392, 1);
    
  //-- Draw Cursor(for tests) ----------
  //range_sec >= 1 ? wave.draw_Cursor(0.3, 0.3, 0.3, 0.2);

);



/***************************************************************************************************
*** Other functions ********************************************************************************
***************************************************************************************************/
// == For TimeLoop Mode ================================= //
function LoopMode.GetRange()
  instance(last_play_pos, last_samplesblock, range_sec)
  local(play_step)
(
  play_step = play_position - last_play_pos;
  play_state && play_step < 0 ? (
   //range_start = play_position;
   //range_end = last_play_pos + last_samplesblock/srate;
   range_sec = -play_step + last_samplesblock/srate;
   wave.spos = 0; // reset position on loop start!!!
  );
  last_play_pos = play_position;
  last_samplesblock = samplesblock;
  range_sec; // ret range
);

// == For Midi Mode ===================================== //
function MidiMode.GetRange()
  instance(range_sec, note, freq)
  local(offset,msg1,msg2,msg3)
(
  while (midirecv(offset,msg1,msg2,msg3)) (
     (msg1&240)==144 && msg3!=0 ? (
       note = msg2;
       freq = (2^((note-69)/12)) * 440;   // v1
       //freq = 2^(note/12+3.031);        // v2
       //freq = (1.05946^msg2) * 8.17742; // v3
       range_sec = 1/freq;
     );
     midisend(offset,msg1,msg2,msg3); // passthrough other events
  );
  range_sec; // ret range
);


/***************************************************************************************************
** Very simple slider-linked button - only for label-sliders buttons! ******************************
***************************************************************************************************/ 
function Btn_New(x,y,w,h, r,g,b,a, lbl, sldr_idx, val, maxval)
(
  this.x = x; this.y = y; this.w = w; this.h = h; // coord
  this.r = r; this.g = g; this.b = b; this.a = a; // color
  this.lbl = lbl;
  this.sldr_idx = sldr_idx;
  this.val = val;
  //this.minval = minval; 
  this.maxval = maxval;
);

//--------------------------------------
function Btn_Draw()
  instance(x,y,w,h, r,g,b,a, lbl, sldr_idx, val, maxval)
  local(offs, str, str_w, str_h)
(  
  // --- set value -----------
  this.mouseClick() ? (
    val < maxval ? slider(sldr_idx)+=1 : slider(sldr_idx) = 0; // minval = 0 
    val = slider(sldr_idx);
    slider_automate(2 ^ (sldr_idx-1)); // upd to slider
  );
  
  val != slider(sldr_idx) ? val = slider(sldr_idx); // upd from slider
  
  // --- draw ----------------
  this.mouseIN() ? (
    gfx_set(0,0,0,0.4);
    gfx_rect(x+1,y+1,w,h,0); // shadow
    gfx_set(r,g,b,0.25);
    gfx_rect(x,y,w,h,0); // frame
  );
    
  str = #; // tmp str
  strcpy(str, lbl);
  strcat(str, strcpy_fromslider(#, slider(sldr_idx)));
  gfx_measurestr(str, str_w, str_h);
  w = str_w + 16; // update w
  
  gfx_set(0,0,0,0.4); // str shadow col
  offs = (h - gfx_texth) * 0.5; 
  gfx_x = x + 9; gfx_y = y + offs + 1;
  gfx_drawstr(str);
  
  gfx_set(r,g,b,a);
  gfx_x = x + 8; gfx_y = y + offs;
  gfx_drawstr(str);

);


/***************************************************************************************************
** INIT ********************************************************************************************
***************************************************************************************************/
/* Note: We can use "ext_noinit=1", but we want to have a buffer reset in different situations:
On playback start, stop, seek(depends on the settings) etc. And in addition we have @serialize section. 
This is a simple solution.*/

function INIT()
  local(r,g,b,a)
(
  /*Координаты wave - (x,y,w,h) задаются для самой волны(пространство внутри рамки). 
  Координаты внешней рамки, соответственно, получаются чуть другие - см. wave.draw_Frame.*/
  /*Оффсет для sbuf = 65536(на pbuf и всякую хрень).*/
  //--------------------------
  // function args = (x,y,w,h, sbuf, pbuf); // sbuf, pbuf - buffers offsets 
  wave.Init(_Ls+1, _Up, 800, 408, 65536, 0);
  //--------------------------
  // function args = (x,y,w,h,  r,g,b,a,  lbl, sldr_idx, val, maxval)
  r = 150/255; g = 200/255; b = 100/255; a = 1; // buttons color
  SyncMode.Btn_New( 27,402,120,20, r,g,b,a, "Sync: ",    4, 0, 3);
  ViewMode.Btn_New(155,402,92,20, r,g,b,a, "View: ",    5, 0, 1);
  ChanMode.Btn_New(255,402,94,20, r,g,b,a, "Chan: ", 6, 0, 2);
  //--------------------------
  Colors.Btn_New(667,402,144,20, r,g,b,a, "Colors: ", 7, 0, 1);
  ColMenu.New(667,298,144,100,r,g,b,a);
  //--------------------------
  INIT = 1;
);

//******************************************************************************
//------------------------------------------------------------------------------
!INIT ? INIT() : wave.ResetBuffer(); // Init(on first start) else reset buf
  

@serialize
/*Важно, если есть сериализация, все переменные и память не инициализируются 
в ноль по умолчанию при плэй-стоп т.п., только намеренно в init нужно прописывать.*/

// -- Serialize colors ---
function Serialize_Color()
(
  file_var(0, this.r); 
  file_var(0, this.g); 
  file_var(0, this.b); 
  file_var(0, this.a);
);

//------------------------
wave.ch1.Serialize_Color();
wave.ch2.Serialize_Color();


@slider
range_sec = slider1;
wave.Vzoom = slider3;
SyncMode.val = slider4;
ViewMode.val = slider5;
ChanMode.val = slider6;

@block
/*Можно и не считать range когда wave.pause = 1, однако, считая,
мы получим range моментально после, когда отпустим паузу - это удобно.*/
// TimeLoop Mode ---------------------------------
SyncMode.val == 0 ? (
  range_sec = LoopMode.GetRange(); 
  while(range_sec > max_range_sec + 0.001) ( range_sec*=0.5 ); // verify range
) :
// Midi Pitch Mode -------------------------------
SyncMode.val == 1 ? (
  range_sec = MidiMode.GetRange();
  range_sec *= 2^(slider2); // multiply
) :
// Tempo-sync Mode -------------------------------
SyncMode.val == 2 ? (
  //range_sec = 60/tempo * 2^(slider2);    // min 1/4 test
  range_sec = (60/tempo)/4 * 2^(slider2);  // min 1/16 test
  while(range_sec > max_range_sec + 0.001) ( range_sec*=0.5 );
) :
SyncMode.val == 3 ? (
  range_sec = slider1;
);

//-- If wave paused  -----------------------------
wave.pause ? range_sec = slider1;


//-- Verify Range --------------------------------
range_sec <= 0 ? range_sec = 1;
slider1 = range_sec; // range_sec to slider
range_spls = round(range_sec * srate); // range to samples

//-- Update Wave values(divfactor and sbuffer size) ---------
wave.div = range_spls/wave.w; // division factor
wave.sbuf_sz = range_spls*2;  // wave samplebuffer size(1-2 channels)


@sample
// -- Get input samples ------
wave.SamplesToBuffer(spl0, spl1);

@gfx 858 445

char = gfx_getchar(); // Можно перенести - Mouse - module

//******************************************************************************
/*Надо потом поправить Range(Hzoom)-Offset, а лучше вообще всю функцию.
все работает, просто переписать четко, понятно и красиво, сейчас чушь какая-то.*/
//-- Get Mouse  ----------------------------------------------------------------
function wave.GetMouse()
  instance(x,y,w,h)
  local(K, offs_min)
(
  //-- Change Range(aka H Zoom) ----------------------------
  mouse_wheel ? (
    // Free mode(or wave paused) -----------------
    SyncMode.val == 3 || this.pause ? (
      K = mouse_wheel * this.div;     
      range_sec = slider1 -= K/srate;
      slider1 = minmax(slider1, min_range_sec, max_range_sec);
      this.pause && slider1 < max_range_sec && slider1 > min_range_sec ? (
        slider1 = minmax(slider1, min_range_sec, last_range_sec); // regard last range
        offs_min = (range_sec - last_range_sec)*srate;
        offs_min = min(offs_min, 0); // не вылазь за ноль 
        this.offset -= K * (mouse_x-x)/w; 
        this.offset = minmax(this.offset, offs_min, 0); // offs_max = 0;
      );
    ) : (
      // MIDI, Tempo Sync Mode(mlt factor) -------
      SyncMode.val == 1 || SyncMode.val == 2 ? (
        slider2 -= sign(mouse_wheel);
        slider2 = minmax(slider2, 0, 6);
      );
    );
    
  );  
  
  //-- V Zoom, Offset --------------------------------------
  !(mouse_cap&1) ? this.isCaptured = 0; // Reset cap state
  
  //-- Capture(ignore btns area) -------
  this.mouseDown() && mouseINrect(x, y+24, w, h-48) ? (
    this.isCaptured = 1;  // Capture wave
    this.cap_x = mouse_x;
    this.cap_y = mouse_y;
    this.Vzoom.cap_val = this.Vzoom;
  );
  
  //-- V Zoom --------------------------
  this.isCaptured && !Shift ? (
    this.Vzoom = this.Vzoom.cap_val * DB2VAL((mouse_down_y-mouse_y)/20);
    this.Vzoom = minmax(this.Vzoom, 1, DB2VAL(150) );
    slider3 = this.Vzoom;
  );
  
  //-- Offset --------------------------
  this.pause && this.isCaptured && Shift ? (
    this.offset += this.div * (mouse_x-mouse_last_x);
    offs_min = (range_sec - last_range_sec)*srate;
    this.offset = minmax(this.offset, offs_min, 0); // offs_max = 0;
  );
  
  //-- Pause ---------------------------
  this.mouseMClick() ? (
    this.pause = !this.pause;
    this.offset = 0;
    last_range_sec = range_sec;
  );
  
);

//************************************************
//-- Update coords  ------------------------------
function UpdateCoords()
(  
  wave.w = max(700, gfx_w - wave.x - _Rs - 1);
  wave.h = max(268, gfx_h - wave.y - _Dn - 1);
  wave.h -= (wave.h % 4); // Must always be a multiple of four for full symmetry!
  //--------------------
  SyncMode.x = wave.x + 8;  // upd sync btn x
  //ViewMode.x = SyncMode.x + SyncMode.w + 8; // view btn x
  //ChanMode.x = ViewMode.x + ViewMode.w + 8; // chan btn x
  ViewMode.x = max(ViewMode.x, SyncMode.x + SyncMode.w + 8); // view btn x
  ChanMode.x = max(ChanMode.x, ViewMode.x + ViewMode.w + 8); // chan btn x
  SyncMode.y = ViewMode.y = ChanMode.y = wave.y + wave.h - 20 - 4; // btns y
  //--------------------
  Colors.x = ColMenu.x = wave.x + wave.w - Colors.w - 8; // col btn x
  Colors.y = SyncMode.y; // col btn y
  //--------------------
  ColMenu.y = Colors.y - ColMenu.h - 4; // col menu y
  ColMenu.w = Colors.w; // col menu w
);

//************************************************
//-- Change Colors -------------------------------
function ChangeColors()
(
  Colors.val == 0 ? (
    ColMenu.Draw(wave.ch1);
  ) :
  Colors.val == 1 ? (
    ColMenu.Draw(wave.ch2);
  ); 

);

/*******************************************************************************
** DRAW ************************************************************************
*******************************************************************************/
function DRAW()
( 
  UpdateCoords();
  // -- waveforms ------------
  wave.Draw();
  !(Show_ColMenu && ColMenu.mouseIN()) ? wave.GetMouse();
  // -- buttons --------------
  gfx_setfont(2,"Tahoma", 17); // buttons font
  SyncMode.Btn_Draw();
  ViewMode.Btn_Draw();
  ChanMode.Btn_Draw();
  // -- colors ---------------
  /*wave.mouseDblClick() && mouseINrect(wave.x, wave.y+24, wave.w, wave.h-48) && 
  !ColMenu.mouseIN() ? Show_ColMenu = !Show_ColMenu;*/
  /*mouse_rdown ? (
    gfx_x = mouse_x;
    gfx_y = mouse_y; 
    ret = gfx_showmenu("Set Colors|Help")
  );*/
  
  char == 43 ? Show_ColMenu = !Show_ColMenu; // "+" ( Shift + "=")
  Show_ColMenu ? (
    Colors.Btn_Draw();
    ChangeColors();
  );
  
);


//***************************************************************
/* Можно не морочить голову с пересчетами, а просто снизить на 10-15 фпс на больших рангах!
Все равно там фпс похеру абсолютно. Как в Рипере при записи. Пока пусть так будет.*/
GetMouseState();
DRAW();
SetMouseLastState();
//***************************************************************