desc:Window function viewer
// Copyright (C) 2015-2017 Theo Niessink
// License: LGPL - http://www.gnu.org/licenses/gpl.html

slider1:0<0,30,1{Rectangle,Triangle,Parzen,Welch,Hann,Hamming,Blackman,Nuttall,Blackman-Nuttall,Blackman-Harris,SRS Flat Top,Rife-Vincent I,Rife-Vincent II,Rife-Vincent III,Cosine,Bohman,Gaussian,Confined Gaussian,Tukey,Planck-taper,Kaiser 2.0,Kaiser 3.0,Dolph-Chebyshev,Poisson 1.0,Poisson 6.9,Bartlett-Hann,Planck-Bessel,Hann-Poisson,Lanczos,Cauchy,Connes}>Window

import Tale/window.jsfx-inc

@init

function sinc(x) ( x != 0 ? sin(x)/x : 1 );

function wnd(wnd, i, n)
(
  wnd == 1 ? wnd_rect(i, n) :
  wnd == 2 ? wnd_bartlett(i, n) :
  wnd == 3 ? wnd_parzen(i, n) :
  wnd == 4 ? wnd_welch(i, n) :
  wnd == 5 ? wnd_hann(i, n) :
  wnd == 6 ? wnd_hamming(i, n) :
  wnd == 7 ? wnd_blackman(i, n) :
  wnd == 8 ? wnd_nuttall(i, n) :
  wnd == 9 ? wnd_blackman_nuttall(i, n) :
  wnd == 10 ? wnd_blackman_harris(i, n) :
  wnd == 11 ? wnd_flat_top(i, n) :
  wnd == 12 ? wnd_rife_vincent1(i, n, 2) :
  wnd == 13 ? wnd_rife_vincent2(i, n, 3, 0.001) :
  wnd == 14 ? wnd_rife_vincent3(i, n, 3) :
  wnd == 15 ? wnd_cos(i, n) :
  wnd == 16 ? wnd_bohman(i, n) :
  wnd == 17 ? wnd_gauss(i, n, 0.4) :
  wnd == 18 ? wnd_gauss_conf(i, n, 0.1) :
  wnd == 19 ? wnd_tukey(i, n, 0.5) :
  wnd == 20 ? wnd_planck_taper(i, n, 0.1) :
  wnd == 21 ? wnd_kaiser(i, n, 2) :
  wnd == 22 ? wnd_kaiser(i, n, 3) :
  wnd == 23 ? wnd_dolph_chebyshev(i, n, 5) :
  wnd == 24 ? wnd_poisson(i, n, 1) :
  wnd == 25 ? wnd_poisson(i, n, 60 / (20*log10($e))) :
  wnd == 26 ? wnd_bartlett_hann(i, n) :
  wnd == 27 ? wnd_planck_bessel(i, n, 0.1, 4.45) :
  wnd == 28 ? wnd_hann_poisson(i, n, 2) :
  wnd == 29 ? wnd_lanczos(i, n) :
  wnd == 30 ? wnd_cauchy(i, n, 3) :
  wnd == 31 ? wnd_connes(i, n, 1);
);

gfx_clear = -1;

@slider

wnd = (slider1 + 1.5)|0;

@gfx 492 248

function gfx_setrgba(rgba)
  // global(gfx_r, gfx_g, gfx_b, gfx_a)
(
  gfx_r = (rgba & 0xFF) * 1/255;
  gfx_g = (rgba & 0xFF00) * 1/65280;
  gfx_b = (rgba & 0xFF0000) * 1/16711680;
  gfx_a = (rgba & 0xFF000000) * 1/4278190080;
);

function stack_push_gfx_rgba() ( stack_push(gfx_r); stack_push(gfx_g); stack_push(gfx_b); stack_push(gfx_a) );
function stack_push_gfx_rgba(rgba) ( stack_push_gfx_rgba(); gfx_setrgba(rgba) );
function stack_push_gfx_rgba(rgba, a) ( stack_push_gfx_rgba(rgba); gfx_a *= stack_peek() );
function stack_pop_gfx_rgba() ( stack_pop(gfx_a); stack_pop(gfx_b); stack_pop(gfx_g); stack_pop(gfx_r) );

