Matlab Demos

I was the teaching assistant for Julius Smith's digital signal processing courses, Music 320A & B in fall 2014 and winter 2015. Much of the class time was spent on some matlab demos that I had prepared. Here they are!

I have included explanations and wikipedia links for subjects that deserve a bit more explanation.

Vector Projections

This demo takes two vectors and projects one onto the other. This demo is in preparation of the DFT (Discrete Fourier Transform) derivation in which some signal is projected onto the sinusoidal basis functions. The code below has one moving vector projected onto a stationary vector. The projection is shown as well as the difference between the projection and the moving vector. The projected component is always in the direction of y, and the difference is always perpendicular to the projection.
% Vector projection
for theta = 0:0.01:2*pi % Go around the circle once
    x = 3*[cos(theta), sin(theta)]; 
    y = [1,0];
    % Project x onto y
    x_projy = dot(x,y)/dot(y,y)*y;
    hold off
    % The vector we are projecting (blue)
    plot([0 x(1)], [0 x(2)], 'b-','LineWidth',3);
    axis([-5 5 -5 5]); % Change the window size
    hold on
    % The line we are projecting onto (red)
    plot([0 y(1)], [0 y(2)], 'r--','LineWidth',3);
    
    % The removed component (x - x_projy) (black)
    plot([x(1) x_projy(1)], [x(2) x_projy(2)], 'k--'); 
    % The projected component (green)
    plot([0 x_projy(1)], [0 x_projy(2)], 'g-','LineWidth',2);
    pause(0.01)
end
        

Reconstructing a signal

Sinusoids are used to reconstruct some arbitrary signal. We start with DC and increase in frequency, filling in the spectrum. For any arbitrary signal, we see that progressively higher frequency sinusoids can be used to reconstruct. This is known as reconstruction using the Fourier series.
N = 512;     % The length of the signal
n = (0:N-1); % The indices of the signal
fs = 800;    % A sample rate
f = 1;       % A scalar for the frequency (try raising and lowering this)
x = sin(2*pi*f*0.05*n.^2/fs)+cos(2*pi*f*0.07*n.^2/fs); % Some random signal

