// This effect Copyright (C) 2021 and later Cockos Incorporated
// License: LGPL - http://www.gnu.org/licenses/lgpl.html

desc: Loudness Meter Peak/RMS/LUFS (Cockos)
//tags: analysis loudness meter cockos
//author: Cockos


slider1:cfg_peak=4<0,4,1{off,true peak,true peak clips,peak,peak clips}>-Peak
slider2:cfg_rms_m=0<0,1,1{off,on}>-RMS momentary
slider3:cfg_rms_i=0<0,1,1{off,on}>-RMS integrated
slider4:cfg_lufs_m=2<0,2,1{off,on,on + histogram}>-LUFS momentary
slider5:cfg_lufs_s=1<0,1,1{off,on}>-LUFS short-term
slider6:cfg_lra=1<0,1,1{off,on}>-LRA loudness range
slider7:cfg_lufs_i=1<0,1,1{off,on}>-LUFS integrated
slider8:cfg_alert=0<0,3,1{off,yellow,red,yellow + red>-LUFS alerts
slider9:cfg_yellow=-12<-60,0>-Yellow alert level
slider10:cfg_red=-6<-60,0>-Red alert level
slider11:cfg_reinit=1<0,1,1{off,on}>-Reset on playback start
slider12:cfg_mono=0<0,1,1{off,on}>-Force mono analysis
slider13:cfg_textsize=0<-2,8,1>-Text size
slider14:cfg_yscale=1.8<0.5,4,0.1>-Y axis scaling
slider15:cfg_out_auto=0<0,16,1{off,all,all (inverted),Peak,Peak (inverted),RMS-M,RMS-M (inverted),RMS-I,RMS-I (inverted),LUFS-M,LUFS-M (inverted),LUFS-S,LUFS-S (inverted),LUFS-I,LUFS-I (inverted),LRA,LRA (inverted)}>-Output loudness values as automation

slider30:outparm_peak=-150<-150,20,1>-Peak/True peak dB (output)
slider31:outparm_rms_m=-100<-100,0,1>-RMS-M (output)
slider32:outparm_rms_i=-100<-100,0,1>-RMS-I (output)
slider33:outparm_lufs_m=-100<-100,0,1>-LUFS-M (output)
slider34:outparm_lufs_s=-100<-100,0,1>-LUFS-S (output)
slider35:outparm_lufs_i=-100<-100,0,1>-LUFS-I (output)
slider36:outparm_lufs_lra=0<0,100,1>-LRA (output)

options:no_meter


@init

<?
  nch = 64; // maximum channel count
  sinc_sz = 16; // true peak interpolation size
  use_mem_sinc = sinc_sz>32;

  printf("sinc_sz = %d;\n",sinc_sz);

 function do_all_channels(fmt) local(p x c) global(nch) (
   p=x=0;
   while (x < nch) (
     p+=1;
     x>0 ? printf("num_ch > %d ? ( ",x);
     c = x>=24 ? 8 : x>=8 ? 4 : 2;
     loop(c, printf(fmt,x,x); x+=1);
     printf("\n");
   );
   loop(p-1,printf(")"));
 );
?>

// rms-i, lufs-i, lra calculated 0=during playback only, 1=always
WANT_INTEGRATED_ALWAYS=0;

FONT_SZ_MIN=12;
FONT_SZ_MAX=16;
// $xRRGGBB
BG_COLOR=$x000000;
GRID_COLOR=$x7f7f7f;
TEXT_COLOR=$xffffff;
PEAK_COLOR=$x3fff3f;
PEAK_CLIP_COLOR=$xff0000;
RMS_COLOR=$x7f7f7f;
LUFS_COLOR=$x00bfff;
LUFS_HIST_COLOR=$x005f7f;
RED_COLOR=$xff0000;
YELLOW_COLOR=$xffff00;
MONO_COLOR=$xffa500;

PEAK_METER_DECAY=0.150;
LOUD_METER_UPDATE=0.100; // default 100ms, must be divisible into both 0.4 and 3.0
LOUD_METER_SPEED=0.075;

UI_SLIDER_MASK=(1<<15)-1;

ext_noinit=1;
ext_nodenorm=1;
gfx_ext_retina=max(gfx_ext_retina,1);

sliders_showing=0;

NUM_BINS=1024;
BINS_PER_DB=10;
DB_PER_BIN=1/BINS_PER_DB;

function alloc(sz) global()
(
  memset((this.top+=sz)-sz,0,sz);
);

// sinc filter for true peak
function sinc_gen_val() global(srate sinc_sz) local(sincpos windowpos) instance(slice_pos) (
  windowpos = (2.0 * $pi / sinc_sz) * slice_pos;
  sincpos = $pi * (slice_pos - sinc_sz * .5);
  slice_pos += 1;
  (0.53836 - cos(windowpos)*0.46164) * sin(sincpos) / sincpos;
);

function sinc_gen_slice(cs, o*) instance(sinc_gen_val slice_pos) local(x) global(sinc_sz) (
  slice_pos = cs;
<? 
  use_mem_sinc ? (
    printf("o.v = alloc(sinc_sz);  loop(x=0;sinc_sz, o.v[x] = sinc_gen_val(); x+=1); ");
  ) : (
    loop(x=0;sinc_sz, printf("o.v%02d = sinc_gen_val();%s",x,(x&3)==3?"\n": " "); x += 1)
  );
?>
);

function sinc_init() global(srate) instance(sinc_gen_slice) (
  sinc_gen_slice(srate < 96000 ? .25 : .5,this.s1);
  srate < 96000 ? (
    sinc_gen_slice(.5, this.s2);
    sinc_gen_slice(.75, this.s3);
  );
);

<?
 function emit_sinc(hist, t) (
   use_mem_sinc ? (
    printf("abs(mem_multiply_sum(%s.v,%s.h,sinc_sz))",t,hist);
   ) : (
     printf("abs(");
     loop(x=0;sinc_sz, printf("%s.h%02d * %s.v%02d%s",hist,x,t,x,x==sinc_sz-1?"":(x&3)==3?" +\n":" + "); x+=1);
     printf(")");
   );
 );
?>

function init(chidx)
(
  this.chan=chidx;
  this.wt = chidx < 3 || num_ch < 6 ? 1 : chidx == 3 ? 0 : sqrt(2);
  this.f1p1=this.f1p2=this.f2p1=this.f2p2=0;
  this.pkval=this.hipkval=this.clips=0;
  this.ch_lufs_sum=this.ch_rms_sum=0;
  <? 
    use_mem_sinc ? (
      printf("this.h = alloc(sinc_sz);\n"); 
    ) : ( 
      loop(x=0;sinc_sz, printf("this.h%02d =%s",x,x==sinc_sz-1 ? " 0" : (x&7)==7 ? "\n" : " "); x+=1);
    );
  ?>
);

function decay()
(
  this.pkval *= pk_decay;
);

function copypks() global(pk hipk clip_cnt) (
  pk[this.chan] = this.pkval;
  hipk[this.chan] = this.hipkval;
  clip_cnt[this.chan] = this.clips;
);

function proc(lspl)
  local(pspl f1p0 f2p0)
  instance(wt ch_rms_sum ch_lufs_sum pkval hipkval clips
           f1p1 f1p2 f2p1 f2p2)
  global(f1a1 f1a2 f1b0 f1b1 f1b2
         f2a1 f2a2 f2b0 f2b1 f2b2
         srate cfg_peak
         lval rval win_pos global_peak sinc_sz
         sinc.s1* sinc.s2* sinc.s3*
         )
(
  cfg_peak == 1 || cfg_peak == 2 ? (
    <?
      use_mem_sinc ? (
        printf("mem_insert_shuffle(this.h, sinc_sz, lspl);");
      ) : (
        loop(x=sinc_sz-1;sinc_sz-1, printf("this.h%02d = this.h%02d;%s",x,x-1, (x&3)==0 ? "\n" : " "); x-= 1; );
        printf(" this.h00 = lspl;");
      );
    ?>
    pspl = max(
        abs(this.h<? printf(use_mem_sinc ? "[%d]" : "%02d",sinc_sz/2) ?> ),
        <? emit_sinc("this","sinc.s1") ?>
       );
    srate < 96000 ? pspl = max(pspl,max( <? emit_sinc("this","sinc.s2") ?>,  <? emit_sinc("this","sinc.s3") ?>));
  ) : 
  cfg_peak == 3 || cfg_peak == 4 ? (
    pspl=abs(lspl);
  );
  cfg_peak ? (
    pspl > pkval ? (
      pkval=pspl;
      pspl > hipkval ? (
        hipkval=pspl;
        global_peak = max(global_peak,pspl);
      );
    );
    pspl > 1.0 ? clips += 1;
  );

  win_pos == 0 ? ch_rms_sum=ch_lufs_sum=0;

  rval += (ch_rms_sum += lspl*lspl);
  
  lspl *= wt;

  f1p0=lspl-f1a1*f1p1-f1a2*f1p2;
  lspl=f1b0*f1p0+f1b1*f1p1+f1b2*f1p2;
  f1p2=f1p1;
  f1p1=f1p0;
  
  f2p0=lspl-f2a1*f2p1-f2a2*f2p2;
  lspl=f2b0*f2p0+f2b1*f2p1+f2b2*f2p2;
  f2p2=f2p1;
  f2p1=f2p0;

  lval += (ch_lufs_sum += lspl*lspl);
);

function init_lufs_filters()
  local(Vh Vb db f0 Q K a0)
  global(f1a1 f1a2 f1b0 f1b1 f1b2
         f2a1 f2a2 f2b0 f2b1 f2b2 srate)
(
  // f1,f2 could be combined into a 5th order filter

  db=3.999843853973347;
  f0=1681.974450955533;
  Q=0.7071752369554196;
  K=tan($pi*f0/srate);
  Vh=pow(10, db/20);
  Vb=pow(Vh, 0.4996667741545416);
  a0=1+K/Q+K*K;
  f1a1=2*(K*K-1)/a0;
  f1a2=(1-K/Q+K*K)/a0;
  f1b0=(Vh+Vb*K/Q+K*K)/a0;
  f1b1=2*(K*K-Vh)/a0;
  f1b2=(Vh-Vb*K/Q+K*K)/a0;

  f0=38.13547087602444;
  Q=0.5003270373238773;
  K=tan($pi*f0/srate);
  f2a1=2*(K*K-1)/(1+K/Q+K*K);
  f2a2=(1-K/Q+K*K)/(1+K/Q+K*K);
  f2b0=1;
  f2b1=-2;
  f2b2=1;
);

function Reset()
(
  init_lufs_filters();

  alloc.top=0;
  pk=alloc(<?printf("%d",nch)?>);
  hipk=alloc(<?printf("%d",nch)?>);
  clip_cnt=alloc(<?printf("%d",nch)?>);
  sinc.sinc_init();

  m_win_cnt=0.4/LOUD_METER_UPDATE;
  s_win_cnt=3/LOUD_METER_UPDATE;

  win_pos=0;
  win_cnt=0;
  win_len=(LOUD_METER_UPDATE*srate)|0;
  i_win_len=1/(m_win_cnt*win_len);
  i_win_len2=1/(s_win_cnt*win_len);

  rms_m_sum=0;
  rms_m_db=-100;
  rms_m_db_max=-100;
  rms_i_sum=0;
  rms_i_sum_cnt=0;
  rms_i_db=-100;

  lufs_m_sum=0;
  lufs_m_sum_max=0;
  lufs_m_db=-100;
  lufs_s_sum=0;
  lufs_s_sum_max=0;
  lufs_s_db=-100;
  lufs_a_sum=0;
  lufs_a_sum_cnt=0;
  lufs_b_sum=0;
  lufs_b_sum_cnt=0;
  lra_db_diff = 0;
  lra_db_hi = lra_db_lo = lufs_i_db = lufs_m_db = lufs_s_db = -100;

  last_t=0;
  th_lufs_i=th_lufs_s=th_lufs_m=th_rms_i=th_rms_m=0;

  cur_buf=0;
  cur_buf2=0;
  rms_buf=alloc(m_win_cnt);
  lufs_buf=alloc(m_win_cnt);
  lufs_buf2=alloc(s_win_cnt);
  lufs_a_hist=alloc(2*NUM_BINS);
  lufs_b_hist=alloc(NUM_BINS);
  db_hist=alloc(75);
  db_hist_max=0;

  global_peak = 0;

  <? do_all_channels("ch%d.init(%d); "); ?>;
);

Reset();


@block

want_reset =
  (num_ch > 0 && num_ch != last_nch) ||
  srate != last_sr;
last_nch=num_ch;
last_sr=srate;

cfg_reinit && (play_state&1) ? (
  abs(play_position-last_play_pos) > 0.1 ? want_reset=1;
  last_play_pos=play_position+samplesblock/srate;
);

want_reset ? Reset();

pk_decay=pow(0.5, samplesblock/srate/PEAK_METER_DECAY);

<? do_all_channels("ch%d.decay(); "); ?>;

function make_output_slider(b, base) ( cfg_out_auto>=2 && !(cfg_out_auto&1) ? base - b : b );
outparm_lufs_m = make_output_slider(lufs_m_db + (cfg_mono ? -3 : 0),-100);
outparm_lufs_s = make_output_slider(lufs_s_db + (cfg_mono ? -3 : 0),-100);
outparm_lufs_i = make_output_slider(lufs_i_db + (cfg_mono ? -3 : 0),-100);
outparm_lufs_lra = make_output_slider(lra_db_diff,100);
outparm_peak = make_output_slider(global_peak > 0 ? log(global_peak)*20/log(10) : -150,-150);
outparm_rms_m = make_output_slider(rms_m_db + (cfg_mono ? -3 : 0),-100);
outparm_rms_i = make_output_slider(rms_i_db + (cfg_mono ? -3 : 0),-100);

cfg_out_auto ? slider_automate(
  cfg_out_auto > 2 ? (2^(29 + floor((cfg_out_auto-3)/2))) : (
    2^29 | // (true) peak
    2^30 | // rms-m
    2^31 | // rms-i
    2^32 | // lufs-m
    2^33 | // lufs-s
    2^34 | // lufs-i
    2^35 | // lra
    0
  )
);

@sample

rval=lval=0;

<? do_all_channels("ch%d.proc(spl%d); "); ?>;

(win_pos += 1) >= win_len ? (
  win_pos=0;
  win_cnt += 1;

  prev_rval=rms_buf[cur_buf];
  rms_buf[cur_buf]=rval;
  
  prev_lval=lufs_buf[cur_buf];
  lufs_buf[cur_buf]=lval;
  
  prev_lval2=lufs_buf2[cur_buf2];
  lufs_buf2[cur_buf2]=lval;

  (cur_buf += 1) >= m_win_cnt ? cur_buf=0;
  (cur_buf2 += 1) >= s_win_cnt ? cur_buf2=0;

  rms_m_sum += (rval-prev_rval)*i_win_len;
  lufs_m_sum += (lval-prev_lval)*i_win_len;
  lufs_s_sum += (lval-prev_lval2)*i_win_len2;
  
  WANT_INTEGRATED_ALWAYS || (play_state&1) ? (
    rms_i_sum += rms_m_sum;
    rms_i_sum_cnt += 1;
    rms_i_sum > 0 && rms_i_sum_cnt >= m_win_cnt ? (
      rms_i_db=log(rms_i_sum/rms_i_sum_cnt)*10/log(10);
    ) : (
      rms_i_db=-100;
    );
  );
  
  rms_m_sum > 0 && win_cnt >= m_win_cnt ? (
    rms_m_db=log(rms_m_sum)*10/log(10);
    rms_m_db > rms_m_db_max ? rms_m_db_max=rms_m_db;
  ) : (
    rms_m_db=-100;
  );
  
  lufs_m_sum > 0 && win_cnt >= m_win_cnt ? (
    lufs_m_sum > lufs_m_sum_max ? lufs_m_sum_max=lufs_m_sum;
    lufs_m_db=-0.691+log(lufs_m_sum)*10/log(10);

    a = WANT_INTEGRATED_ALWAYS || (play_state&1) ? ((lufs_m_db+70)*BINS_PER_DB)|0 : -1;
    a >= 0 ? (
      a >= NUM_BINS ? a=NUM_BINS-1;
      lufs_a_sum += lufs_m_sum;
      lufs_a_sum_cnt += 1;
      lufs_a_hist[2*a] += 1;
      lufs_a_hist[2*a+1] += lufs_m_sum;
      db_hist_max = max(db_hist_max,db_hist[min(a*DB_PER_BIN,74)] += 1);

      lufs_a_db=-0.691+log(lufs_a_sum/lufs_a_sum_cnt)*10/log(10);
      lufs_a_gate=((lufs_a_db-10+70)*BINS_PER_DB)|0;
      lufs_i_sum=0;
      lufs_i_cnt=0;
      bin=max(lufs_a_gate,0);
      loop(NUM_BINS-bin,
        lufs_i_cnt += lufs_a_hist[2*bin];
        lufs_i_sum += lufs_a_hist[2*bin+1];
        bin += 1;
      );
      lufs_i_db=lufs_i_sum > 0 ? -0.691+log(lufs_i_sum/lufs_i_cnt)*10/log(10) : -100;
    );
  ) : (
    lufs_m_db=-100;
  );

  lufs_s_sum > 0 && win_cnt >= s_win_cnt ? (
    lufs_s_sum > lufs_s_sum_max ? lufs_s_sum_max=lufs_s_sum;
    lufs_s_db=-0.691+log(lufs_s_sum)*10/log(10);

    b = WANT_INTEGRATED_ALWAYS || (play_state&1) ? ((lufs_s_db+70)*BINS_PER_DB)|0 : -1;
    b >= 0 ? (
      b >= NUM_BINS ? b=NUM_BINS-1;
      lufs_b_sum += lufs_s_sum;
      lufs_b_sum_cnt += 1;
      lufs_b_hist[b] += 1;

      lufs_b_db=-0.691+log(lufs_b_sum/lufs_b_sum_cnt)*10/log(10);
      lufs_b_gate=((lufs_b_db-20+70)*BINS_PER_DB)|0;

      lra_cnt=0;
      bin=max(lufs_b_gate,0);
      loop(NUM_BINS-bin,
        lra_cnt += lufs_b_hist[bin];
        bin += 1;
      );
      lra_cnt >= 20 ? (
        lra_cnt_lo=lra_cnt_hi=0;
        bin=lufs_b_gate;
        while(bin < NUM_BINS && lra_cnt_lo < lra_cnt*0.10)
        (
          lra_cnt_lo += lufs_b_hist[bin];
          bin += 1;
        );
        bin_lo=bin-1;
        bin=NUM_BINS-1;
        while(bin >= lufs_b_gate && lra_cnt_hi < lra_cnt*0.05)
        (
          lra_cnt_hi += lufs_b_hist[bin];
          bin -= 1;
        );
        bin_hi=bin+1;
        lra_db_lo=bin_lo*DB_PER_BIN-70;
        lra_db_hi=bin_hi*DB_PER_BIN-70;
        lra_db_diff = lra_db_hi-lra_db_lo;
      );
    );
  ) : (
    lufs_s_db=-100;
  );
);


@gfx 300 400

<? do_all_channels("ch%d.copypks(); ");?>;

t=time_precise();
speed = last_t ? 1.0-pow(0.5, (t-last_t)/LOUD_METER_SPEED) : 1;
last_t=t;

gfx_clear=((BG_COLOR&$xff)<<16)|(BG_COLOR&$xff00)|((BG_COLOR&$xff0000)>>16);

fsz=FONT_SZ_MIN+min(gfx_w/100,FONT_SZ_MAX-FONT_SZ_MIN);
fsz *= 1.0+cfg_textsize/10;
gfx_ext_retina > 1 ? fsz *= 1.5;
gfx_setfont(1,"Arial",fsz);
rsz=2*gfx_ext_retina;

xu=gfx_texth*3/4;
yu=gfx_texth*7/8;

want_vu = gfx_h > yu*7;
want_axis = gfx_h > yu*10 && gfx_w > xu*14;
want_capt = gfx_h > yu*4;

nch = cfg_mono ? 1 : max(num_ch,2);

xl=xr=xu;
want_axis ? xr += gfx_texth*2;

vh = want_vu ? gfx_h-yu*5.5 : gfx_h/2-yu;
y0 = want_vu ? gfx_h-yu*3.5 : gfx_h/2-yu*3/4;
yt = (want_vu ? y0+yu : gfx_h/2)|0;
ylo=gfx_h-yu;
yhi=yu;

want_peak = cfg_peak ? 1 : 0;
want_rms_m = cfg_rms_m ? 1 : 0;
want_rms_i = cfg_rms_i ? 1 : 0;
want_lufs_m = cfg_lufs_m ? 1 : 0;
want_lufs_s = cfg_lufs_s ? 1 : 0;
want_lra = cfg_lra ? 1 : 0;
want_lufs_i = cfg_lufs_i ? 1 : 0;

cols=want_peak*nch+want_rms_m+want_rms_i+
  want_lufs_m+want_lufs_s+want_lra*0.5+want_lufs_i;

colw = cols ? (gfx_w-xl-xr)/cols : gfx_w;
while(colw < xu*3 && cols > want_lufs_i) (
  want_peak ? ( want_peak=0; cols -= nch; ) :
  want_rms_m ? ( want_rms_m=0; cols -= 1; ) :
  want_rms_i ? ( want_rms_i=0; cols -= 1; ) :
  want_lufs_m ? ( want_lufs_m=0; cols -= 1; ) :
  want_lufs_s ? ( want_lufs_s=0; cols -= 1; ) :
  want_lra ? ( want_lra=0; cols -= 0.5; );
  colw=(gfx_w-xl-xr)/cols;
);

want_abbr = colw < gfx_texth*4;

x=gfx_w-xr-colw/2;

function draw_str(ts tx ty tc)
(
  gfx_x=tx;
  gfx_y=ty;
  gfx_drawstr(ts,tc|256,tx,ty);
);

function format_dbv(hv)
(
  hv <= -72 ? "" : sprintf(#, "%+.1f", hv);
);
function format_db(v)
(
  v <= 10^(-72/20) ? "" : format_dbv(log(v)*20/log(10));
);
function scale_db1(v) global(cfg_yscale)
(
  v <= -72 ? 0 : v >= 0 ? 1 : pow(v*1/72+1,cfg_yscale);
);
function scale_v(v) global(cfg_yscale)
(
  v <= 10^(-72/20) ? 0 : v >= 1 ? 1 : pow(log(v)*20/log(10)/72+1,cfg_yscale);
);

function set_color(trgb)
(
  gfx_set((trgb&$xff0000)/$xff0000,(trgb&$xff00)/$xff00,(trgb&$xff)/$xff);
);
function set_alert_color(tdb, trgb)
(
  (cfg_alert == 2 || cfg_alert == 3) && tdb >= cfg_red ?
    set_color(RED_COLOR) :
  (cfg_alert == 1 || cfg_alert == 3) && tdb >= cfg_yellow ?
    set_color(YELLOW_COLOR) :
    set_color(trgb);
);
function set_text_alert_color(tdb)
(
  want_vu ? set_color(TEXT_COLOR) : set_alert_color(tdb, TEXT_COLOR);
);

function draw_grid(db hhi hlo) local(th)
(
  th=scale_db1(db)*vh;
  hlo < 0 || hhi < 0 || (th > hlo+gfx_texth*5/4 && th < hhi-gfx_texth*5/4) ? (
    gfx_line(xl,y0-th,gfx_w-xr,y0-th);
    sprintf(#num,"%+.0f",db);
    want_axis ? draw_str(#num,gfx_w-xr+gfx_texth*3/4,y0-th,4);
    th;
  ) : (
    -1;
  );
);

function draw_histogram(txlo, txhi, lasty, lasttw, y, tw)
(
  gfx_triangle(txlo-lasttw,y0-lasty,txlo-tw,y0-y,txlo,y0-lasty,txlo,y0-y);
  gfx_triangle(txhi+lasttw,y0-lasty,txhi+tw,y0-y,txhi,y0-lasty,txhi,y0-y);
  gfx_line(txlo-lasttw,y0-lasty,txlo-tw,y0-y);
  gfx_line(txhi+lasttw,y0-lasty,txhi+tw,y0-y);
);

want_vu ? (
  set_color(GRID_COLOR);

  ty0=draw_grid(0,-1,-1);
  ty12=draw_grid(-12,-1,-1);
  ty24=draw_grid(-24,-1,-1);
  ty48=draw_grid(-48,-1,-1);

  ty6=draw_grid(-6,ty0,ty12);
  ty18=draw_grid(-18,ty12,ty24);
  ty36=draw_grid(-36,ty24,ty48);
  ty60=draw_grid(-60,ty48,0);

  ty3=draw_grid(-3,ty0,ty6 >= 0 ? ty6 : ty12);
  ty30=draw_grid(-30,ty24,ty36 >= 0 ? ty36 : ty24);
  ty42=draw_grid(-42,ty36 >= 0 ? ty36 : ty24,ty48);

  set_color(TEXT_COLOR);
  gfx_line(xl,y0,gfx_w-xr,y0);
);


want_lufs_i ? (
  want_capt ? (
    set_color(TEXT_COLOR);
    draw_str(!want_abbr ? "LUFS-I" : "L-I",x,yt,5);
  );
  lufs_a_sum > 0 ? (
    lufs_i_db > -100 ? (
      db = cfg_mono ? lufs_i_db-3 : lufs_i_db;
      want_vu ? (
        th_lufs_i += (scale_db1(db)-th_lufs_i)*speed;
        th=(vh*th_lufs_i)|0;
        set_alert_color(db,LUFS_COLOR);
        gfx_rect(x-colw/4,y0-th,colw/2,th);
      );
      set_text_alert_color(db);
      draw_str(format_dbv(db),x,ylo,5);
    );
  );
  x -= colw;
);

want_lra ? (
  want_capt ? (
    set_color(TEXT_COLOR);
    draw_str("LRA",x+colw/4,yt,5);
  );
  lra_db_diff > 0 ? (
    want_vu ? (
      dblo = cfg_mono ? lra_db_lo-3 : lra_db_lo;
      dbhi = cfg_mono ? lra_db_hi-3 : lra_db_hi;
      th_lo=(scale_db1(dblo)*vh)|0;
      th_hi=(scale_db1(dbhi)*vh)|0;
      set_color(LUFS_COLOR);
      gfx_rect(x+colw*3/32,y0-th_hi,colw*5/16,rsz);
      gfx_rect(x+colw*3/32,y0-th_lo,colw*5/16,rsz);
      gfx_rect(x+colw*9/32,y0-th_hi,colw/8,th_hi-th_lo+rsz); 
    );
    set_color(TEXT_COLOR);
    // lra_db_diff is always positive and close to x.y0
    draw_str(sprintf(#,"%.1f",lra_db_diff + 0.01),x+colw/4,ylo,5);
  );
  
  x -= colw/2;
);

want_lufs_s ? (
  want_capt ? (
    set_color(TEXT_COLOR);
    draw_str(!want_abbr ? "LUFS-S" : "L-S",x,yt,5);
  );
  lufs_s_db > -100 ? (
    db = cfg_mono ? lufs_s_db-3 : lufs_s_db;
    want_vu ? (
      th_lufs_s += (scale_db1(db)-th_lufs_s)*speed;
      th=(vh*th_lufs_s)|0;
      set_alert_color(db,LUFS_COLOR);
      gfx_rect(x-colw/4,y0-th,colw/2,th);
    );
    set_text_alert_color(db);
    draw_str(format_dbv(db),x,ylo,5);
  );
  lufs_s_sum_max > 0 ? (
    db=-0.691+log(lufs_s_sum_max)*10/log(10);
    cfg_mono ? db -= 3;
    want_vu ? (
      set_alert_color(db,LUFS_COLOR);
      gfx_rect(x-colw/4,y0-scale_db1(db)*vh,colw/2,rsz);
    );
    set_text_alert_color(db);
    draw_str(format_dbv(db),x,yhi,5);
  );
  x -= colw;
);

want_lufs_m ? (
  want_capt ? (
    set_color(TEXT_COLOR);
    draw_str(!want_abbr ? "LUFS-M" : "L-M",x,yt,5);
  );
  lufs_m_db > -100 ? (
    db = cfg_mono ? lufs_m_db-3 : lufs_m_db;
    want_vu ? (
      th_lufs_m += (scale_db1(db)-th_lufs_m)*speed;
      th=(vh*th_lufs_m)|0;
      set_alert_color(db,LUFS_COLOR);
      gfx_rect(x-colw/4,y0-th,colw/2,th);
    );
    set_text_alert_color(db);
    draw_str(format_dbv(db),x,ylo,5);
  );
  lufs_m_sum_max > 0 ? (
    db=-0.691+log(lufs_m_sum_max)*10/log(10);
    cfg_mono ? db -= 3;
    want_vu ? (
      set_alert_color(db,LUFS_COLOR);
      gfx_rect(x-colw/4,y0-scale_db1(db)*vh,colw/2,rsz);
    );
    set_text_alert_color(db);
    draw_str(format_dbv(db),x,yhi,5);
  );
  cfg_lufs_m == 2 && want_vu && db_hist_max > 0 ? (
    set_color(LUFS_HIST_COLOR);
    txlo=x-colw/4-3;
    txhi=x+colw/4+2;
    maxtw=colw/4-2;
    lasty=lasttw=0;
    d=cnt=0;
    loop(75,
      db = cfg_mono ? d-70-3 : d-70;
      y=scale_db1(db-0.5)*vh;
      y > lasty ? (
        tw=(cnt/db_hist_max*maxtw)|0;
        tw > 0 || lasttw > 0 ? (
          draw_histogram(txlo,txhi,lasty,lasttw,y,tw);
        );
        lasty=y;
        lasttw=tw;
        cnt=0;
      );
      cnt += db_hist[d];
      d += 1;
    );
  );
  x -= colw;
);

want_rms_i ? (
  want_capt ? (
    set_color(TEXT_COLOR);
    draw_str(!want_abbr ? "RMS-I" : "R-I",x,yt,5);
  );
  rms_i_db > -100 ? (
    db = cfg_mono ? rms_i_db-3 : rms_i_db;
    want_vu ? (
      th_rms_i += (scale_db1(db)-th_rms_i)*speed;
      th=(vh*th_rms_i)|0;
      set_color(RMS_COLOR);
      gfx_rect(x-colw/4,y0-th,colw/2,th);
    );
    set_color(TEXT_COLOR);
    draw_str(format_dbv(db),x,ylo,5);
  );
  x -= colw;
);

want_rms_m ? (
  want_capt ? (
    set_color(TEXT_COLOR);
    draw_str(!want_abbr ? "RMS-M" : "R-M",x,yt,5);
  );
  rms_m_db > -100 ? (
    db = cfg_mono ? rms_m_db-3 : rms_m_db;
    want_vu ? (
      th_rms_m += (scale_db1(db)-th_rms_m)*speed;
      th=(vh*th_rms_m)|0;
      set_color(RMS_COLOR);
      gfx_rect(x-colw/4,y0-th,colw/2,th);
    );
    set_color(TEXT_COLOR);
    draw_str(format_dbv(db),x,ylo,5);
  );
  rms_m_db_max > -100 ? (
    db = cfg_mono ? rms_m_db_max-3 : rms_m_db_max;
    want_vu ? (
      set_color(RMS_COLOR);
      gfx_rect(x-colw/4,y0-scale_db1(db)*vh,colw/2,rsz);
    );
    set_color(TEXT_COLOR);
    draw_str(format_dbv(db),x,yhi,5);
  );
  x -= colw;
);

want_peak ? (
  want_capt ? (
    set_color(TEXT_COLOR);
    label = cfg_peak == 1 ? (!want_abbr && !cfg_mono ? "True Peak" : "TPeak") :
       cfg_peak == 2 ? (!want_abbr && !cfg_mono ? "True Peak Clips" : "TPClip") :
       cfg_peak == 4 ? (!want_abbr && !cfg_mono ? "Peak Clips" : "Clip") :
       "Peak";
    draw_str(label,x-(nch-1)*colw/2,yt,5);
  );
  ch=nch-1;
  loop(nch,
    pk[ch] > 0 ? (
      pv=pk[ch];
      want_vu ? (
        th=(vh*scale_v(pv))|0;
        set_color(PEAK_COLOR);
        gfx_rect(x-colw/4,y0-th,colw/2,th);
      );
      cfg_peak == 1 || cfg_peak == 3 ? (
        set_color(!want_vu && pv > 1 ? PEAK_CLIP_COLOR : TEXT_COLOR);
        draw_str(format_db(pv),x,ylo,5);
      ) : (
        clips=clip_cnt[ch];
        set_color(!want_vu && clips ? PEAK_CLIP_COLOR : TEXT_COLOR);
        draw_str(clips > 999 ? ">999" : sprintf(#,"%d",clips),x,ylo,5);
      );
    );
    hipk[ch] > 0 ? (
      hv=hipk[ch];
      want_vu ? (
        set_color(hv > 1 ? PEAK_CLIP_COLOR : PEAK_COLOR);
        gfx_rect(x-colw/4,y0-vh*scale_v(hv),colw/2,rsz);
      );
      set_color(!want_vu && hv > 1 ? PEAK_CLIP_COLOR : TEXT_COLOR);
      draw_str(format_db(hv),x,yhi,5)
    );
    x -= colw;
    ch -= 1;
  );
);


has_click = (mouse_cap&1) && !(last_cap&1) ? 1 : 0;
last_cap=mouse_cap;

want_axis && !(gfx_ext_flags&1) ? (
  set_color(MONO_COLOR);
  tc1=(gfx_w-gfx_texth*27/16)|0;
  tc2=(gfx_w-gfx_texth*21/16)|0;
  tr=(gfx_texth*7/16)|0;
  cfg_mono ? gfx_mode = 1;
  gfx_circle(tc1,yt,tr,cfg_mono?1:0,0);
  gfx_circle(tc2,yt,tr,cfg_mono?1:0,0);
  cfg_mono ? (
    set_color(BG_COLOR);
    gfx_x=tc1-tr; gfx_y=yt-tr+1;
    gfx_drawstr("M",1|4|256,tc2+tr,yt+tr+1);
  );
  has_click && abs(mouse_x-(tc1+tc2)/2) <= tr*3/2+1 && abs(mouse_y-yt) <= tr+1 ? (
    cfg_mono=!cfg_mono;
    slider_automate(cfg_mono);
    has_click=0;
  );
);
(want_axis || sliders_showing) && !(gfx_ext_flags&1) ? (
  set_color(TEXT_COLOR);
  tx=gfx_w-gfx_texth*3/2;
  gfx_rect(gfx_x=tx-gfx_texth/2,gfx_y=ylo-gfx_texth/2,gfx_texth+1,gfx_texth+1,0);
  gfx_drawstr("?",1|4,gfx_x+gfx_texth+1,gfx_y+gfx_texth+1);
  has_click && abs(mouse_x-tx) < gfx_texth/2 && abs(mouse_y-ylo) < gfx_texth/2 ? (
    sliders_showing = slider_show(UI_SLIDER_MASK,-1) > 0;
    has_click=0;
  );
);

has_click ? Reset();

