%% This is the package dspfunctions
%%
%% (c) Paolo Prandoni <paolo.prandoni _at_ epfl.ch>
%%
%% This program can be redistributed and/or modified under the terms
%% of the LaTeX Project Public License Distributed from CTAN archives
%% in directory macros/latex/base/lppl.txt.
%%
%% DESCRIPTION:
%%   `dspfunctions' is a companion package to dsptricks; it contains a
%%     set of postscript macros to compute the value of various DSP
%%     common functions
%%
%% v1.1, November 2023
%%

\ProvidesPackage{dspfunctions}[2023/11/08 package for signal processing graphics]

\def\dspToDeg{180 mul }

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% \dspRect{a}{b}  rect((x-a)/b)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspRect#1#2{ #1 sub abs #2 div 0.5 gt {0} {1} ifelse }

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% \dspTri{a}{b}  triangle((x-a)/b)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspTri#1#2{ #1 sub abs #2 div dup 1 gt {pop 0} {1 exch sub} ifelse }

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% \dspExpDec{a}{b}  b^(x-a)u[x-a]
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspExpDec#1#2{ #1 sub dup 0 lt {pop 0} {#2 exch exp} ifelse }


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% \dspQuad{a}{b}  quadratic((x-a)/b)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspQuad#1#2{ #1 sub abs #2 div dup 1 gt {pop 0} {dup mul 1 exch sub } ifelse }

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Porkpie hat shape (useful for spectral prototypes
% \dspPorkpie{a}{b}  phi((x-a)/b)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspPorkpie#1#2{ #1 sub #2 div dup abs 1 gt {pop 0}%
 {32.4 mul dup cos exch %
           dup 3 mul cos 2 mul exch %
           12 mul cos -0.7 mul  %
           add add 0.31 mul 0.017 add } ifelse }

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Raised cosine
% \dspRaisedCos{cutoff}{rolloff}
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspRaisedCos#1#2{abs %
 dup dup
   1 #2 sub #1 mul lt %
     {pop pop 1} %
     {1 #2 add #1 mul gt %
       {pop 0} {1 #2 sub #1 mul sub 2 #2 #1 mul mul div 3.14 mul RadtoDeg cos 1 add 0.5 mul} ifelse } %
   ifelse }

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Raised cosine, better syntax
% \dspRaisedCos{a}{b}{r} b = cutoff, r = rolloff
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspRaisedCosine#1#2#3{%
 #1 sub abs #2 div
   dup dup
     1 #3 sub lt
       {pop pop 1}
       {1 #3 add gt
         {pop 0}
         {1 #3 sub sub 2 #3 mul div 180 mul cos 1 add 0.5 mul}
         ifelse}
       ifelse }


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% \dspSinc{a}{b}  sinc((x-a)/b)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspSinc#1#2{ #1 sub #2 div dup 0 eq {pop 1} {dup 180 mul sin exch 3.1415 mul div} ifelse }

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% \dspSincN{a}{b}  (1/b)sinc((x-a)/b)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspSincN#1#2{\dspSinc{#1}{#2} #2 div }


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% \dspAudio{a}{b}  zero-DC baseband shape (e.g. an audio signal)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspAudio#1#2{%
 #1 sub #2 div
 abs
 dup 1 gt
   {pop 0}%
   {dup 0.01 le
     {pop 0}
     {dup 0.2 lt
       {-30 mul}
       {1 exch sub -8 mul}
       ifelse
       2.7 exch exp 1 add 1 exch div 0.5 sub 2 mul
     }
     ifelse}
   ifelse}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Fourier transform of a symmetric 2N+1 tap rect
% \dspSincS{a}{N}  sin((x-a)(2N+1)/2)/sin((x-a)/2)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspSincS#1#2{ #1 sub 90 mul dup #2 2 mul 1 add mul sin exch sin dup 0 eq {pop pop #2 2 mul 1 add} {div} ifelse}


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Fourier transform magnitude of a causal N tap rect
% (phase is e^{-j\frac{N-1}{2}\omega})
% \dspSincC{a}{N}  sin((x-a)(N/2))/sin((x-a)/2)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspSincC#1#2{ #1 sub 90 mul dup #2 mul sin exch sin dup 0 eq {pop pop #2} {div} ifelse}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% \dspRand  % Random number uniformly distributed over [-1 1]
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspRand{rand 2147483647 div 0.5 sub 2 mul }


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Discrete Fourier Transform of an input sequence; input is
%  the integer value of the DFT coefficient.
%
% \dspDFTRE{a_0 a_1 ... a_{N-1}}  (real part)
% \dspDFTIM{a_0 a_1 ... a_{N-1}}  (imaginary part)
% \dspDFTMAG{a_0 a_1 ... a_{N-1}} (magnitude)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspDFT#1{%
 cvi [#1] length mod         % DFT is N periodic
 360 mul [#1] length div     % w = (2k\pi)/N
 0                           % index n
 0                           % accumulator Re
 0                           % accumulator Im
 [#1]                        % data points
 {                           %  STACK:
                             % w n re im a_n
   dup                       % w n re im a_n a_n
   5 index                   % w n re im a_n a_n w
   5 index                   % w n re im a_n a_n w n
   mul dup                   % w n re im a_n a_n nw nw
   sin exch cos              % w n re im a_n a_n sin(nw) cos(nw)
   4 1 roll mul              % w n re im cos(nw) a_n (a_n)sin(nw)
   3 1 roll mul              % w n re im (a_n)sin(nw) (a_n)cos(nw)
   4 1 roll add              % w n (a_n)cos(nw) re im'
   3 1 roll add exch         % w n re' im'
   3 2 roll 1 add            % w re' im' n'
   3 1 roll                  % w n re im
 } forall
 4 2 roll pop pop            % re im
}
\def\dspDFTRE#1{\dspDFT{#1} pop }
\def\dspDFTIM#1{\dspDFT{#1} exch pop }
\def\dspDFTMAG#1{\dspDFT{#1} dup mul exch dup mul add sqrt }


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Frequency response of a (2N+1)-tap Type-I FIR computed at a given
%  normalized frequency value. Frequency response is real for Type-I
% The filter is considered zero-centered, so a_0 is the center tap
%
% \dspFIRI{a_0 a_1 ... a_{N-1}}
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspFIRI#1{%
 \dspToDeg                   % input to degrees
 0                           % index n
 0                           % accumulator A
 [#1]                        % coefficients a_n
 {
   3 index                   % x
   3 index                   % n [*** using index INCREASES stack size... so it's 3 3 rather than 3 2]
   mul cos mul               % a_n cos nx
   add                       % accumulate
   exch 1 add exch           % i++
 } forall
 3 1 roll pop pop
 2 mul                       % final value is 2A - a_0
 [#1] 0 get sub
}

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Magnitude response of a generic digital filter defined by the
%  constant-coefficient difference equation:
%   y[n] = b_0 x[n] + b_1 x[n-1] + ... + b_{N-1} x[n-N+1]
%          - a_1 y[n-1] - ... - a_{M-1} y[n-M+1]
%
% The response is computed at the given normalized frequency value
%
% \dspTFM{b_0 b_1 b_2 ... b_{M-1}}{a_0 a_1 ... a_{N-1}}
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspTFM#1#2{%
 \dspToDeg                   % input to degrees
 dup                         % save a copy for denominator
 0                           % index n
 0                           % accumulator Re
 0                           % accumulator Im
 [#1]                        % coefficients b_n
 {                           %  STACK (neglecting saved input at bottom):
                             % x n re im b_n
   dup                       % x n re im b_n b_n
   5 index                   % x n re im b_n b_n x
   5 index                   % x n re im b_n b_n x n
   mul dup                   % x n re im b_n b_n nx nx
   sin exch cos              % x n re im b_n b_n sin(nx) cos(nx)
   4 1 roll mul              % x n re im cos(nx) b_n (b_n)sin(nx)
   3 1 roll mul              % x n re im (b_n)sin(nx) (b_n)cos(nx)
   4 1 roll add              % x n (b_n)cos(nx) re im'
   3 1 roll add exch         % x n re' im'
   3 2 roll 1 add            % x re' im' n'
   3 1 roll                  % x n re im
 } forall
 4 2 roll pop pop            % re im
 dup mul exch dup mul add    % (re^2 + im^2)
 sqrt                        % mag of the numerator of transfer function
 exch                        % bring up saved input copy
 0                           % same loop for the a_n coefficients
 0
 0
 [1 #2]
 {
   dup
   5 index
   5 index
   mul dup
   sin exch cos
   4 1 roll mul
   3 1 roll mul
   4 1 roll add
   3 1 roll add exch
   3 2 roll 1 add
   3 1 roll
 } forall
 4 2 roll pop pop
 dup mul exch dup mul add
 sqrt
 div %0 eq {pop pop 0} {div} ifelse
}


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% Filter the data with a system implementing the transfer function
%   y[n] = b_0 x[n] + b_1 x[n-1] + ... + b_{N-1} x[n-N+1]
%          - a_1 y[n-1] - ... - a_{M-1} y[n-M+1]
%
% Use the setFilter macro in the setup part of the drawing command
%  and then \dspFilter{b_0 b_1 b_2 ... b_{M-1}}{a_1 ... a_{N-1}}
%
% \dspSignalOpt{\dspSetFilter{b_0 b_1 b_2 ... b_{M-1}}{a_1 ... a_{N-1}}}{x ... \dspFilter}
%
% NB: skip a_0 (assumed = 1) from the feedback part!
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
\def\dspSetFilter#1#2{%
 /b [#1] def
 /xlen b length def
 /xbuf [xlen {0} repeat] def
 /a [#2] def
 /ylen a length def
 /ybuf [ylen {0} repeat] def
}
\def\dspFilter{
 xbuf aload pop pop xbuf astore /xbuf exch def
 0
 0 1 xlen 1 sub {
   dup
   xbuf exch get
   exch
   b exch get
   mul add
 } for
 0 1 ylen 1 sub {
   dup
   ybuf exch get
   exch
   a exch get
   mul sub
 } for
 dup
 ybuf aload pop pop ybuf astore /ybuf exch def
}