k = (0:N-1);             % The frequency axis (bins)
omega = k/N*2-1;         % Relabel in units of rads/sample
S = exp(-j*2*pi*n'*k/N); % The sinusoids that we are projecting onto
X = x*S;                 % Multiply by the DFT matrix (the projection)

components = zeros(1,N);
X_components = zeros(1,N);
    
for this_k = 1:N/2+1
    % Split into magnitude and phase
    mag = abs(X(this_k));
    phase = angle(X(this_k));

    % Grab a positive and negative pair of frequencies
    component_contribution = mag * cos(2*pi*n*(this_k-1)/N+phase)/N;
    X_components(this_k) = X(this_k); 
    
    % DC and half the sampling rate don't have conjugate pairs
    if this_k ~= 1 && this_k ~= N/2+1
        component_contribution = component_contribution * 2; % Add more sinusoids
        X_components(N-this_k+2) = X(N-this_k+2);            % Fill in spectrum
    end
    % Add the next component into the accumulating signal
    components = components + component_contribution;
    
    % Plot the reconstruction of the time domain signal
    subplot(121)
    plot(components,'b')
    axis([0 N -2.5 2.5])
    hold on
    plot(x,'r--')
    xlabel('Samples')
    hold off
    
    % Frequency info (fill in the spectrum as the signal is reconstructed)
    subplot(122)
    plot(omega, fftshift(abs(X)),'r');
    hold on
    area(omega, fftshift(abs(X_components)));
    hold off
    xlabel('Radian Frequency')
    ylabel('Magnitude')
    title('Spectrum')
    
    pause(exp(-this_k*0.05))
    
end
        

Filtering the Messiah

Similar to the previous demo, we reconstruct Handel's Messiah using sinusoids, just a few at a time. Because we are starting with low frequencies and working our way up to higher ones, this is equivalent to low pass filtering the signal (with progressively less agressive filters). Listen to each one!
load handel %Handel's Messiah

N = length(y);
n = (0:N-1);
x = y; % Some random signal

k = (0:N-1);             % The frequency axis (bins)
omega = k/N*2-1;         % Relabel in units of rads/sample
X = fft(y);              % Perform an FFT on the signal

components = zeros(1,N);
X_components = zeros(1,N);
    
for this_k = 1:floor(N/2+1)
    % Split into magnitude and phase
    mag = abs(X(this_k));
    phase = angle(X(this_k));
    
    % Grab a positive and negative pair of frequencies
    component_contribution = mag * cos(2*pi*n*(this_k-1)/N+phase)/N;
    X_components(this_k) = X(this_k); 
    
    % DC and half the sampling rate don't have conjugate pairs
    if this_k ~= 1 && this_k ~= N/2+1
        component_contribution = component_contribution * 2; % Add more sinusoids
        X_components(N-this_k+2) = X(N-this_k+2);            % Fill in spectrum
    end
    % Add the next component into the accumulating signal
    components = components + component_contribution;
 
    %Do it in chunks so it doesn't take all day
    if (mod(this_k, round(N/10))==0)
        % Frequency info
        plot(omega, fftshift(abs(X)),'r');
        hold on
        area(omega, fftshift(abs(X_components)));
        hold off
        xlabel('Radian Frequency')
        ylabel('Magnitude')
        title('Spectrum')
        pause
        soundsc(ifft(X_components))
        
   end
    
end
        

Phase-stripping Handel

What is the importance of phase? Don't we usually look at magnitude plots? This demo zeros out the phase for the entire signal. The consequence of this is that we get impulsive behavior at time zero. The reason is becuase our reconstructing DFT cosines at every frequency are at zero phase. This is the only sample where each cosine is at a maximum at the same time. Even more interesting than that is the rest of the signal--it sounds like the entire song is happening simultaneously.
load handel %Handel's Messiah
sound(y,Fs);

N = length(y);
Y = fft(y);

% The magnitude and phase of the unprocessed spectrum
omega = (0:N-1)/N*2-1;
subplot(121)
plot(omega,abs(Y))
xlabel('Radian Frequency')
ylabel('Magnitude')
subplot(122)
plot(omega,unwrap(angle(Y)))
xlabel('Radian Frequency')
ylabel('Magnitude')
title('Spectrum')
pause

% Make a copy and zero out the phase
new_Y = abs(Y); 
new_y = ifft(new_Y);
soundsc(new_y,Fs); % Play the sound
subplot(111)
plot(ifft(new_Y))
xlabel('Samples')
ylabel('Amplitude')
pause

% Cut out the impulse and play again (rescaled)
new_y = new_y(500:end-500);
soundsc(new_y,Fs);
subplot(111)
plot(new_y)
xlabel('Samples')
ylabel('Amplitude')
        

A Graphical Convolution

This demo shows the geometric approach to a convolution. One signal slides past the other in and the amount of overlap (more accurately, the product of the signals) is computed. The overlap as well as the sliding signals are shown in the animation. How could we convert this to a correlation by only changing one line of code?
% Two signals
x = ones(1,100);
y = ones(1,100);
% Normalize the area to 1 so the signal is a good size to plot
norm_area = sum(abs(y));

% Zero padding
N = max(length(x),length(y))+300;
x = [x, zeros(1, N - length(x))];
y = [y, zeros(1, N - length(y))];

x_conv_y = zeros(1,N);

for i = 1:N
   accumulate = 0;
    y_shift = zeros(1,N);
    % Perform the convolution
    for j = 1:N
        if i-j1
            y_shift(j) = y(i-j);
            accumulate = accumulate + x(j)*y_shift(j);
        end
    end
    x_conv_y(i) = accumulate;
    
    area(x.*y_shift,'FaceColor',[1 1 0])
    hold on
    plot(x)
    % For speed, comment the legend out
    legend('overlapping area','x','y','conv(x,y)')
    plot(y_shift,'g')
    plot(x_conv_y/norm_area,'r')
    axis([0 N -1 2])
    hold off
    pause(0.001)
end
 
Filtering a real signal:
clear
clc
x = wavread('soundclip.wav');
x = x(1:5*44100,1);
Fs = 44100;
N = 100; % Change this
f = ones(1,N)/N; % Our filter
y = conv(x,f); % An easy convolution

sound(y,Fs);

omega = linspace(-1,1,8192);
plot(omega', abs(fftshift(fft(f,8192))));
        

Profiling the FFT

The time domain convolution of two length N signals can be computed using N^2 multiplication operations. What if we did an FFT? Just how much faster is the Fast Fourier Fransform?
All_N = 2.^(1:15);
t_conv = zeros(length(All_N),1); % Arrays to store the times 
t_fft  = zeros(length(All_N),1); 

count = 1;

for N = All_N
    N
    x = rand(1,N);
    y = rand(1,N);
    
    t = tic; % Start the timer
    % Convolution
    for i = 1:100
        conv(x,y); % An O(n^2), time domain convolution
    end
    t_conv(count) = toc(t)/100; %Stop the timer
    
    t = tic;
    % FFT
    for i = 1:100
        ifft(fft(x).*fft(y)); % An O(nlogn), frequency domain convolution
    end
    t_fft(count) = toc(t)/100;
    
    count = count + 1
end
% Plot the time domain convolution time
plot(All_N, t_conv);
hold on
% Plot the FFT convolution time (in seconds) in green
plot(All_N, t_fft,'g');
        

A Graphical Convolution - Making a Triangle Wave

This is nearly identical to the previous example, which shows a convolution. This shows how convolving a train of square pulses with a square pulse gives a triangle wave.
% Convolve an impulse train with a square pulse
%x = mod((1:256),32)==1;
%y = ones(1,16);

% Convolve a square wave with a square pulse
x = [zeros(1,10),mod((1:255),32)<16];
y = ones(1,16)/16;

norm_area = sum(abs(y));

N = max(length(x),length(y))+30;

x = [x, zeros(1, N - length(x))];
y = [y, zeros(1, N - length(y))];

x_conv_y = zeros(1,N);

for i = 1:N
   accumulate = 0;
    y_shift = zeros(1,N);
        
    for j = 1:N
        index = i-j+2;% Fixes some off by one errors
        if index1
            y_shift(j) = y(index);
            accumulate = accumulate + x(j)*y_shift(j);
        end
    end
    x_conv_y(i) = accumulate;
    
    area(x.*y_shift,'FaceColor',[1 1 0])
    hold on
    plot(x)
    %legend('overlapping area','x','y','conv(x,y)')
    plot(y_shift,'g')
    plot(x_conv_y,'r')
    axis([0 N -1 2])
    hold off
    pause(0.001)
end
        

The Short Time Fourier Transform

This demo computes a convolution via a Short Time Fourier Transform. The current signals are set to produce the same result as the previous example. There is a line of code to replace the FFT convolution with a time domain convolution. Note the extra computation time when this is done. If we don't zero pad the time domain signals before taking their FFT, we get time-aliasing (the tail of the segment wraps back around to the beginning). The reason is because we must account for the length of the filtered segment, which is longer than M. Additionally, set L = M and see what happens. This corresponds to ignoring the increased length of the signal when we accumulate the result (failing to overlap-add).
% Convolve a square wave with a square pulse to get a triangle wave
x = [zeros(10,1);mod((1:255)',32)<16];
h = ones(16,1)/16;

N = length(x); % The length of the signal x
M = length(h); % The length of the filter h

N = M*(ceil(N/M)+3); % Make the filter fit in the signal an integer number of times
x = [x; zeros(N - length(x),1)]; % Zero pad up to length N

y = zeros(N,1);

for n = 1 : M : N-M
   segment = x(n:n+M-1);

   % Take the convolution of each section
   % conv_seg = conv(segment,h);
   % FFTs are faster!
   conv_seg = ifft(fft(segment, 2*M-1).*fft(h,2*M-1));
   
   % Add it back into the buffer. Don't forget, the signal 
   % is longer than M now. We must not cut off the tails of 
   % the signals. (Overlap-add method)
   L = length(conv_seg);
   y(n:n+L-1) = y(n:n+L-1) + conv_seg(1:1+L-1);

end
% Look at the signal
plot(y)
        

Finding a signal using a correlation

Using a correlation, we attempt to find when an audio signal is played in the middle of a white noise signal. We do this by looking for a peak in the correlation of the noisy audio signal and the original audio signal. A peak occurs at the point where the two signals match up best. This position will be the correct position of the signal as long as the SNR doesn't get too bad. We plot the probability of recovering the signal as a function of SNR.
x_orig = wavread('dont_speak-no_doubt.wav');
x_orig = x_orig(1265000:1500000,1);
Fs = 22000;
%soundsc(x_orig,Fs) % Listen to song!

x_orig = x_orig/max(abs(x_orig)); % Normalize

% Testing amplitudes. Start at 0dB and decrease to -50dB
test_points = 10.^(-(0:3:50)/20);
data_y = zeros(length(test_points),1);

% Loop variables
j = 1;
i = 0;
% Number of successful recoveries
count = 0;
% Number of data points (for collecting stats)
tests = 100;

hold on

for factor = test_points % Try a new test point
    while (i < tests)
        % Scale down the waveform
        x = x_orig*factor;

        % Add it somewhere in a noisy signal
        L = 500000;
        r = rand(L,1);
        start = round(rand*(length(r)-length(x)));
        r(start: start + length(x) - 1) = r(start: start + length(x) - 1) + x;

        % This is the slow way
        %cross = xcorr(r,x);
        % This is the fast way
        cross = fftshift(ifft(fft(r,2*L-1).*conj(fft(x,2*L-1))));
  
        % We filter (this may not be necessary, it was done to
        % account for the finite length of the correlation, the 
        % edge effects). Don't focus too much on this.
        [b,a] = butter(4, 0.01,'high');
        cross = filtfilt(b,a,cross);
        
        % See if the peak in the autocorrelation is where we expect it
        [val, loc] = max(abs(cross));
        if loc - (length(r) - 1) == start
            % We found the signal!
            count = count + 1;
        end
        
        i=i+1;
    end
    
    i = 0;
    data_y(j) = count/tests;
    if count == 0
        break;
    end
    count = 0;
    j = j + 1
    
    
end

plot(20*log10(test_points), data_y)
xlabel('SNR (dB)')
ylabel('Recovery Probability')
        

Downsampling and Upsampling

This demo goes through some examples of upsampling and downsampling. The imaging and aliasing can be heard if the proper filtering is not done. The final examples show an ideal filter being applied to the signal. Here are some sound files that can be used.
%Choose between this:
x = wavread('TomsDiner_full.wav');
x = x(1:265000,1);
x = x/max(abs(x));
Fs = 44100;
% and this
%t= 0:1/Fs:4;                    
%fo=20;f1=20000;                 
%x=chirp(t,fo,4,f1,'linear');

%Upsampling without filtering. Sounds bad.
y = upsample(x,3);
N = length(y);
omega = linspace(-1,1,N);
plot(omega, abs(fftshift(fft(y/N))))
% Because we upsampled, the sampling rate has changed.
sound(y, Fs)
pause

% Listen. Sounds good. Why? The images are out of 
% the audible range and probably filtered before reaching 
% your speakers
sound(y, Fs*3)
pause

% Downsampling without filtering. Sounds bad. Wrong rate again.
y = downsample(x,2);
N = length(y);
omega = linspace(-1,1,N);
plot(omega, abs(fftshift(fft(y/N))))
sound(y, Fs)
pause

% Sounds bad. Downsampling caused aliasing!
sound(y, Fs/2)
pause
 

% Fix Upsampling by using a filter
K=3 % Upsampling factor
y = upsample(x,3);
N = length(y);
omega = linspace(-1,1,N);
f = (1:N);
f = (fN-N/2/K); % building an ideal filer
Y = fft(y);
max_Y = max(abs(Y));
plot(omega, fftshift(abs((Y/max_Y))))
hold on 
plot(omega, fftshift(f,'r'))
Y_filt = Y.*f;
y_up = real(ifft(Y_filt));
$ Sounds good!
sound(y, Fs*K)
pause

hold off 

% Fix Downsampling by using a filter
L=2 % Downsampling factor
N = length(x);
omega = linspace(-1,1,N);
f = (1:N);
f = (fN-N/2/L); % building an ideal filer
X = fft(x);
X_filt = X.*f;
x_filt = real(ifft(X_filt));
y = downsample(x_filt,L);

max_X = max(abs(X));
plot(omega, fftshift(abs((X/max_X))))
hold on 
plot(omega, fftshift(f),'r')
sound(y, Fs/L)
pause



% Upsample and downsample using only one filter
L=2 
K=3 % Upsampling factor
y = upsample(x,3);
N = length(y);
Y = fft(y);
f = (1:N)';
% Replace two filtering operations
  % f = (fN-N/2/K);
  % Y_filt = Y.*f;
  % y_up = real(ifft(Y_filt));
  % f = (fN-N/2/L);
  % Y_up_filt = Y_up.*f;

% with one
f = (fN-N/2/max(L,K)); % building an ideal filer

% Filter and downsample
Y_up_filt = Y.*f;
y_up_filt = real(ifft(Y_up_filt));
z = downsample(y_up_filt,L);

N = length(z);
omega = linspace(-1,1,N);
plot(omega, abs(fftshift(fft(z/N))))

sound(z, round(Fs*K/L))
        

IIR vs. FIR

Linear filters can be classified according to the length of their impulse response. If this impulse response goes to t=infinity, we call this an Infinite Impulse Response (IIR). If the impulse response truncates before time infinity, we call this a Finite Impulse Response (FIR). This demo shows that IIR filters can easily be constructed using simple recursive filters. These recursive filters have very long impulse responses, but can be calculated very efficiently. They also have much higher rolloff rates than FIR filters. By changing the value of alpha in this demo, it is shown that difference equations can be easily converted from high pass to low pass.
clear 
clc

N = 50
impulse = (1:N==1);


x = impulse;
y = zeros(1,N);
z = zeros(1,N);

alpha = -0.8
% FIR vs IIR
for i = 1:N
    if i>1
        % A simple nonrecursive and recursive filter
        y(i) = x(i) + alpha * x(i-1);
        z(i) = x(i) + alpha * z(i-1);
    else
        y(i) = x(i);
        z(i) = x(i);
    end
    
end
% Look at the impulse responses
subplot(131)
plot(y)
subplot(132)
plot(z,'r')
unstable = 0;
if (max(abs(z)>1))
    unstable = 1;
    title('WARNING: UNSTABLE')
end

% Look at the frequency responses
subplot(133)
N_pad = 1024;
Y = fft(y,N_pad);
Z = fft(z,N_pad);
omega = linspace(-1, 1, N_pad);
plot(omega, abs(fftshift(Y)/max(abs(Y))))
axis([-1 1 0 1]);
hold on
if unstable == 0
    plot(omega, abs(fftshift(Z)/max(abs(Z))), 'r')
else
    plot(omega, abs(fftshift(Z)/max(abs(Z))), 'r--')    
end
hold off
xlabel('Normalized Frequency')
ylabel('Magnitude')


        

Moving Poles & Zeros in the Z Plane

This demo shows creates poles and zeros on the unit circle and gives them some trajectory (changing their distance and/or angle to the origin as a function of time). The frequency response and the phase response are animated showing these changes. The group delay can be displayed instead of the phase response.
circle      = linspace(0,2*pi,1000);

fs = 48000;
snapshots = 150;
zero_radius = linspace(2, 2, snapshots);
zero_theta  = linspace(0, pi,snapshots);

pole_radius = linspace(0.5, 0.5, snapshots);
pole_theta  = linspace(0, pi,snapshots);

for i = 1:snapshots
    P = [];
    Z = [];
    
    % A zero and it's conjugate
    zero = zero_radius(i).*exp(1j*zero_theta(i));
    Z(end+1) = real(zero)+1j*imag(zero);
    Z(end+1) = real(zero)-1j*imag(zero);
    
    % A pole and it's conjugate
    pole = pole_radius(i).*exp(1j*pole_theta(i));
    P(end+1) = real(pole)+1j*imag(pole);
    P(end+1) = real(pole)-1j*imag(pole);
    
    
    % The unit circle
    subplot(131)
    plot(cos(circle), sin(circle), 'k-', 'LineWidth', 1)
    hold on
    plot(real(P),imag(P),'x', 'MarkerSize',12,'LineWidth',2)
    plot(real(Z),imag(Z),'o', 'MarkerSize',12,'LineWidth',2)
    axis([-2 2 -2 2])
    xlabel('real(Z)')
    ylabel('imag(Z)')
    hold off
    
    [B, A]     = zp2tf(Z',P',1);
    [H, omega] = freqz(B,A);
    
    % Plot the magnitude response
    subplot(132)
    plot(fs*omega/2/pi,20*log10(abs(H)))
    axis([0 fs/2 -30 30])
    xlabel('Radians/Sample')
    ylabel('Magnitude')
    
    % Plot the phase response
    subplot(133)
    plot(fs*omega/2/pi,unwrap(atan2(imag(H),real(H))))
    axis([0 fs/2 -10 10])
    xlabel('Radians/Sample')
    ylabel('Phase (Rads)')
    
    % Plot the group delay instead of phase response
    % subplot(133)
    % plot(fs*omega/2/pi, [-diff(unwrap(atan2(imag(H),real(H))));0]/(2*pi/length(omega)))
    % axis([0 fs/2 -10 10])
    % xlabel('Frequency')
    % ylabel('Group Delay  (samples)')

    pause(0.01)
end


        

Digitizing an Analog RC filter

A simple RC circuit can be digitized once we have obtained its continuous time transform function, (1/(1+sRC)), in this case. Using the bilinear transform, we can create a digital model of this filter. The transfer function magnitude responses are shown for the analog and digital case. The digital case shows the frequency response that is nearly identical to the analog frequencies for the passband region. In the higher frequencies, we see that the digital filter rolls off faster. This is due to the frequency warping properties of the bilinear transform.
R = 1000; % Ohms
C = 1e-6; % Farads

fs = 48000;

B = [1];
A = [R*C, 1];

subplot(121)
F = 1:24000
H_analog = freqs(B,A,2*pi*F);
loglog(F,abs(H_analog));
axis([10 20000 .00001 1.1])
title('Transfer function of a simple RC circuit (Analog)');
xlabel('Frequency')
ylabel('Magnitude Response')

% Bilinear likes descending powers of s
[b_z, a_z] = bilinear(B,A,fs);

w = linspace(0, pi, 10000);
H = freqz(b_z,a_z,w);
subplot(122)
f = w*fs/2/pi;
loglog(f,abs(H));
axis([10 20000 .00001 1.1])
title('Transfer function of a simple RC circuit (Digital)');
xlabel('Frequency')
ylabel('Magnitude Response')
        

Bessel, Butterworth, Chebyshev, and Elliptical Filter Zero/Pole Plots

This demo shows four filter types as a pole zero plot. They are shown to be arranged in arcs in the s-plane.
order = 6;

% Show the zeros and poles of the Bessel Filter    
[Z,P,k] = besselap(order);
plot(real(P),imag(P),'x', 'MarkerSize',12,'LineWidth',2)
title('S-Plane')
xlabel('Real')
ylabel('Imag')
axis([-2 2 -2 2])
hold on
plot(real(Z),imag(Z),'o', 'MarkerSize',12,'LineWidth',2)

% Show the zeros and poles of the Chebyshev Filter
[Z,P,k] = cheb1ap(order, 1); % 1dB max of passband error
plot(real(P),imag(P),'rx', 'MarkerSize',12,'LineWidth',2)
plot(real(Z),imag(Z),'ro', 'MarkerSize',12,'LineWidth',2)

% Show the zeros and poles of the Butterworth Filter
[Z,P,k] = buttap(order);
plot(real(P),imag(P),'gx', 'MarkerSize',12,'LineWidth',2)
plot(real(Z),imag(Z),'go', 'MarkerSize',12,'LineWidth',2)

% Show the zeros and poles of the Elliptical Filter
[Z,P,k] = ellipap(order,1,60); % 1dB max of passband error
plot(real(P),imag(P),'kx', 'MarkerSize',12,'LineWidth',2)
plot(real(Z),imag(Z),'ko', 'MarkerSize',12,'LineWidth',2)

grid
        

Bessel, Butterworth, Chebyshev, and Elliptical Filters: Frequency Response and Group Delay

This demo shows the advantages and disadvantages of four popular filter types. The Bessel filter exhibits a high degree of passband phase linearity at the expense of a sharp cutoff and high rolloff. The Butterworth filter is maximally flat in the pass band. The Chebyshev and Elliptical filters have very steep rolloff, but have ripple in their passband. There are a couple of commented out lines that will demonstrate an increase in order of the filters.
order = 4
% for order = 4:12

    w = linspace(.1,100,6000);
    cols = ['b','r','g','k'];
    for i = 1:4
        if i == 1
            % Bessel Filter
            [Z,P,k] = besselap(order);
        elseif i == 2
            % Chebyshev Filter
            [Z,P,k] = cheb1ap(order,1);
        elseif i == 3
            % Butterworth Filter
            [Z,P,k] = buttap(order);
        else
            % Elliptical Filter
            [Z,P,k] = ellipap(order,1,60);
        end

    [b,a] = zp2tf(Z,P,k);
    H = freqs(b,a,w);
    subplot(131)
    loglog(w,abs(H),cols(i));
    title('Magnitude Response')
    xlabel('Frequency')
    ylabel('Magnitude')
    axis([.1, 100, .000001, 10])
    hold on
legend('Bessel','Chebyshev','Butterworth','Elliptical')

    subplot(132)
    plot(w,unwrap(atan2(imag(H),real(H))),cols(i));
    title('Phase Response')
    xlabel('Frequency')
    ylabel('Phase Delay (rads)')
    axis([.1, 2, -8, 0])
    hold on

    subplot(133)
    plot(w(1:end-1),-diff(unwrap(atan2(imag(H),real(H))))/(2*pi/length(w)),cols(i));
    title('Group Delay')
    xlabel('Frequency')
    ylabel('Delay (samples)')
    axis([.1, 2, 0, 260])
    hold on

    end
    legend('Bessel','Chebyshev','Butterworth','Elliptical')

%     pause(2)
%     subplot(131);hold off
%     subplot(132);hold off
%     subplot(133);hold off
% end
        

Bilinear Transform Frequency Warping

This demo shows the transfer characteristic between analog and digital frequencies. The high frequencies in the s-plane get compressed to fit into the finite range of the z-plane
fs = 48000;
T = 1/fs;
f = 0:500000;
plot(f,f,'b-')
hold on

alpha = 2/T
plot(f, alpha/(2*pi)*atan(f/alpha*(2*pi)),'g')
plot(f, fs/2*ones(1,length(f)),'r--')

xlabel('Frequency (from the s plane)')
ylabel('Mapped Frequency')

legend('Original Frequency', 'Bilinear Mapped Frequency', 'Nyquist')
        

Butterworth Filter with Frequency Warping

Depending on whether the filter is properly pre-warped or not, we will find that we do not get the same filter once we have digitized the coefficients that we expected.
fs = 48000;
cutoff = 0.25*fs; % Hz

[B,A] = butter(10, 2*pi*cutoff,'s');
% Look at it in the s plane!
% [H,w] = freqs(B,A)
% semilogx(w,20*log10(abs(H)))
% semilogx(w,20*unwrap(phase(H)))

% No prewarping when making analog filter (no correction)
[b_z, a_z] = bilinear(B,A,fs);
w = linspace(0,pi,2048);
[H,w] = freqz(b_z,a_z,w);
plot(w/pi/2*fs,20*log10(abs(H)),'b')
hold on

% Butter digital corrects for bilinear warping
[b_z, a_z] = butter(10, cutoff/fs*2);
[H,w] = freqz(b_z,a_z,w);
plot(w/pi/2*fs,20*log10(abs(H)),'r--')

xlabel('Frequency (Hz)')
ylabel('Magnitude (dB)')
axis([10 fs/2 -100 20])

legend('Unwarped','Warped')
        

Resonant Lowpass Frequency Warping

When the filter in question has an audible resonance, it is very important that the peak get mapped from the analog plane to the digital plane properly. This demo shows the deviation in peak placement as the frequency of the peak approaches the high frequencies.
fs = 48000;

for f = linspace(100,10000,1000)
    hold off
    omega = 2*pi*f;
    Q = 10;
    B = [1]
    A = [1/omega^2 1/omega/Q 1]

    [b_z,a_z] = bilinear(B,A,fs);
    w = linspace(0,pi,2000);
    [H,w] = freqz(b_z,a_z,w);
    plot(w/pi/2*fs,20*log10(abs(H)),'b')
    hold on

    [b_z,a_z] = bilinear(B,A,fs,f);
    w = linspace(0,pi,2000);
    [H,w] = freqz(b_z,a_z,w);
    plot(w/pi/2*fs,20*log10(abs(H)),'r')

    xlabel('Frequency (Hz)')
    ylabel('Magnitude (dB)')
    axis([10 fs/2 -100 50])
    pause(0.01)
    
end