function stack_push_gfx_xy() ( stack_push(gfx_x); stack_push(gfx_y) );
function stack_pop_gfx_xy() ( stack_pop(gfx_y); stack_pop(gfx_x) );

function gfx_drawgrid(x, y, w, h, nx, ny, border, fill)
  // global(gfx_x, gfx_y, gfx_a)
  local(dx, dy)
(
  stack_push_gfx_xy();

  w -= 1;
  h -= 1;

  fill >= 0 ? (
    stack_push_gfx_rgba(fill, gfx_a);
    gfx_x = x;
    gfx_y = y;
    gfx_rectto(gfx_x + w, gfx_y + h);
    stack_pop_gfx_rgba();
  );

  nx ? (
    dx = w / nx;
    gfx_x = x;
    dx < 0 ? gfx_x += w - 1;
    loop(abs(nx) + 1,
      gfx_y = y;
      gfx_lineto(gfx_x, gfx_y + h, 0);
      gfx_x += dx;
    );
  );

  ny ? (
    dy = h / ny;
    gfx_y = y;
    dy < 0 ? gfx_y += h;
    loop(abs(ny) + 1,
      gfx_x = x;
      gfx_lineto(gfx_x + w, gfx_y, 0);
      gfx_y += dy;
    );
  );

  border >= 0 ? (
    stack_push_gfx_rgba(border, gfx_a);
    gfx_x = x;
    gfx_y = y;
    gfx_lineto(gfx_x + w, gfx_y, 0);
    gfx_lineto(gfx_x, gfx_y + h, 0);
    gfx_lineto(gfx_x - w, gfx_y, 0);
    gfx_lineto(gfx_x, gfx_y - h, 0);
    stack_pop_gfx_rgba();
  );

  stack_pop_gfx_xy();
);

function lerp(i, n, buf, len)
  local(x)
(
  x = i / (n + (n & 1) - 2) * len;
  i = x|0;
  x -= i;

  len -= 1;
  (1 - x) * buf[i & len] + x * buf[(i + 1) & len];
);

function gfx_drawwnd(buf, len, x, y, w, h, aa, fill)
  // global(gfx_x, gfx_y, gfx_a)
  local(n, i, y2)
(
  stack_push_gfx_xy();

  n = w;
  h -= 1;

  fill >= 0 ? (
    stack_push_gfx_rgba(fill, gfx_a);
    gfx_x = x;
    i = 0;
    loop(n,
      gfx_y = y + h * (1 - lerp(i, n, buf, len));
      gfx_lineto(gfx_x, y + h, 0);
      gfx_x += 1;
      i += 1;
    );
    stack_pop_gfx_rgba();
  );

  gfx_x = x;
  i = 0;
  loop(n,
    y2 = y + h * (1 - lerp(i, n, buf, len));
    i > 0 ? gfx_lineto(gfx_x + 1, y2, aa) : gfx_y = y2;
    i += 1;
  );

  stack_pop_gfx_xy();
);

function mag(buf, idx, db, amp)
  local(re, im, mag)
(
  buf += idx * 2;
  re = buf[];
  im = buf[1];

  mag = re*re + im*im;
  idx == 0 ? mag *= 0.25; // 0.5^2

  // amp <= 0 ? amp = (10^(db/20))^2
  mag > amp ? 10*log10(mag) / db : 1;
);

function lerp(i, n, buf, len, num, db, amp)
  local(x)
(
  x = i / (n + (n & 1));
  x = x < 0.5 ? num * (0.5 - x) : (len - 1) - num * (x - 0.5);

  i = x|0;
  x -= i;

  len -= 1;
  (1 - x) * mag(buf, i /* & len */, db, amp) + x * mag(buf, (i + 1) & len, db, amp);
);

function gfx_drawfft(buf, len, num, db, x, y, w, h, aa, fill)
  // global(gfx_x, gfx_y, gfx_a)
  local(amp, n, i, y2)
