%% Lesson 7a. Designing filters the modern way
% * Learn how to use `filterDesigner`
% * Learn how to use `fdesign`
close all; clear; clc;

%% Filter Overview
% Remember order = # of delay elements, want to minimize to meet
% some spec.
%
% There are 4 main types of filters:
% * Lowpass - passes low frequencies through
% * Highpass - passes high frequencies through
% * Bandpass - passes a specific band
% * Bandstop - stops a specific band from going through
%
% There are four primary implementations of filters that optimize
% different properties:
% * Butterworth - maxflat passband, monotonic passband & stopband
% * Chebychev I - equiripple passband, monotonic stopband
% * Chebychev II - monotonic passband, equiripple stopband
% * Elliptic - minimum order, equiripple passband & stopband
%
% In this lesson, you will be able to very clearly see these
% properties and filter types!

%% Filter design (GUI)
% Note: Ask Prof. Keene about `filterDesigner`, it'll make him
% very happy.
filterDesigner;         % then export to workspace as numerator and
                        % denominator (not SOS for now)

%% Applying a GUI-designed filter
% Note that if a filter is generated using a `filterDesigner` function,
% it will be a filter object. You can apply a filter object using the
% `filter()` function. If your filter is given as an impulse response, you
% can either use the `filter()` function or convolve the impulse response
% and the input signal.

% Load handel again
load handel;

% Get a generated filter (this should decimate a narrow frequency band
% centered at 2.5kHz, assuming a 8192Hz sampling frequency).
flt = bandstop2500;

% Perform the filter
y1 = filter(flt, y);

%% Filter design via `fdesign`
% Filter specifications
Fsample = 44100;
Apass = 1;
Astop = 80;
Fpass = 1e3;
Fstop = 1e4;

% Use the appropriate `fdesign.*` object to create the filter; in this case,
% we're designing a lowpass filter.
specs = fdesign.lowpass(Fpass, Fstop, Apass, Astop, Fsample);

% Note: the arguments to `fdesign.*` can be altered by passing in a SPEC option
% -- the following call to `fdesign.lowpass` will produce the same filter. If
% you want to do more complex stuff (like setting the 3 dB point directly or
% setting the filter order), you can change this argument. See `doc
% fdesign.lowpass` for more info.
equivalent_specs = fdesign.lowpass('Fp,Fst,Ap,Ast', ...
	Fpass, Fstop, Apass, Astop, Fsample);

% For a given filter type, there are a myriad of different ways to design it
% depending on what properties you want out of the filter (equiripple
% passband/stopband, monotonic passband/stopband, minimum-order, max-flat,
% etc.), which our lowpass filter object supports.
%
% Note: `'SystemObject'` should be set to `true` by Mathworks' recommendation
% for these calls, even though they will often work without it.
designmethods(specs, 'SystemObject', true); 

% There are also various options we can set when designing the filter. In this
% example, we'll tell it to match the passband amplitude exactly.
designoptions(specs, 'butter', SystemObject=true);

% Finally, let's make this thing!
butter_filter = design(specs, 'butter', ...
	MatchExactly='passband', SystemObject=true);

%% Examining a filter
% Filters can be visualized with `fvtool`, drawing a nice box around the
% transition zone and lines at the edge frequencies & important amplitudes:
h = fvtool(butter_filter); % note no call to `figure`!

% `measure` is another way to check:
measurements = measure(butter_filter);

% `h` is a handle to the graphics object we've created with `fvtool`, and we
% can change its properties (listed with `get(h)`) if we wish:
h.FrequencyScale = 'Log';
ax = h.Children(15); % the axes object, found by looking at `h.Children`
ax.YLim = [-60 10]; % in dB

%% Using a filter
% Filter objects have a lot of methods to them (things like `freqz`, `impz`,
% `islinphase`, `isallpass`, etc -- check `methods(butter_filter)` if you want
% the list) that are rather useful. One method they *don't* have is `filter`,
% so if you want to apply a filter to a signal, you do it like this:
load handel; % of course; loads `y` and `Fs` into the workspace
y1 = butter_filter(y); % use like a function!
sound(y, Fs);
sound(y1, Fs);

%% Plotting the result
% Obtain the FFT of the original and the filtered signal.
shifted_fft_mag = @(sig, len) fftshift(abs(fft(sig, len))) / len;
N = 2^15;
S1 = shifted_fft_mag(y, N);
S2 = shifted_fft_mag(y1, N);
F = linspace(-Fsample/2, Fsample/2, N); % not quite accurate but close

% Plot the FFTs.
figure;

subplot(2, 1, 1);
plot(F, S1);
title 'Fourier Transform of Original Audio';
xlabel 'Frequency (Hz)';
ylabel 'Magnitude';

subplot(2, 1, 2);
plot(F, S2);
title 'Fourier Transform of Filtered Audio';
xlabel 'Frequency (Hz)';
ylabel 'Magnitude';
