desc:WaveScopeStereo v1-1-2
options:no_meter


slider1:1<0.01,4,0.00001>-Range(in sec)
slider2:0<0,4,1>-for mlt Hor Zoom
slider3:1<1,10,0.01>-Vertical Zoom
slider4:0<0,3,1{TimeLoop, MidiPitch, Tempo, Free} >-Sync Mode:
slider5:0<0,1,1{1 axis,2 axis}>-View Mode:
slider6:0<0,2,1{ 1 / 2 (Stereo), 1+2 / 3+4 (Multy) }>-Chan Mode:

in_pin:IN1
in_pin:IN2
in_pin:IN3
in_pin:IN4

@init

ext_nodenorm = 1;

//-- 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; );
  /* release coordinates, not used in this case --
  mouse_up  ? (mouse_up_x  = mouse_x; mouse_up_y  = mouse_y; );
  mouse_rup ? (mouse_rup_x = mouse_x; mouse_rup_y = mouse_y; );
  mouse_mup ? (mouse_mup_x = mouse_x; mouse_mup_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 
);




//-- 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); );


/***************************************************************************************************
*** Waveform functions *****************************************************************************
***************************************************************************************************/
// == Init Wave ========================================= //
function wave.init(x,y,w,h, r1,g1,b1,a1, r2,g2,b2,a2, sbuf) 
(
  this.x = x; this.y = y; this.w = w; this.h = h; // coord 
  this.r1 = r1; this.g1 = g1; this.b1 = b1; this.a1 = a1; // ch1 color
  this.r2 = r2; this.g2 = g2; this.b2 = b2; this.a2 = a2; // ch2 color
  this.sbuf = sbuf; // samplebuffer memory offset, must NOT cross other used mem slots!
  this.view_mode = 1; // view mode 0=on one axis, 1=on sep axis
  this.Vzoom = 1;     // vertical zoom 
  this.div = 200;     // divfactor(smpls in peak)
);

// == Input samples to sbuf(sample buffer) ============== //
function wave.SamplesToBuffer(input1, input2)
  instance(sbuf, sbuf_sz, spos)   
(
  spos>=sbuf_sz ? (
    //memset(sbuf,0,sbuf_sz); clear sbuf(if need) 
    spos=0;
  );
  
  sbuf[spos]   = input1;
  sbuf[spos+1] = input2;
  //------------------------------------
  spos+=2; // upd smpl pos counter

);
// == Draw Wave ========================================= //
function wave.draw_waveform()
  instance(x,y,w,h, r1,g1,b1,a1, r2,g2,b2,a2, sbuf, view_mode, Vzoom, div)
  local(hlf_h, axis, axis1, axis2, peak_x, s, next_peak,
    cur_spl1, min_peak1, max_peak1, last_min_peak1, last_max_peak1,
    cur_spl2, min_peak2, max_peak2, last_min_peak2, last_max_peak2) 
