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

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

REAPER_MAX_CHANNELS=64;


// sinc filter for true peak
function sinc_gen_val() global(srate) local(sincpos windowpos) instance(slice_pos) (
  windowpos = (2.0 * $pi / 16/*SINC_FILTER_SIZE*/) * slice_pos;
  sincpos = $pi * (slice_pos - 16/*SINC_FILTER_SIZE*/ * .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) global() (
  slice_pos = cs;
  o.v00 = sinc_gen_val(); o.v01 = sinc_gen_val(); o.v02 = sinc_gen_val(); o.v03 = sinc_gen_val();
  o.v04 = sinc_gen_val(); o.v05 = sinc_gen_val(); o.v06 = sinc_gen_val(); o.v07 = sinc_gen_val();
  o.v08 = sinc_gen_val(); o.v09 = sinc_gen_val(); o.v10 = sinc_gen_val(); o.v11 = sinc_gen_val();
  o.v12 = sinc_gen_val(); o.v13 = sinc_gen_val(); o.v14 = sinc_gen_val(); o.v15 = sinc_gen_val();
);

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 sinc_slice_abs(hist*) global() (
  abs(
    hist.h00 * this.v00 + hist.h01 * this.v01 + hist.h02 * this.v02 + hist.h03 * this.v03 +
    hist.h04 * this.v04 + hist.h05 * this.v05 + hist.h06 * this.v06 + hist.h07 * this.v07 +
    hist.h08 * this.v08 + hist.h09 * this.v09 + hist.h10 * this.v10 + hist.h11 * this.v11 +
    hist.h12 * this.v12 + hist.h13 * this.v13 + hist.h14 * this.v14 + hist.h15 * this.v15
  );
);

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

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;

  this.h00 = this.h01 = this.h02 = this.h03 = this.h04 = this.h05 = this.h06 = this.h07 =
  this.h08 = this.h09 = this.h10 = this.h11 = this.h12 = this.h13 = this.h14 = this.h15 = 0;
);

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
         sinc.s1.sinc_slice_abs
         sinc.s2.sinc_slice_abs
         sinc.s3.sinc_slice_abs
         lval rval win_pos global_peak)