(
  stack_push_gfx_xy();

  amp = exp(log(100)/20 * db); // (10^(x/20))^2
  n = w;
  h -= 1;

  fill >= 0 ? (
    stack_push_gfx_rgba(fill, gfx_a);
    gfx_x = x;
    i = 0;
    loop(n,
      gfx_y = y + h * lerp(i, n, buf, len, num, db, amp);
      gfx_lineto(gfx_x, y + h, 0);
      gfx_x += 1;
      i += 1;
    );
    stack_pop_gfx_rgba();
  );

  gfx_x = x;
  i = 0;
  loop(n,
    y2 = y + h * lerp(i, n, buf, len, num, db, amp);
    i > 0 ? gfx_lineto(gfx_x + 1, y2, aa) : gfx_y = y2;
    i += 1;
  );

  stack_pop_gfx_xy();
);

wnd != old_wnd || gfx_w != old_gfx_w || gfx_h != old_gfx_h ? (
  old_wnd = wnd;
  old_gfx_w = gfx_w;
  old_gfx_h = gfx_h;

  // Clear
  gfx_setrgba(0xFFFFFFFF);
  gfx_x = gfx_y = 0;
  gfx_rectto(gfx_w, gfx_h);

  // WINDOW FUNCTION

  // Render window function.
  len = 1024;
  sum = sum2 = i = 0;
  // Periodic window.
  n = len + 1;
  loop(len,
    sum += buf[i] = wnd(wnd, i, n);
    sum2 += sqr(buf[i]);
    i += 1;
  );
  b = len * sum2 / (sum*sum);

  // Grid
  gfx_setrgba(0xFF191919);
  x = 6 * gfx_texth; y = 7 * gfx_texth / 2; w = 184; h = 172;
  gfx_drawgrid(x, y, w, h, 8, -10.5, 0xFF191919, -1);

  gfx_setrgba(0x8EFFFFFF);
  gfx_x = x + 8; gfx_y = y + 8;
  gfx_rectto(x + w - (gfx_x - x), y + h - (gfx_y - y));

  // Title
  gfx_setrgba(0xFF000000);
  gfx_x = x + (w - 15 * gfx_texth) / 2; gfx_y = y0 = y - 5 * gfx_texth / 2;
  // gfx_drawstr("Window Function");
  gfx_drawchar($'W'); gfx_drawchar($'i'); gfx_drawchar($'n'); gfx_drawchar($'d'); gfx_drawchar($'o'); gfx_drawchar($'w'); gfx_drawchar($' '); gfx_drawchar($'F'); gfx_drawchar($'u'); gfx_drawchar($'n'); gfx_drawchar($'c'); gfx_drawchar($'t'); gfx_drawchar($'i'); gfx_drawchar($'o'); gfx_drawchar($'n');

  // Y-axis label.
  gfx_x = x0 = x - 5 * gfx_texth; gfx_y = y + (h - 4 * 9 * gfx_texth / 3) / 2;
  str = buf + len * 2;
  str[0] = $'a'; str[1] = $'m'; str[2] = $'p'; str[3] = $'l'; str[4] = $'i'; str[5] = $'t'; str[6] = $'u'; str[7] = $'d';  str[8] = $'e';
  i = 0;
  loop(9,
    stack_push(gfx_x); gfx_drawchar(str[i]); stack_pop(gfx_x);
    gfx_y += 4 * gfx_texth / 3;
    i += 1;
  );

  // Y-axis units.
  gfx_y = y + 8 - gfx_texth / 2;
  dy = (h - 8) * 0.1;
  i = 1.0000001;
  loop(11,
    n = i > 0.0000001 && i < 1;
    gfx_x = x - gfx_texth * (n ? 7 : 3) / 2;
    gfx_drawnumber(i, n);
    gfx_y += dy;
    i -= 0.1;
  );

  // X-axis units.
  gfx_x = x - gfx_texth / 2; gfx_y = y + h + gfx_texth / 2;
  gfx_drawchar($'0');
  gfx_x = x + w - 3 * gfx_texth / 2;
  // gfx_drawstr("N-1");
  gfx_drawchar($'N'); gfx_drawchar($'-'); gfx_drawchar($'1');

  // X-axis label.
  gfx_x = x + (w - 7 * gfx_texth) / 2; gfx_y += 3 * gfx_texth / 2;
  // gfx_drawstr("samples");
  gfx_drawchar($'s'); gfx_drawchar($'a'); gfx_drawchar($'m'); gfx_drawchar($'p'); gfx_drawchar($'l'); gfx_drawchar($'e'); gfx_drawchar($'s');

  // Noise equivalent bandwidth metric.
  gfx_setrgba(0xFF252525);
  gfx_x = x0; gfx_y += 2 * gfx_texth;
  // gfx_drawstr("B=");
  gfx_drawchar($'B'); gfx_drawchar($'=');
  gfx_drawnumber(b, 4);

  // Window function graph.
  gfx_setrgba(0xFF191919);
  gfx_drawwnd(buf, len, x, y + 8, w, h - 8, 1, 0xFF996600);

  // FOURIER TRANSFORM

  // Scale window, apply to sinc.
  scale = 2 / sum;
  i = n = len;
  loop(n,
    i -= 1;
    p = buf + i*2;
    p[] = buf[i] * scale * sinc($pi*i / n);
    p[1] = 0;
  );

  // Forward FFT.
  fft(buf, len);
  fft_permute(buf, len);

  // Grid
  gfx_setrgba(0xFF191919);
  x += w + 15 * gfx_texth / 2;
  gfx_drawgrid(x, y, w, h, 8, -13.5, 0xFF191919, -1);

  gfx_setrgba(0x8EFFFFFF);
  gfx_x = x + 8; gfx_y = y + 6;
  gfx_rectto(x + w - (gfx_x - x), y + h - (gfx_y - y));

  // Title
  gfx_setrgba(0xFF000000);
  gfx_x = x + (w - 17 * gfx_texth) / 2; gfx_y = y - 5 * gfx_texth / 2;
  // gfx_drawstr("Fourier Transform");
  gfx_drawchar($'F'); gfx_drawchar($'o'); gfx_drawchar($'u'); gfx_drawchar($'r'); gfx_drawchar($'i'); gfx_drawchar($'e'); gfx_drawchar($'r'); gfx_drawchar($' '); gfx_drawchar($'T'); gfx_drawchar($'r'); gfx_drawchar($'a'); gfx_drawchar($'n'); gfx_drawchar($'s'); gfx_drawchar($'f'); gfx_drawchar($'o'); gfx_drawchar($'r'); gfx_drawchar($'m');

  // Y-axis label.
  gfx_x = x - 11 * gfx_texth / 2; gfx_y = y + (h - 4 * 8 * gfx_texth / 3) / 2;
  // str = buf + len * 2;
  str[0] = $'d'; str[1] = $'e'; str[2] = $'c'; str[3] = $'i'; str[4] = $'b'; str[5] = $'e'; str[6] = $'l'; str[7] = $'s';
  i = 0;
  loop(8,
    stack_push(gfx_x); gfx_drawchar(str[i]); stack_pop(gfx_x);
    gfx_y += 4 * gfx_texth / 3;
    i += 1;
  );

  // Y-axis units.
  gfx_y = y + 6 - gfx_texth / 2;
  dy = (h - 6) / 13;
  i = 0;
  loop(14,
    n = i > -10 ? 1 : i > -100 ? 2 : 3;
    i < 0 ? n += 1;
    gfx_x = x - gfx_texth * (2*n + 1) / 2;
    gfx_drawnumber(i, 0);
    gfx_y += dy;
    i -= 10;
  );

  // X-axis units.
  gfx_x = x - 2 * gfx_texth; gfx_y = y + h + gfx_texth / 2;
  dx = w * 0.125;
  i = -40;
  loop(9,
    i == 0 ? gfx_x += 3 * gfx_texth / 2;
    stack_push(gfx_x); gfx_drawnumber(i, 0); stack_pop(gfx_x);
    i == 0 ? gfx_x -= gfx_texth / 2;
    gfx_x += dx;
    i += 10;
  );

  // X-axis label.
  gfx_x = x + (w - 4 * gfx_texth) / 2; gfx_y += 3 * gfx_texth / 2;
  // gfx_drawstr("bins");
  gfx_drawchar($'b'); gfx_drawchar($'i'); gfx_drawchar($'n'); gfx_drawchar($'s');

  // Fourier transform graph.
  gfx_setrgba(0xFF007DDE);
  gfx_drawfft(buf, len, 80, -130, x, y + 6, w, h - 6, 1, 0xFF007DDE);

  // Measure @gfx width/height.
  w = x + w + 2 * gfx_texth;
  h = y + h + 6 * gfx_texth;
);
