// 1st order UHJ transcoder/encoder based on
// Gerzon, Michael A. (November 1985). 
// "Ambisonics in Multichannel Broadcasting and Video". 
// Journal of the Audio Engineering Society. AES. 33 (11): 859–871.
// http://decoy.iki.fi/dsound/ambisonic/motherlode/source/11730.pdf
// Allpass based, 90 degree phase shift filters are based on:
// https://www.mikrocontroller.net/attachment/33904/hilbert_Olli_Niemitalo.pdf
// GUI is heavily based on the GONIO example that ships with Reaper!

desc: WigWare UHJ Encoder/Transcoder

//include Ambisonic Filter Functions
import WigAmbFilter.jsfx-inc

filename:0,UoD_Logo_Small.png

slider1:InType=1<0,1,1{FuMa,AmbiX}>B Format Input Type
slider2:GFXType=1<0,1,1{Ignore Loudness,React to Loudness}>PQ Meter Style
slider3:GFXGain=18.0<0,48,0.1>PQ Meter Display Gain (dB)

in_pin:AmbiX 0/FuMa W
in_pin:AmbiX 1/FuMa X
in_pin:AmbiX 2/FuMa Y
in_pin:AmbiX 3/FuMa Z
out_pin:L
out_pin:R
out_pin:T
out_pin:Q 

@init
gfx_clear=-1;
bpos=0;        //I forget what this is....probs not even used now
wx_0 = 0.0;    //wx zero degrees memory (for single sample delay)
y_0 = 0.0;     //y (and other wx!) zero degrees memory
wlev_0 = 0.0;  //for detecting sample amplitude
wlev90 = wlev0 = 0.0;    // for detecting sample amp
b = 0;         //index into samples arrays for GUI
re_sum = 0.0;  //used for PQ meter
re_dif = 0.0;  //so is this
im_dif = 0.0;  //so is this
GFXGainLin = 1.0;

//memory management bits and bobs (which is a pain in JSFX)
length_of_csps = 4; 
F1coef = 1024;                          //Coef1 array
end_of_csps = F1coef + length_of_csps;  
F2coef = end_of_csps;                   //Coef2 array
end_of_csps = F2coef + length_of_csps;
maxspls = 10000;
P_x = end_of_csps;
end_of_csps = P_x + maxspls+1;
Q_y = end_of_csps;
end_of_csps = Q_y + maxspls+1;
PQamp = end_of_csps;
end_of_csps = PQamp + maxspls+1;

//coefs for IIR 90 degree phase shift filters (F1 is 0 degrees, F2 is 90)
F1coef[0] = 0.6923878;      F1coef[1] = 0.9360654322959;F1coef[2] = 0.9882295226860;F1coef[3] = 0.9987488452737;
F2coef[0] = 0.4021921162426;F2coef[1] = 0.8561710882420;F2coef[2] = 0.9722909545651;F2coef[3] = 0.9952884791278;

//er...think these are left over from the UHJ decoder!
//spangar[0] = spangar[1] = spangar[2] = spangar[3] = 0.0;

//init 4x4 IIR filters needed for 0 and 90 degree phase shift IIRs
F1a.SOS_INIT();
F1b.SOS_INIT();
F1c.SOS_INIT();
F1d.SOS_INIT();
F2a.SOS_INIT();
F2b.SOS_INIT();
F2c.SOS_INIT();
F2d.SOS_INIT();
F3a.SOS_INIT();
F3b.SOS_INIT();
F3c.SOS_INIT();
F3d.SOS_INIT();
F4a.SOS_INIT();
F4b.SOS_INIT();
F4c.SOS_INIT();
F4d.SOS_INIT();
F5a.SOS_INIT();
F5b.SOS_INIT();
F5c.SOS_INIT();
F5d.SOS_INIT();
F6a.SOS_INIT();
F6b.SOS_INIT();
F6c.SOS_INIT();
F6d.SOS_INIT();

wx_0 = y_0 = 0.0;   //pre-zero things (think I've already done it, though)

@slider
//nothing needed in here.....
GFXGainLIN = pow(2.0,GFXGain/6);

@sample
InType==1? (   //AmbiX
W = spl0 * 0.7071;
X = spl3;
Y = spl1;
Z = spl2;) : ( //FuMa (UHJ equations assume FuMa, of course)
W = spl0;
X = spl1;
Y = spl2;
Z = spl3;);

//UHJ equations taken from Gerzon
wx0 = 0.9397*W + 0.1856*X;    //Sum at 0 degrees
wx90 = -0.3420*W + 0.5099*X;  //Diff at 90 degrees
y0 = Y;                       //Diff/T at 0 degrees
y90 = -0.1432*W + 0.6512*X;   //T at 90 degrees

wlev0 = wlev90 = W;
//Allpass 0 and +90 degree filters
	wx0 = F1a.SOS_AP(wx0,F1coef[0]);
	wx0 = F1b.SOS_AP(wx0,F1coef[1]);
	wx0 = F1c.SOS_AP(wx0,F1coef[2]);
	wx0 = F1d.SOS_AP(wx0,F1coef[3]);
	wx90 = F2a.SOS_AP(wx90,F2coef[0]);
	wx90 = F2b.SOS_AP(wx90,F2coef[1]);
	wx90 = F2c.SOS_AP(wx90,F2coef[2]);
	wx90 = F2d.SOS_AP(wx90,F2coef[3]);
	y0 = F3a.SOS_AP(y0,F1coef[0]);
	y0 = F3b.SOS_AP(y0,F1coef[1]);
	y0 = F3c.SOS_AP(y0,F1coef[2]);
	y0 = F3d.SOS_AP(y0,F1coef[3]);
	y90 = F4a.SOS_AP(y90,F2coef[0]);
	y90 = F4b.SOS_AP(y90,F2coef[1]);
	y90 = F4c.SOS_AP(y90,F2coef[2]);
	y90 = F4d.SOS_AP(y90,F2coef[3]);
	wlev0 = F5a.SOS_AP(wlev0,F1coef[0]);
	wlev0 = F5b.SOS_AP(wlev0,F1coef[1]);
	wlev0 = F5c.SOS_AP(wlev0,F1coef[2]);
	wlev0 = F5d.SOS_AP(wlev0,F1coef[3]);
	wlev90 = F6a.SOS_AP(wlev90,F2coef[0]);
	wlev90 = F6b.SOS_AP(wlev90,F2coef[1]);
	wlev90 = F6c.SOS_AP(wlev90,F2coef[2]);
	wlev90 = F6d.SOS_AP(wlev90,F2coef[3]);