(
  cfg_peak == 1 || cfg_peak == 2 ? (
    this.h15 = this.h14; this.h14 = this.h13; this.h13 = this.h12; this.h12 = this.h11;
    this.h11 = this.h10; this.h10 = this.h09; this.h09 = this.h08; this.h08 = this.h07;
    this.h07 = this.h06; this.h06 = this.h05; this.h05 = this.h04; this.h04 = this.h03;
    this.h03 = this.h02; this.h02 = this.h01; this.h01 = this.h00; this.h00 = lspl;
  
    pspl = max(abs(this.h08), sinc.s1.sinc_slice_abs(this));
    srate < 96000 ? pspl = max(pspl,max(sinc.s2.sinc_slice_abs(this), sinc.s3.sinc_slice_abs(this)));
  ) : 
  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()
(
  sinc.sinc_init();
  init_lufs_filters();

  alloc.top=0;
  pk=alloc(REAPER_MAX_CHANNELS);
  hipk=alloc(REAPER_MAX_CHANNELS);
  clip_cnt=alloc(REAPER_MAX_CHANNELS);

  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;

  ch0.init(0); ch1.init(1);
  num_ch > 2 ? ( ch2.init(2); ch3.init(3);
  num_ch > 4 ? ( ch4.init(4); ch5.init(5);
  num_ch > 6 ? ( ch6.init(6); ch7.init(7);
  num_ch > 8 ? ( ch8.init(8); ch9.init(9); ch10.init(10); ch11.init(11);
  num_ch > 12 ? ( ch12.init(12); ch13.init(13); ch14.init(14); ch15.init(15);
  num_ch > 16 ? ( ch16.init(16); ch17.init(17); ch18.init(18); ch19.init(19);
  num_ch > 20 ? ( ch20.init(20); ch21.init(21); ch22.init(22); ch23.init(23);
  num_ch > 24 ? ( ch24.init(24); ch25.init(25); ch26.init(26); ch27.init(27); ch28.init(28); ch29.init(29); ch30.init(30); ch31.init(31);
  num_ch > 32 ? ( ch32.init(32); ch33.init(33); ch34.init(34); ch35.init(35); ch36.init(36); ch37.init(37); ch38.init(38); ch39.init(39);
  num_ch > 40 ? ( ch40.init(40); ch41.init(41); ch42.init(42); ch43.init(43); ch44.init(44); ch45.init(45); ch46.init(46); ch47.init(47);
  num_ch > 48 ? ( ch48.init(48); ch49.init(49); ch50.init(50); ch51.init(51); ch52.init(52); ch53.init(53); ch54.init(54); ch55.init(55);
  num_ch > 56 ? ( ch56.init(56); ch57.init(57); ch58.init(58); ch59.init(59); ch60.init(60); ch61.init(61); ch62.init(62); ch63.init(63);
  ))))))))))));
);

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

ch0.decay(); ch1.decay();
num_ch > 2 ? ( ch2.decay(); ch3.decay();
num_ch > 4 ? ( ch4.decay(); ch5.decay();
num_ch > 6 ? ( ch6.decay(); ch7.decay();
num_ch > 8 ? ( ch8.decay(); ch9.decay(); ch10.decay(); ch11.decay();
num_ch > 12 ? ( ch12.decay(); ch13.decay(); ch14.decay(); ch15.decay();
num_ch > 16 ? ( ch16.decay(); ch17.decay(); ch18.decay(); ch19.decay();
num_ch > 20 ? ( ch20.decay(); ch21.decay(); ch22.decay(); ch23.decay();
num_ch > 24 ? ( ch24.decay(); ch25.decay(); ch26.decay(); ch27.decay(); ch28.decay(); ch29.decay(); ch30.decay(); ch31.decay();
num_ch > 32 ? ( ch32.decay(); ch33.decay(); ch34.decay(); ch35.decay(); ch36.decay(); ch37.decay(); ch38.decay(); ch39.decay();
num_ch > 40 ? ( ch40.decay(); ch41.decay(); ch42.decay(); ch43.decay(); ch44.decay(); ch45.decay(); ch46.decay(); ch47.decay();
num_ch > 48 ? ( ch48.decay(); ch49.decay(); ch50.decay(); ch51.decay(); ch52.decay(); ch53.decay(); ch54.decay(); ch55.decay();
num_ch > 56 ? ( ch56.decay(); ch57.decay(); ch58.decay(); ch59.decay(); ch60.decay(); ch61.decay(); ch62.decay(); ch63.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;

ch0.proc(spl0); ch1.proc(spl1);
num_ch > 2 ? ( ch2.proc(spl2); ch3.proc(spl3);
num_ch > 4 ? ( ch4.proc(spl4); ch5.proc(spl5);
num_ch > 6 ? ( ch6.proc(spl6); ch7.proc(spl7);
num_ch > 8 ? ( ch8.proc(spl8); ch9.proc(spl9); ch10.proc(spl10); ch11.proc(spl11);
num_ch > 12 ? ( ch12.proc(spl12); ch13.proc(spl13); ch14.proc(spl14); ch15.proc(spl15);
num_ch > 16 ? ( ch16.proc(spl16); ch17.proc(spl17); ch18.proc(spl18); ch19.proc(spl19);
num_ch > 20 ? ( ch20.proc(spl20); ch21.proc(spl21); ch22.proc(spl22); ch23.proc(spl23);
num_ch > 24 ? ( ch24.proc(spl24); ch25.proc(spl25); ch26.proc(spl26); ch27.proc(spl27); ch28.proc(spl28); ch29.proc(spl29); ch30.proc(spl30); ch31.proc(spl31);
num_ch > 32 ? ( ch32.proc(spl32); ch33.proc(spl33); ch34.proc(spl34); ch35.proc(spl35); ch36.proc(spl36); ch37.proc(spl37); ch38.proc(spl38); ch39.proc(spl39);
num_ch > 40 ? ( ch40.proc(spl40); ch41.proc(spl41); ch42.proc(spl42); ch43.proc(spl43); ch44.proc(spl44); ch45.proc(spl45); ch46.proc(spl46); ch47.proc(spl47);
num_ch > 48 ? ( ch48.proc(spl48); ch49.proc(spl49); ch50.proc(spl50); ch51.proc(spl51); ch52.proc(spl52); ch53.proc(spl53); ch54.proc(spl54); ch55.proc(spl55);
num_ch > 56 ? ( ch56.proc(spl56); ch57.proc(spl57); ch58.proc(spl58); ch59.proc(spl59); ch60.proc(spl60); ch61.proc(spl61); ch62.proc(spl62); ch63.proc(spl63);
))))))))))));

(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

ch0.copypks(); ch1.copypks();
num_ch > 2 ? ( ch2.copypks(); ch3.copypks();
num_ch > 4 ? ( ch4.copypks(); ch5.copypks();
num_ch > 6 ? ( ch6.copypks(); ch7.copypks();
num_ch > 8 ? ( ch8.copypks(); ch9.copypks(); ch10.copypks(); ch11.copypks();
num_ch > 12 ? ( ch12.copypks(); ch13.copypks(); ch14.copypks(); ch15.copypks();
num_ch > 16 ? ( ch16.copypks(); ch17.copypks(); ch18.copypks(); ch19.copypks();
num_ch > 20 ? ( ch20.copypks(); ch21.copypks(); ch22.copypks(); ch23.copypks();
num_ch > 24 ? ( ch24.copypks(); ch25.copypks(); ch26.copypks(); ch27.copypks(); ch28.copypks(); ch29.copypks(); ch30.copypks(); ch31.copypks();
num_ch > 32 ? ( ch32.copypks(); ch33.copypks(); ch34.copypks(); ch35.copypks(); ch36.copypks(); ch37.copypks(); ch38.copypks(); ch39.copypks();
num_ch > 40 ? ( ch40.copypks(); ch41.copypks(); ch42.copypks(); ch43.copypks(); ch44.copypks(); ch45.copypks(); ch46.copypks(); ch47.copypks();
num_ch > 48 ? ( ch48.copypks(); ch49.copypks(); ch50.copypks(); ch51.copypks(); ch52.copypks(); ch53.copypks(); ch54.copypks(); ch55.copypks();
num_ch > 56 ? ( ch56.copypks(); ch57.copypks(); ch58.copypks(); ch59.copypks(); ch60.copypks(); ch61.copypks(); ch62.copypks(); ch63.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);
  ytol=gfx_texth;
  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();