( 
  
  // -- center axis --------
  hlf_h = h/2;
  axis  = y + hlf_h;
  gfx_set(0.6,0.8,0.6,0.2);
  gfx_line(x, axis, w, axis);
  
  // -- dependent on the view_mode values --------
  view_mode = ViewMode.val ? (    // if view_mode = 1;
    axis1 = hlf_h - hlf_h/2;
    axis2 = hlf_h + hlf_h/2;
    hlf_h = hlf_h/2;
  ) : (            // if view_mode = 0;
    axis1 = axis2 = axis; 
  );
     
  //-- Get samples from sbuf, create peaks, draw waves -----
  peak_x = x; // first peak x-pos
  s = 0;      // sample cnt
  last_min_peak1 = last_max_peak1 = 0; //     
  last_min_peak2 = last_max_peak2 = 0; //     
  
  loop(w,
      // -- reset min-max peaks(v1-draw) ---------
      div > 1 ? (
        min_peak1 = min_peak2 = 10; max_peak1 = max_peak2 = -10; 
      ) : ( // if divfactor < 1 (less than one sample on one peak) 
        min_peak1 = min_peak2 = 0; max_peak1 = max_peak2 = 0;
      );
      
      // -- Create peaks from buffer samples -----
      next_peak = floor((peak_x+1) * div * 2 ); 
      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
      );
    
        // -- 1-channel
        min_peak1 = hlf_h * min_peak1 * Vzoom;
        max_peak1 = hlf_h * max_peak1 * Vzoom;
        min_peak1 = min( max(min_peak1, -hlf_h), hlf_h); // verify range
        max_peak1 = min( max(max_peak1, -hlf_h), hlf_h); // verify range
        // -- 2-channel
        min_peak2 = hlf_h * min_peak2 * Vzoom;
        max_peak2 = hlf_h * max_peak2 * Vzoom;
        min_peak2 = min( max(min_peak2, -hlf_h), hlf_h); // verify range
        max_peak2 = min( max(max_peak2, -hlf_h), hlf_h); // verify range    
      
      // -- Draw waveforms -----------------------
      gfx_set(r1, g1, b1, a1); //-- 1 peak line --
      gfx_line(peak_x, axis1-min_peak1, peak_x, axis1-max_peak1); // 1
      gfx_a += 0.3;            //-- 1 contour ----
      gfx_line(peak_x-1, axis1-last_min_peak1, peak_x, axis1-min_peak1, 1); // 1
      gfx_line(peak_x-1, axis1-last_max_peak1, peak_x, axis1-max_peak1, 1); // 1
      
      gfx_set(r2, g2, b2, a2); //-- 2 peak line --
      gfx_line(peak_x, axis2-min_peak2, peak_x, axis2-max_peak2); // 2
      gfx_a += 0.3;            //-- 2 contour ----
      gfx_line(peak_x-1, axis2-last_min_peak2, peak_x, axis2-min_peak2, 1); // 2
      gfx_line(peak_x-1, axis2-last_max_peak2, peak_x, axis2-max_peak2, 1); // 2    
    
      // -- store last peaks ---
      last_min_peak1 = min_peak1;
      last_max_peak1 = max_peak1;
      last_min_peak2 = min_peak2;
      last_max_peak2 = max_peak2;
  
      // -----------------------
      peak_x+=1; // to next x
  );

  //-- Draw frame ---------- 
  gfx_set(0.2,0.2,0.2,1);
  //gfx_rect(x-1,y,w+2,h+1,0);
  //gfx_rect(x,y,w+1,h+1,0);
  //gfx_rect(x-1,y-1,w+1,h+3,0);
  
);



/***************************************************************************************************
*** Other functions ********************************************************************************
***************************************************************************************************/
// For info, debug etc(flag < 0 = string, flag >= 0 = number, ndigits)
// If x,y = 0 - draw in current position 
function ShowMsg(x,y, msg, flag)
(  
  gfx_r = gfx_g = gfx_b = gfx_a = 0.9;
  x && y ? (gfx_x = x; gfx_y = y; );
  //gfx_set(0.7, 0.7, 0.7, 1);
  gfx_set(0.6, 0.8, 0.6, 1);
  gfx_setfont(1,"Arial", 14);
  flag >= 0 ? gfx_drawnumber(msg, flag) : gfx_drawstr(msg);
);

// == For TimeLoop Mode ================================= //
function LoopMode.GetRange()
  local(play_step)
  instance(last_play_pos, last_samplesblock, range_sec)
(
  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;
  );
  last_play_pos = play_position;
  last_samplesblock = samplesblock;
  range_sec; // ret range
);

// == For Midi Mode ===================================== //
function MidiMode.GetRange()
  local(offset,msg1,msg2,msg3)
  instance(range_sec, note, freq)
