%% 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 }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% \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 }
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
% \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
}