classdef Filter < handle
    %FILTER Summary of this class goes here
    %   Detailed explanation goes here

    properties
        H
        filterType
        f_cutoff
        lowpass

        signal_length
        filtdegree
        passband_ripple
        stopband_ripple
        fs

        w
    end

    methods
        function obj = Filter(options)
            %FILTER Construct an instance of this class
            %   Detailed explanation goes here
            arguments
                options.filterType filtertypes = filtertypes.bessel_inp ;
                options.f_cutoff   = 0;
                options.fs         = 0;
                options.signal_length = 0;
                options.filtdegree = 3;
                options.passband_ripple = 0.5;
                options.stopband_ripple = 0.5;
                options.lowpass    = 1;
            end


            fn = fieldnames(options);
            for n = 1:numel(fn)
                obj.(fn{n}) = options.(fn{n});
            end
            
            % *Nur* wenn die beiden gegeben wurde, kann das Filter vorab
            % erstellt werden - sonst zur Laufzeit
            if obj.fs ~= 0 && obj.signal_length ~= 0
                [obj.H,obj.w] = obj.buildFilter();
            end

        end

        function signal_out = process(obj,signal_in)
            
            if isa(signal_in,'Signal')

                % actual processing of the signal
                signal_out = signal_in;
                signal_out.signal = obj.process_(signal_in.signal);
    
                % append to logbook
                filterdesc = [num2str(obj.filtdegree),'. order ',char(obj.filterType),' filter with f_cutoff at ', num2str(obj.f_cutoff*1e-9), ' GHz.'];
                signal_out  = signal_out.logbookentry(filterdesc);

            elseif isa(signal_in,"double")

                signal_out = obj.process_(signal_in);
            
            end

        end

        function yout = process_(obj,xin)
            
            obj.signal_length = length(xin);
            
            if isempty(obj.H) || obj.signal_length ~= length(obj.H)
                [obj.H,obj.w] = obj.buildFilter();
            end
            
            yout = obj.applyFilter(xin);
            
        end 
            
        function y_filtered = applyFilter(obj,xin)
            
            y_filtered = ifft(obj.H.*fft(xin));
            
        end
            
        function [H,w] = buildFilter(obj)
            w = [];
            rp = obj.passband_ripple; %passband ripple
            rs = obj.stopband_ripple; %stopband ripple

            switch obj.filterType
                case 1

                    % Bessel filter, impulse invariant transformed

                    [B, A] = besself(obj.filtdegree, 2*pi*obj.f_cutoff);
                    [B ,A] = impinvar(B,A,obj.fs);

                case 2

                    % Bessel filter, impulse bilinear transformed

                    [Z, P, K] = besself(obj.filtdegree, 2*pi*obj.f_cutoff);
                    [Z ,P, K] = bilinear(Z,P,K,obj.fs);
                    [B ,A] = zp2tf(Z ,P ,K);

                case 3

                    % Butterworth filter
                    if obj.lowpass == 1 %lowpass
                        [B, A] = butter(obj.filtdegree, obj.f_cutoff/(obj.fs/2),'low');
                    else % highpass
                        [B, A] = butter(obj.filtdegree, obj.f_cutoff/(obj.fs/2),'high');
                    end

                case 4

                    % Chebyshev 1 filter

                    [B, A] = cheby1(obj.filtdegree,rp, obj.f_cutoff/(obj.fs/2));

                case 5

                    % Chebyshev 2 filter

                    [B, A] = cheby2(obj.filtdegree,rs, obj.f_cutoff/(obj.fs/2));

                case 6

                    % Elliptic filter

                    [B, A] = ellip(obj.filtdegree,rp,rs,obj.f_cutoff/(obj.fs/2));

                case 7

                    % Hamming filter

                    g=(obj.filtdegree-1)/2;
                    wc=obj.f_cutoff/(obj.fs/2);
                    B = wc*sinc(wc*(-g:g)).*hamming(obj.filtdegree)';
                    A=1;

                case 8

                    % Raised Cosine filter

                    B = firrcos(obj.filtdegree,obj.f_cutoff,df,obj.fs);
                    A=1;

                case 9

                    % Sinc filter

                    g=(obj.filtdegree-1)/2;
                    wc=obj.f_cutoff/(obj.fs/2);
                    B = wc*sinc(wc*(-g:g));
                    A=1;

                case 10

                    % Gaussian Filter
                    %check if order ist multiple of 1/2
                    blocklen=obj.signal_length;
            
                    faxis=linspace(-obj.fs/2,obj.fs/2,blocklen+1)';%generates arow vector faxis of blocklen+1 points linearly spaced between and including -fs/2 and fs/2
                    faxis=ifftshift(faxis(1:end-1));

                    H=exp(-((faxis)/(obj.f_cutoff*2)).^(2*obj.filtdegree)*log(2)*2^(2*obj.filtdegree-1));

            end

            % Build Filter from coefficients
            if obj.filterType ~= 10
                [H,w] = freqz(B, A, obj.signal_length,'whole');

            end

        end


        function show(obj)
                        obj.signal_length = 512*8;
                        [H_,~] = obj.buildFilter();
                                  
                        figure()
                        sgtitle('- Filter Visualization -','FontSize',11)
                        hold on
                        fs = obj.fs;
                        frex = linspace(-fs/2,fs/2,numel(H_)).*1e-9;

                        filt = fftshift(20*log10(abs(H_)));
                        filt_angle = fftshift(angle(H_));

                        filt_grpdly = diff(unwrap(filt_angle))./diff(frex.');
                        
                        fc = obj.f_cutoff;
                        fcut_= [-fc*1e-9;fc*1e-9];

                        [~, index] = min(abs(filt - (-3)));
                        threedB = frex(index);

                        cur_module_name = '';

                        filter_desc = char([num2str(obj.filtdegree),'th-order ',char(obj.filterType),'; f cut:',num2str(fc.*1e-9),' GHz']);
                        legend Interpreter none
                        
                        subplot(3,1,1);
                        legend Interpreter none
                        title('Magnitude')
                        hold on
                        p = plot(frex,filt,'LineWidth',1,'LineStyle','-','DisplayName',[cur_module_name,': ',filter_desc]);
                        xline([-threedB,threedB],'LineStyle','-','LineWidth',1,'HandleVisibility','off','Color',p.Color);
                        xline(fcut_,'LineStyle',':','LineWidth',1,'HandleVisibility','off','Color',p.Color);
                        yline(-3,'LineStyle',':','LineWidth',1,'HandleVisibility','off');
                        ylim([-10 0])
                        xlim([-200 200])
                        ylabel('Magnitude (dB)')
                        legend

                        subplot(3,1,2);
                        legend Interpreter none
                        title('Phase')
                        hold on
                        p = plot(frex,filt_angle,'LineWidth',1,'Color',p.Color,'LineStyle',':','DisplayName',[cur_module_name,': ',filter_desc]);
                        ylim([-pi,pi]);
                        yticks([-pi, 0, pi]);
                        yticklabels({'-$\pi$', '0', '$\pi$'});
                        ylabel('Angle (rad)');

                        subplot(3,1,3);
                        legend Interpreter none
                        title('Group Delay')
                        hold on
                        p = plot(frex(2:end),filt_grpdly,'LineWidth',1,'Color',p.Color,'LineStyle',':','DisplayName',[cur_module_name,': ',filter_desc]);
                        ylabel('Angle (rad)');
                        

                        ax = gca;

                        xlim([-200 200])
                        grid on
                        xlabel('Freq in GHz')

                        legend



        end

        function showHere(obj)
            if isempty(obj.H)
                obj.signal_length = 4096;
                [H_,~] = obj.buildFilter();
            else
                H_ = obj.H;
            end
            fs = obj.fs;
            frex = linspace(-fs/2,fs/2,numel(H_)).*1e-9;

            filt = fftshift(20*log10(abs(H_)));
            filt_angle = fftshift(angle(H_));

            filt_grpdly = diff(unwrap(filt_angle))./diff(frex.');

            fc = obj.f_cutoff;
            fcut_= [-fc*1e-9;fc*1e-9];

            [~, index] = min(abs(filt - (-3)));
            threedB = frex(index);

            [~, index] = min(abs(filt - (-6)));
            sixdB = frex(index);

            [~, index] = min(abs(filt - (-10)));
            ninedB = frex(index);

            cur_module_name = '';

            filter_desc = char([num2str(obj.filtdegree),'th-order ',char(obj.filterType),'; f cut:',num2str(fc.*1e-9),' GHz']);
            legend Interpreter none

            legend Interpreter none
            title('Magnitude')
            hold on
            p = plot(frex,filt,'LineWidth',1,'LineStyle','-','DisplayName',[cur_module_name,': ',filter_desc, '; 3/6/10 dB: ', num2str(threedB),'/ ',num2str(sixdB),'/ ',num2str(ninedB)]);

            xline([-threedB,threedB],'LineStyle','-','LineWidth',1,'HandleVisibility','off','Color',p.Color);
            xline([-sixdB,sixdB],'LineStyle','-','LineWidth',1,'HandleVisibility','off','Color',p.Color);
            xline([-ninedB,ninedB],'LineStyle','-','LineWidth',1,'HandleVisibility','off','Color',p.Color);

            xline(fcut_,'LineStyle',':','LineWidth',1,'HandleVisibility','off','Color',p.Color);
            yline([-3, -6, -9],'LineStyle',':','LineWidth',1,'HandleVisibility','off');
            
            xlim([0 fc.*2].*1e-9);
            ylim([ninedB-6, 2]);
            legend

        end
    end
end