sum = wx_0;              //UHJ Sum
dif = wx90 + 0.6555*y_0; //UHJ Dif
T = y90 - 0.7071*y_0;    //UHJ T
Q = 0.9772*Z;            //UHJ Q

//Me trying to figure out how to get a real-time PQ meter....this wasn't the way!
//re_sum = sum;            //used for GUI
//re_dif = 0.6555*y_0;     //used for GUI
//im_dif = wx90;           //used for GUI

//Real-time PQ Meter Calcs (which assumes AmbiX gains [W = 1], interestingly!)
W*=1.414;
re_sum = 0.9397*W + 0.1856*X;      //used for GUI
re_dif = -0.3420*W + 0.5099*X;     //used for GUI
im_dif = 0.6555*Y;                 //used for GUI

GFXType==1 ? (
	wlev = GFXGainLIN*sqrt(sqr(wlev_0)+sqr(wlev90));) : (
	wlev = 1.0;);
P_x[b] = re_dif/(re_sum);          //used for GUI
Q_y[b] = im_dif/(re_sum);          //used for GUI
PQamp[b] = wlev;
b<maxspls ? b+=1;                  //used for GUI - don't ever overrun array check

//delay by one sample
wx_0 = wx0;
y_0 = y0;
wlev_0 = wlev0;

spl0 = 0.7071*(sum+dif);      //L UHJ
spl1 = 0.7071*(sum-dif);      //R UHJ
spl2 = T;                     //T UHJ
spl3 = Q;                     //Q UHJ



@gfx 480 260

//Taken from GONIO example------------------
size = min(gfx_w,gfx_h*.95)|0;

// override drawing functions to center
gxo = gfx_w*.5 - size*.5;//axis offset x
y_off = -size*0.2;       //Axis offset y
//GFXType==1 ? (PQ_off = 1.0;) : (PQ_off = 1.25;); //offset as UHJ PQ circle's origin is down a bit...
PQ_off = 1.25;
function gfx_lineto(x,y,aa) ( gfx_x+=gxo; gfx_y+=y_off; gfx_lineto(x+gxo,y+y_off,aa); gfx_y-=y_off;gfx_x-=gxo; );
function gfx_setpixel(r,g,b) ( gfx_x+=gxo; gfx_y+=y_off;gfx_setpixel(r,g,b); gfx_y-=y_off;gfx_x-=gxo; );
function gfx_drawnumber(y,x) ( gfx_x+=gxo; gfx_y+=y_off;gfx_w<200||gfx_drawnumber(y,x); gfx_y-=y_off;gfx_x-=gxo; );
function gfx_drawchar(x) (gfx_x+=gxo; gfx_y+=y_off;gfx_w<200||gfx_drawchar(x);  gfx_y-=y_off;gfx_x-=gxo;);

sizeH = size/2;                    //half size
sizeDSqr05 = sizeH * 0.70710681;   //not used
sizeQ = sizeH/2;                   //one quarter size
size3Q = 3*sizeQ;                  //three quarters size

//------------------------------------------
gfx_r = 1.0;gfx_g =1.0;gfx_b = 1.0;
gfx_x = 3;
gfx_y = 3;
gfx_drawstr("WigWare B Format to UHJ Plugin");

gfx_x = gfx_w-130;
gfx_y = 5;
gfx_blit(0, 1.0, 0.0);   //UoD Logo

//More taken from GONIO example------------------

gfx_r=gfx_g=gfx_b=0.0; gfx_a=0.045;
gfx_x=gfx_y=0;
gfx_rectto(gfx_w,gfx_h);           //alpha blend previous draws

i = min(b,maxspls);                //find last PQ values
while (
  gfx_a=1;
  gfx_x=sizeH-Q_y[i]*sizeDSqr05;
  gfx_y=sizeH-P_x[i]*sizeDSqr05;
  gfx_setpixel(0.5*PQamp[i],1*PQamp[i],0.1*PQamp[i]);           //draw pixel
  //gfx_setpixel(0.5,1,0);           //draw pixel
  (i-=1)>0;
);
b=0;                               //reset index

//draw axis
gfx_r=gfx_g=gfx_b=0.8; gfx_a=1;

gfx_x=sizeQ; gfx_y=sizeH*PQ_off;
gfx_lineto(size3Q,sizeH*PQ_off,0);

gfx_x=sizeH; gfx_y=size3Q*1.2;
gfx_lineto(sizeH,sizeQ*1.8,0);

gfx_x = sizeQ; gfx_y = sizeH*PQ_off;
gfx_drawchar($'L');
gfx_x = size3Q; gfx_y = sizeH*PQ_off;
gfx_drawchar($'R');
gfx_x = sizeH; gfx_y = sizeQ*1.7;
gfx_drawchar($'F');
gfx_drawchar($'r');
gfx_x = sizeH; gfx_y = size3Q*1.2;
gfx_drawchar($'B');
gfx_drawchar($'a');

//----------------------------------------------