(
  while (midirecv(offset,msg1,msg2,msg3)) (
     (msg1&240)==$x90 && 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)
(  
  // --- get-set value -------
  this.mouseClick() ? (
    val < maxval ? slider(sldr_idx)+=1 : slider(sldr_idx) = 0;
    val =  slider(sldr_idx);
    slider_automate(2 ^ (sldr_idx-1));
  );
  //val != slider(sldr_idx) ? val = slider(sldr_idx);
  // --- draw ----------------
  gfx_set(r,g,b,a);
  gfx_rect(x,y,w,h,0);
  offs = h - gfx_texth;
  gfx_a = 1;  
  gfx_x = x + offs; gfx_y = y + offs/2;
  gfx_drawstr(lbl);
  gfx_drawstr( strcpy_fromslider( #, slider(sldr_idx) ) );

);



//*******************************************************************************
//*******************************************************************************
// function args = (x,y,w,h, r1,g1,b1,a1, r2,g2,b2,a2, sbuf) !!!
wave.init(1,1,960,580, 0.6,0.3,0.3,0.3, 0.3,0.6,0.3,0.3, 0);

//------------------------------------------------------------------------------
// function args = (x,y,w,h, r,g,b,a, lbl, sldr_idx, val, maxval)
SyncMode.btn_New( 10,10,140,20, 0.6,0.8,0.6,0.4, "Sync Mode: ",    4, 0, 3);
ViewMode.btn_New(160,10,120,20, 0.6,0.8,0.6,0.4, "View Mode: ",    5, 0, 1);
ChanMode.btn_New(210,10,180,20, 0.6,0.8,0.6,0.4, "Channel Mode: ", 6, 0, 1);


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



@block

// TimeLoop Mode ---------------------------------
SyncMode.val == 0 ? (
  range_sec = LoopMode.GetRange();  
  while(range_sec > 4.001) ( range_sec*=0.5 ); // 4.001

);

// Midi Pitch Mode -------------------------------
SyncMode.val == 1 ? (
  range_sec = MidiMode.GetRange();
  range_sec *= 2^(slider2); // multiply
);

// Test tempo-sync - range -----------------------
SyncMode.val == 2 ? (
  range_sec = 60/tempo * 2^(slider2);
  while(range_sec > 4.001) ( range_sec*=0.5 ); // 4.001
);

// Free mode -------------------------------------
SyncMode.val == 3 ? (
    range_sec = slider1;
);


//-- Verify Range --------------------------------
range_sec <= 0 ? range_sec = 1;
slider1 = range_sec; // range_sec to slider
range_spls = floor(range_sec * srate + 0.5); // Range to samples
SyncMode.val == 1 ? while(range_spls*0.5<wave.w) ( range_spls*=2 );  //   ?


//-- 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
ChanMode.val ? ( 
  // 1+2 / 3+4
  input1 = (spl0 + spl1) * 0.5;
  input2 = (spl2 + spl3) * 0.5;
) : (
  // 1 / 2
  input1 = spl0;
  input2 = spl1;
);  

// 1 + 2 - Mono
// input1 = input2 = (spl0 + spl1) * 0.5; //  

wave.SamplesToBuffer(input1, input2);

@gfx 960 580

gfx_getchar(); // request mouse_cap to be set even when mouse button not down

//***************************************************************
//-- Upd coord  ----------------------------------
function wave.Zoom()
  local(K)
(
  //-- H Zoom -----------------------
  mouse_wheel ? (
    // MIDI,Tempo Sync Mode ---
    SyncMode.val == 1 || SyncMode.val == 2 ? (
      slider2 -= sign(mouse_wheel);
      slider2 = max(min(slider2,6),0);
    );
    // Free mode ---------
    SyncMode.val == 3 ? (
      mouse_cap&4 ? K = 1.01 : K = 1.1;
      mouse_wheel<0 ? slider1 *= K;
      mouse_wheel>0 ? slider1 /= K;
      slider1 = max(min(slider1,4),0.001);
    );
  );  
  
  //-- V Zoom -----------------------
  wave.mouseDown() ? (
    wave.isCaptd = 1;
    wave.cap_x = mouse_x;
    wave.cap_y = mouse_y;
    wave.Vzoom.cap_val = wave.Vzoom;
  );
  //------
  mouse_up ? wave.isCaptd = 0;
  //------
  wave.isCaptd ? (
    wave.Vzoom = wave.Vzoom.cap_val + (mouse_down_y-mouse_y)/150;
    wave.Vzoom = min(max(wave.Vzoom,1),10 );
    slider3 = wave.Vzoom;
  );

);


function UpdateCoord()
(  
  wave.w = gfx_w-wave.x-1;
  wave.h = gfx_h-wave.y-1;
  
  gfx_setfont(1,"Arial", 15);
  
  SyncMode.x = wave.w - SyncMode.w - 4;     // upd sync x
  ViewMode.x = SyncMode.x - ViewMode.w - 4; // upd view x
  ChanMode.x = ViewMode.x - ChanMode.w - 4; // upd chan x
  
  SyncMode.y = ViewMode.y = ChanMode.y = wave.y + 4;     // upd y  
  
  ShowMsg(wave.x + 4, wave.y + 4, range_spls, 0);
  ShowMsg(0,0, " spl", -1);
);

//-- Draw -------------------------
function Draw()
( 
  UpdateCoord();
  wave.Zoom();
  SyncMode.btn_Draw();
  ViewMode.btn_Draw();
  ChanMode.btn_Draw();
  wave.draw_waveform();
);


GetMouseState();
//***************************************************************
Draw();
//***************************************************************
SetMouseLastState();
