classdef filterAnalyzer < handle
% filterAnalyzer Analyze filters with the Filter Analyzer app
%   filterAnalyzer(FILT1,...,FILTN) analyzes the magnitude response of the
%   N input filters FILT1 to FILTN. filterAnalyzer launches the Filter
%   Analyzer app, if not open already, and plots the filter responses. Each
%   subsequent call to filterAnalyzer plots the responses of the input
%   filters in a new display in the app.
%
%   filterAnalyzer supports the following filter formats:
%
%   - filterAnalyzer(...,NUM,DEN,...) analyzes the filter with coefficients
%     specified in numerator NUM and denominator DEN. NUM has P+1 columns,
%     where P is the numerator order (P>=0). DEN has Q+1 columns, where Q
%     is the denominator order (Q>=0). Both NUM and DEN must have an equal
%     number of rows, K, corresponding to the number of filter sections. The
%     k-th rows of NUM and DEN define the numerator and denominator of the
%     k-th filter transfer function in the cascade. The transfer function
%     for the k-th row with numerator NUM_k and denominator DEN_k is:
%
%                    jw                      -jw                  -jPw
%         jw  NUM_k(e)    NUM_k(1) + NUM_k(2)e + .... + NUM_k(P+1)e
%      H(e) = ---------- = -------------------------------------------
%                    jw                      -jw                  -jQw
%             DEN_k(e)    DEN_k(1) + DEN_k(2)e + .... + DEN_k(Q+1)e 
% 
%     
%     NUM or DEN can also be scalars. If NUM is a scalar, Filter Analyzer
%     assumes a cascade of all-pole IIR filters with each section having a
%     scaling gain equal to NUM. If DEN is a scalar, Filter Analyzer
%     assumes a cascade of FIR filters with each section having a scaling
%     gain of 1/DEN.
%
%     NOTE: In the special case when both NUM and DEN are vectors,
%     filterAnalyzer assumes that NUM and DEN specify the transfer function
%     of an IIR filter. In other words, NUM and DEN constitute a
%     single-section cascaded transfer function with K=1, Q = numel(NUM)-1
%     and P = numel(DEN)-1.
%
%   - filterAnalyzer(...,{NUM,DEN,G},...) analyzes the CTF filters defined
%     by the numerator matrix NUM, the denominator matrix DEN, and the
%     scaling gain G. If G is a scalar, it defines a scaling factor for the
%     overall cascade response. If G is a vector, it must have K+1
%     elements, where K is the number of sections. Each of the first K
%     elements of G defines a scaling factor for the corresponding section.
%     The last element of G is an output scaling factor.
%
%   - filterAnalyzer(D) analyzes the digital filter D. You design a digital
%     filter, D, by calling the designfilt function.
%
%   - filterAnalyzer(Hd) analyzes the filter System object Hd.
%
%   filterAnalyzer(...,FilterNames=NAMES) specifies names for each input
%   filter in the N-element string vector NAMES. The filter table in the
%   Filter Analyzer app displays the filters with the specified names.
%   Filter names in the app must be unique. If FilterNames is not
%   specified, Filter Analyzer uses the input variable names or default
%   names consisting of "Filter" followed by an underscore and a number,
%   for example, "Filter_1". If a specified name already exists, the app
%   appends an underscore and a suffix number to it.
%
%   filterAnalyzer(...,SampleRates=Fs) sets the sample rate of the input
%   filters to Fs Hz. Fs can be a scalar that applies to all the input
%   filters, or a vector with number of elements equal to the number of
%   input filters. SampleRates does not apply when one or more input
%   filters are objects with a sample rate.
%
%   filterAnalyzer(...,Arithmetic=ARITH) analyzes any filter System object
%   inputs based on the arithmetic specified in ARITH. ARITH can be set to
%   one of "double", "single", or "fixed". The Filter Analyzer app assumes
%   double precision filters when the arithmetic input is not specified and
%   the filter System objects are in an unlocked state. The arithmetic
%   setting, ARITH, applies to all the filter System objects that you input
%   in the call to filterAnalyzer.
%
%   filterAnalyzer(...,Analysis=RESP) analyzes the input filters with the
%   analysis specified in RESP. RESP can be one of "magnitude", "phase",
%   "groupdelay", "phasedelay", "magestimate", "noisepsd", "impulse",
%   "step", "polezero", "coefficients", or "info". The default is
%   "magnitude".
%
%   filterAnalyzer(...,Analysis=RESP1,OverlayAnalysis=RESP2) analyzes the
%   input filters with the analyses specified in RESP1 and RESP2 in the
%   same display. The two analyses must be either in the frequency-domain
%   or in the time-domain. OverlayAnalysis is not supported when RESP1 is
%   "polezero", "coefficients", or "info". Analysis and OverlayAnalysis do
%   not apply if you specify analysis options using the AnalysisOptions
%   parameter.
%
%   filterAnalyzer(...,AnalysisOptions=OPTS) analyzes the input filters
%   with the analysis options specified in the filter analysis options
%   object OPTS. You can construct OPTS by calling the
%   filterAnalysisOptions function. 
%
%   filterAnalyzer(...,ANALYSISOPT1=VALUE1,...ANALYSISOPTN=VALUEN) analyzes
%   the input filters with the specified analysis options. For a list of
%   available options, see filterAnalysisOptions. You can specify analysis
%   options using either name-value arguments or a filterAnalysisOptions
%   object. You cannot mix formats.
%
%   filterAnalyzer(FILENAME) opens a Filter Analyzer app session stored in
%   the MAT-file called FILENAME. If the app is open already, the app
%   session is replaced with the new opened session.
%
%   filterAnalyzer(FILENAME,"append") appends the filters stored in the
%   MAT-file called FILENAME to the current app session. If the app is not
%   open already, this syntax is equivalent to filterAnalyzer(FILENAME).
%
%   [FA,DISPNUMS] = filterAnalyzer(...) returns a handle object for the
%   Filter Analyzer app, FA, and the ID numbers, DISPNUMS, corresponding to
%   the newly added displays. When no displays are added, DISPNUMS is an
%   empty array. You can call the getFilterAnalyzerHandle function at any
%   time to get the handle FA, that you can use to call the methods listed
%   below.
%
%   filterAnalyzer methods:
%
%   addFilters            - Add new filters to Filter Analyzer app
%   replaceFilters        - Replace existing filters with new filters in Filter Analyzer app
%   deleteFilters         - Delete filters from Filter Analyzer app
%   renameFilters         - Update filter names in Filter Analyzer app
%   showFilters           - Toggle filters on displays in Filter Analyzer app
%   addDisplays           - Add new analysis displays to Filter Analyzer app
%   duplicateDisplays     - Duplicate displays in Filter Analyzer app
%   deleteDisplays        - Delete displays from Filter Analyzer app
%   setAnalysisOptions    - Set analysis options of displays in Filter Analyzer app
%   getAnalysisOptions    - Get analysis options of displays in Filter Analyzer app
%   zoom                  - Zoom into a region of interest in displays in Filter Analyzer app
%   showLegend            - Toggle legends of displays in Filter Analyzer app
%   setLegendStrings      - Define alternative legend strings for filters in Filter Analyzer app
%   clearLegendStrings    - Clear alternative legend strings for filters in Filter Analyzer app
%   showSpecificationMask - Toggle filter specification mask of displays in Filter Analyzer app
%   showUserDefinedMask   - Specify and toggle user-defined spectral mask of displays in Filter Analyzer app
%   saveSession           - Save Filter Analyzer app session
%   newSession            - Clear current Filter Analyzer app session and start a new one
%   close                 - Close Filter Analyzer app session
%
%   % EXAMPLE 1:
%      % Analyze the magnitude response of three filters with Filter Analyzer app.
%      % Set the names of the filters to LP, HP, and BP. Note that the
%      % third filter uses a vector numerator and the denominator is 1.
%      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
%      d2 = designfilt("highpassfir",StopbandFrequency=0.35,PassbandFrequency=0.45);
%      b = fir1(48,[0.35 0.65]);
%      filterAnalyzer(d1,d2,b,1,FilterNames=["LP" "HP" "BP"]);
%
%   % EXAMPLE 2:
%      % Analyze the step responses of two filters.
%      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
%      d2 = designfilt("highpassfir",StopbandFrequency=0.35,PassbandFrequency=0.45);
%      filterAnalyzer(d1,d2,Analysis="step");
%
%   % EXAMPLE 3:
%      % Analyze the magnitude and phase responses of two filters.
%      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
%      d2 = designfilt("highpassfir",StopbandFrequency=0.35,PassbandFrequency=0.45);
%      filterAnalyzer(d1,d2,Analysis="magnitude",OverlayAnalysis="phase");
%
%   % EXAMPLE 4:
%       % Analyze the two-sided magnitude response and group delay of a
%       % filter. Use 1024 frequency points and display the magnitude
%       % squared response.
%       opts = filterAnalysisOptions(Analysis="magnitude",OverlayAnalysis="groupdelay");
%       opts.FrequencyRange = "twosided";
%       opts.NFFT = 1024;
%       opts.MagnitudeMode = "squared";
%       d1 = designfilt('lowpassfir',PassbandFrequency=0.45,StopbandFrequency=0.55);
%       filterAnalyzer(d1,AnalysisOptions=opts);
%
%   % EXAMPLE 5:
%       % Add a filter, plot the magnitude response and update the analysis
%       % to have NFFT = 1024.
%       d1 = designfilt('lowpassfir',PassbandFrequency=0.45,StopbandFrequency=0.55);
%       FA = filterAnalyzer(d1);
%       setAnalysisOptions(FA,NFFT=1024)
%
%   % EXAMPLE 6:
%       % Get the handle to the already-open Filter Analyzer app.
%       d1 = designfilt('lowpassfir',PassbandFrequency=0.45,StopbandFrequency=0.55);
%       filterAnalyzer(d1);
%       FA = getFilterAnalyzerHandle();

%   Copyright 2023 The MathWorks, Inc.

properties(Access=private)
    FilterAnalyzerImpl
end

methods
    function [obj, dispNums] = filterAnalyzer(varargin)
        if ~matlab.internal.feature("FilterAnalyzer")
            error(getString(message('shared_filterapps:filterAnalyzer:filterAnalyzerFeatureControlError')))
        end
        opts = {};
        numArgs = nargin;
        if numArgs && isstruct(varargin{end})
            opts = varargin{end};
            varargin(end) = [];
            numArgs = numArgs-1;
        end

        [varargin{:}] = convertCharsToStrings(varargin{:});

        % Check if first input is a string, if so, it must be a filename
        % with a stored app session. Second input can only be the "append"
        % flag. Parse the filename and open the session.
        appendString = "";
        if numArgs && isstring(varargin{1})
            fileName = varargin{1};
            if numArgs == 2
                appendString = validatestring(varargin{2},"append",2);
            elseif numArgs > 2
                error(message('signal:filterAnalyzer:TooManyInputsForSessionLoad'))
            end
            obj = filterAnalyzer.setGetInstance([],[],obj,opts);
            dispNums = obj.FilterAnalyzerImpl.openSession(fileName,appendString);
            if nargout == 0
                clear obj;
            end
            return;
        end

        % Find all the filters and parse NV pairs.
        parsedSettings = struct;
        
        [filters,argsCell] = findFilters(varargin{:});
        if ~isempty(argsCell)
            parsedSettings = signal.internal.filteranalysis.parsers.parseFilterAnalyzerInputs(argsCell{:});
        end

        % Now that we have a FilterWithAnalysis array and a
        % filterAnalysisOptions object we can import the filters and
        % perform analysis.
        parsedSettings.VarNames = strings(1,numArgs-numel(argsCell));
        for idx = 1:numel(parsedSettings.VarNames)
            parsedSettings.VarNames(idx) = string(inputname(idx));
        end
        [filtArray,warningIDs,warningMsgs,parsedSettings] = signal.internal.filteranalysis.parsers.parseFilters(filters,parsedSettings);
        throwWarnings(warningIDs,warningMsgs);

        [obj, dispNums] = filterAnalyzer.setGetInstance(filtArray,parsedSettings,obj,opts);
        if isequal(dispNums,0)
            dispNums = [];
        end
        if nargout == 0
            clear obj;
        end
    end

    function dispNum = addFilters(obj,varargin)
        %addFilters Add new filters to Filter Analyzer app
        %   addFilters(FA,FILT1,...,FILTN) imports the N input filters,
        %   FILT1 to FILTN, to Filter Analyzer app FA and plots them in the
        %   active display. If there is no display, Filter Analyzer adds a
        %   new one and plots filters on it. For a description of supported
        %   filter formats, see the command-line help for the
        %   filterAnalyzer function.
        %
        %   addFilters(...,FilterNames=NAMES) specifies a name for each
        %   input filter in the N-element string vector NAMES. If
        %   FilterNames is not specified, Filter Analyzer uses the input
        %   variable names or default names consisting of "Filter" followed
        %   by an underscore and a number, for example, "Filter_1". Filter
        %   names in the app must be unique. If a specified name already
        %   exists, the app appends an underscore and a suffix number to
        %   it.
        %
        %   addFilters(...,DisplayNums=NUMS) adds filters and plots them in
        %   the displays with numbers in vector NUMS. If DisplayNums is not
        %   specified, the app plots the filters in the active display.
        %
        %   addFilters(...,SampleRates=FS) sets the sample rate of the
        %   input filters to FS Hz. FS can be a scalar that applies to all
        %   the input filters or a vector with a number of elements equal
        %   to the number of input filters. SampleRates does not apply when
        %   one or more input filters are objects with a sample rate.
        %
        %   addFilters(...,Arithmetic=ARITH) imports any filter System
        %   object inputs with the arithmetic specified in ARITH. ARITH can
        %   be set to one of "double", "single", or "fixed". The app
        %   assumes double-precision filters when the arithmetic input is
        %   not specified and the filter System objects are in an unlocked
        %   state. The arithmetic setting, ARITH, applies to all the input
        %   filter System objects.
        %
        %   DISPNUM = addFilters(...) returns the ID number, DISPNUM,
        %   corresponding to the newly added display. If there is already a
        %   display, the app returns an empty array.
        %
        %   % EXAMPLE:
        %      % Start Filter Analyzer app and add two lowpass filters.
        %      FA = filterAnalyzer;
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      d2 = designfilt("lowpassfir",PassbandFrequency=0.25,StopbandFrequency=0.35);
        %      addFilters(FA,d1,d2,FilterNames=["LP1" "LP2"]);

        if nargin < 2
            error(message('signal:filterAnalyzer:NotEnoughInputs'))
        end

        [varargin{:}] = convertCharsToStrings(varargin{:});

        % Find all the filters and parse NV pairs.
        parsedSettings = struct;

        [filters,argsCell] = findFilters(varargin{:});
        if ~isempty(argsCell)
            parsedSettings = signal.internal.filteranalysis.parsers.parseAddReplaceFiltersInputs("addFilters",argsCell{:});
        end
        parsedSettings.VarNames = strings(1,nargin-numel(argsCell)-1);
        for idx = 1:numel(parsedSettings.VarNames)
            parsedSettings.VarNames(idx) = string(inputname(idx+1));
        end
        [filtArray,warningIDs,warningMsgs,parsedSettings] = signal.internal.filteranalysis.parsers.parseFilters(filters,parsedSettings);
        if isempty(filtArray)
            error(message('signal:filterAnalyzer:MustSpecifyFilters'))
        end
        throwWarnings(warningIDs,warningMsgs);

        dispNum = obj.FilterAnalyzerImpl.addFilters(filtArray,parsedSettings);
        if nargout == 0
            clear dispNum;
        end
    end


    function replaceFilters(obj,varargin)
        %replaceFilters Replace existing filters with new filters in Filter Analyzer app
        %   replaceFilters(FA,FILT1,...,FILTN) replaces all the filters in
        %   the Filter Analyzer app FA with new filters specified in FILT1
        %   to FILTN. N must be equal to the total number of filters in the
        %   app. If you want to replace a subset of the filters in the app,
        %   use the FilterNames parameter. For a description of supported
        %   filter formats, see the command-line help for the
        %   filterAnalyzer function.
        %
        %   replaceFilters(FA,FILT1,...,FILTM,FilterNames=NAMES) replaces
        %   the filters whose names are listed in the M-element string
        %   vector NAMES with new M filters specified in FILT1 to FILTM.
        %
        %   replaceFilters(...,SampleRates=FS) sets the sample rates of
        %   numeric input filters to FS Hz. FS can be a scalar that applies
        %   to all the input filters or a vector with number of elements
        %   equal to the number of input filters. SampleRates does not
        %   apply when one or more input filters are objects with a sample
        %   rate.
        %
        %   replaceFilters(...,Arithmetic=ARITH) sets filter System object
        %   inputs to the arithmetic specified in ARITH. ARITH can be set
        %   to one of "double", "single", or "fixed". The Filter Analyzer
        %   app assumes double-precision when the arithmetic input is not
        %   specified and the filter System objects are in an unlocked
        %   state. The arithmetic setting, ARITH, applies to all the filter
        %   system objects that you input in the call to replaceFilters.
        %
        %   % EXAMPLE:
        %      % Start Filter Analyzer app with one lowpass filter and replace the
        %      % filter with a different lowpass filter.
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      FA = filterAnalyzer(d1,FilterNames="LP");
        %      d2 = designfilt("lowpassfir",PassbandFrequency=0.25,StopbandFrequency=0.35);
        %      replaceFilters(FA,d2,FilterNames="LP")


        if nargin < 2
            error(message('signal:filterAnalyzer:NotEnoughInputs'))
        end

        [varargin{:}] = convertCharsToStrings(varargin{:});

        % Find all the filters and parse NV pairs.
        parsedSettings = struct;

        [filters,argsCell] = findFilters(varargin{:});
        if ~isempty(argsCell)
            parsedSettings = signal.internal.filteranalysis.parsers.parseAddReplaceFiltersInputs("replaceFilters",argsCell{:});
        end

        % Now parse filters that will be used to replace existing filters
        [filtArray,warningIDs,warningMsgs] = signal.internal.filteranalysis.parsers.parseFilters(filters,parsedSettings);
        if isempty(filtArray)
            error(message('signal:filterAnalyzer:MustSpecifyFilters'))
        end
        throwWarnings(warningIDs,warningMsgs);

        obj.FilterAnalyzerImpl.replaceFilters(filtArray,parsedSettings);
    end

    function deleteFilters(obj,nvArgs)
        %deleteFilters Delete filters from Filter Analyzer app
        %   deleteFilters(FA) deletes all the filters in the Filter
        %   Analyzer app FA. If you want to delete a subset of the filters
        %   in the app, use the FilternNames parameter.
        %
        %   deleteFilters(FA,FilterNames=NAMES) deletes filters with names
        %   defined by the M-element string vector NAMES. If FilterNames is
        %   not specified, all filters in the app are deleted.
        %
        %   % EXAMPLE:
        %      % Start Filter Analyzer app with two lowpass filters, then
        %      % delete one of the filters from the app.
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      d2 = designfilt("lowpassfir",PassbandFrequency=0.25,StopbandFrequency=0.35);
        %      FA = filterAnalyzer(d1,d2,FilterNames=["LP1" "LP2"]);
        %      deleteFilters(FA,FilterNames="LP1")

        arguments
            obj % no need to check for class of obj
            nvArgs.FilterNames string {mustBeText,mustBeVector}
        end

        obj.FilterAnalyzerImpl.deleteFilters(nvArgs);
    end

    function renameFilters(obj,filtNames,newNames)
        %renameFilters Update filter names in Filter Analyzer app
        %   renameFilters(FA,FILTNAMES,NEWFILTNAMES) sets the names of
        %   filters specified in string vector FILTNAMES with new names
        %   specified in string vector NEWFILTNAMES.
        %
        %   % EXAMPLE:
        %      % Start Filter Analyzer app with two lowpass filters, then
        %      % rename the filters to "LowpassFilter1" and "LowpassFilter2".
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      d2 = designfilt("lowpassfir",PassbandFrequency=0.25,StopbandFrequency=0.35);
        %      FA = filterAnalyzer(d1,d2);
        %      renameFilters(FA,["d1" "d2"],["LP1" "LP2"])
        arguments
            obj % no need to check for class of obj
            filtNames string {mustBeText,mustBeVector}
            newNames string {mustBeText,mustBeVector}
        end

        if numel(filtNames) ~= numel(newNames)
            error(message('signal:filterAnalyzer:InvalidRenaming'))
        end

        obj.FilterAnalyzerImpl.renameFilters(filtNames,newNames);
    end

    function dispNums = addDisplays(obj,varargin)
        %addDisplays Add new analysis displays to Filter Analyzer app
        %   DISPNUM = addDisplays(FA) adds a new magnitude response
        %   analysis display to Filter Analyzer app FA and returns the ID
        %   number corresponding to this new display in DISPNUM.
        %
        %   DISPNUMS = addDisplays(FA,ADDNUM) adds ADDNUM new magnitude
        %   response analysis displays to Filter Analyzer app FA and
        %   returns the ID number corresponding to each display in
        %   DISPNUMS. ADDNUM is a scalar the specifies how many displays to
        %   add. If ADDNUM is not specified, it defaults to 1.
        %
        %   addDisplays(...,AnalysisOptions=OPTS) sets the analysis options
        %   of the new displays to the values specified in the
        %   filterAnalysisOptions object OPTS. You can construct OPTS by
        %   calling the filterAnalysisOptions function. When adding more
        %   than one display (ADDNUM > 1), you can specify one
        %   filterAnalysisOptions object that applies to all the new
        %   displays. To set different options on each display, you can
        %   specify a cell array of filterAnalysisOptions objects with
        %   ADDNUM elements.
        %
        %   addDisplays(...,ANALYSISOPT1=VALUE1,...,ANALYSISOPTN=VALUEN)
        %   sets the analysis options of the new displays to the specified
        %   values. For a list of available options, see
        %   filterAnalysisOptions. The input option values apply to all the
        %   new displays. If you want to specify different options for each
        %   display, use filterAnalysisOptions objects as shown in the
        %   previous syntax. You cannot specify options using name-value
        %   arguments and filterAnalysisOptions objects simultaneously.
        %
        %   addDisplays(...,FilterNames=NAMES) adds new displays and plots
        %   the filters with names in string vector NAMES in the new
        %   displays. If FilterNames is not specified, no filters are
        %   plotted on the new displays.
        %
        %   % EXAMPLE 1:
        %      % Set up Filter Analyzer app to have three displays to analyze
        %      % magnitude response, phase response, and group delay of filters.
        %
        %      % Start Filter Analyzer app. The default display analyzes
        %      % magnitude responses of filters.
        %      FA = filterAnalyzer;
        %
        %      % Add two displays to analyze phase response and group delay of
        %      % filters. For the group delay display, set the number of
        %      % frequency points, NFFT, to 1024.
        %      opts1 = filterAnalysisOptions("phase");
        %      opts2 = filterAnalysisOptions("groupdelay");
        %      opts2.NFFT = 1024;
        %      dispNums = addDisplays(FA,2,AnalysisOptions={opts1,opts2});
        %
        %      % Store the number of each display for future use.
        %      phaseResponseDispNum = dispNums(1);
        %      groupDelayDispNum = dispNums(2);
        %
        %      % Add a filter and plot the phase response and group delay
        %      % response on the two new displays.
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      addFilters(FA,d1,DisplayNums=[phaseResponseDispNum groupDelayDispNum]);
        %
        %   % EXAMPLE 2:
        %      % Call filterAnalyzer with two filters, add a group delay
        %      % display, and plot the filters in the new display.
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      d2 = designfilt("lowpassfir",PassbandFrequency=0.25,StopbandFrequency=0.35);
        %      FA = filterAnalyzer(d1,d2,FilterNames=["LP1" "LP2"]);
        %      addDisplays(FA,Analysis="groupdelay",FilterNames=["LP1" "LP2"]);

        [varargin{:}] = convertCharsToStrings(varargin{:});
        
        parsedSettings = struct;
        numberOfNewDisplays = 1;
        if nargin > 1
            % Parse inputs and add specified number of displays with
            % specified analysis options

            % Check if number of displays has been specified
            if isnumeric(varargin{1})
                validateattributes(varargin{1},{'numeric'},{'scalar','positive','integer'},'addDisplays','ADDNUM')
                numberOfNewDisplays = varargin{1};
                if nargin > 2
                    varargin = varargin(2:end);
                else
                    varargin = {};
                end
            end

            if ~isempty(varargin)
                parsedSettings = signal.internal.filteranalysis.parsers.parseFilterAnalyzerInputs(varargin{:});
                if isfield(parsedSettings,"SampleRates")
                    error(message('signal:filterAnalyzer:InvalidParameter',"SampleRates","addDisplays"))
                end
                if isfield(parsedSettings,"Arithmetic")
                    error(message('signal:filterAnalyzer:InvalidParameter',"Arithmetic","addDisplays"))
                end
                if isfield(parsedSettings,"AnalysisOptions") && iscell(parsedSettings.AnalysisOptions) && ...
                        numel(parsedSettings.AnalysisOptions) ~= numberOfNewDisplays
                    error(message('signal:filterAnalyzer:InvalidAnalysisOptions'))
                end
            end
        end

        dispNums = obj.FilterAnalyzerImpl.addDisplays(numberOfNewDisplays, parsedSettings);
        if nargout == 0
            clear dispNums;
        end
    end


    function dispNums = duplicateDisplays(obj,nvArgs)
        %duplicateDisplays Duplicate displays in Filter Analyzer app
        %   NEWDISPNUM = duplicateDisplays(FA) duplicates the active
        %   display in the Filter Analyzer app FA. A duplicate display has
        %   the same analysis options and the same plotted filters as the
        %   original display. NEWDISPNUM contains the display number ID of
        %   the newly generated display.
        %
        %   NEWDISPNUMS = duplicateDisplays(FA,DisplayNums=NUMS) duplicates
        %   the specified displays with numbers in vector NUMS in the
        %   Filter Analyzer app FA. If DisplayNums is not specified, the
        %   app duplicates the active display. NEWDISPNUMS is a vector
        %   containing the display numbers IDs of the newly generated
        %   displays.
        %
        %   % EXAMPLE:
        %      % Start filter analyzer with a lowpass filter and phase analysis.
        %      % Duplicate the display and update the analysis of the new display
        %      % to have NFFT = 1024.
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      FA = filterAnalyzer(d1,FilterNames="LP",Analysis="phase");
        %      newDisplayNum = duplicateDisplays(FA);
        %      setAnalysisOptions(FA,DisplayNums=newDisplayNum,NFFT=1024)
        arguments
            obj
            nvArgs.DisplayNums {mustBeNumeric,mustBeVector,mustBeInteger,mustBePositive}
        end

        dispNums = obj.FilterAnalyzerImpl.duplicateDisplays(nvArgs);
        if nargout == 0
            clear dispNums;
        end
    end

    function deleteDisplays(obj,nvArgs)
        %deleteDisplays Delete displays from Filter Analyzer app
        %   deleteDisplays(FA) deletes the active display from the Filter
        %   Analyzer app FA.
        %
        %   deleteDisplays(FA,DisplayNums=NUMS) removes the specified
        %   displays with numbers in vector NUMS from Filter Analyzer app
        %   FA. If DisplayNums is not specified, the app removes the active
        %   display.
        %
        %   % EXAMPLE:
        %      % Start Filter Analyzer and add a display to analyze group
        %      % delay. Then delete the first display from the app.
        %
        %      % Start Filter Analyzer app. The default display analyzes
        %      % magnitude responses of filters.
        %      FA = filterAnalyzer;
        %
        %      % Add one display to analyze group delay.
        %      dispNum = addDisplays(FA,Analysis="groupdelay");
        %
        %      % Delete the first display
        %      deleteDisplays(FA,DisplayNums=1)
        arguments
            obj
            nvArgs.DisplayNums {mustBeNumeric,mustBeVector,mustBeInteger,mustBePositive}
        end

        obj.FilterAnalyzerImpl.deleteDisplays(nvArgs);
    end

    function setAnalysisOptions(obj,varargin)
        %setAnalysisOptions Set analysis options of displays in Filter Analyzer app
        %   setAnalysisOptions(FA,OPTS) updates the analysis options of the
        %   active display in Filter Analyzer app FA, with the options
        %   specified in the filterAnalysisOptions object OPTS. You can
        %   construct OPTS by calling the filterAnalysisOptions function,
        %   or you can get the analysis options from a display using the
        %   getAnalysisOptions method.
        %
        %   setAnalysisOptions(...,DisplayNums=NUMS) updates the analysis
        %   options of displays with numbers in vector NUMS in Filter
        %   Analyzer app FA. If NUMS contains more than one element, OPTS
        %   can be either filterAnalysisOptions that applies to all
        %   specified displays or a cell array of options objects, with the
        %   same number of elements as NUMS, to set different options for
        %   each display. If DisplayNums is not specified, the app updates
        %   the analysis options of the active display.
        %
        %   setAnalysisOptions(...,ANALYSISOPT1=VALUE1,...,ANALYSISOPTN=VALUEN)
        %   updates the analysis options of displays to the specified
        %   option values. The input option values apply to all the
        %   specified displays. If you want to specify different options
        %   for each display, use filterAnalysisOptions objects as shown in
        %   the previous syntax. For a list of available options, see
        %   filterAnalysisOptions. You cannot specify options using
        %   name-value arguments and a filterAnalysisOptions object
        %   simultaneously.
        %
        %   % EXAMPLE:
        %      % Visualize the magnitude response of two lowpass filters.
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      d2 = designfilt("lowpassfir",PassbandFrequency=0.25,StopbandFrequency=0.35);
        %      FA = filterAnalyzer(d1,d2,FilterNames=["LP1" "LP2"]);
        %
        %      % Update the analysis options to add group delay as an
        %      % overlay analysis. Compute two-sided responses with number of
        %      % frequency points equal to 1024.
        %      opts = filterAnalysisOptions("magnitude","groupdelay");
        %      opts.FrequencyRange = "twosided";
        %      opts.NFFT = 1024;
        %      setAnalysisOptions(FA,opts)

        if nargin == 1
            error(message('signal:filterAnalyzer:NotEnoughInputs'))
        end
        opts = [];
        if iscell(varargin{1}) && ~all(cellfun(@isa,varargin{1},repmat("filterAnalysisOptions",1,numel(varargin{1}))))
            error(message('signal:filterAnalyzer:OptsNotConsistent'))
        end
        if isa(varargin{1},'filterAnalysisOptions') || (iscell(varargin{1}) && isa(varargin{1}{1},'filterAnalysisOptions'))
            opts = varargin{1};
            varargin(1) = [];
        end

        [varargin{:}] = convertCharsToStrings(varargin{:});

        [opts1,displayNums] = signal.internal.filteranalysis.parsers.parseUpdateAnalysisOptions(obj.getAnalysisOptions(),varargin{:});

        if isempty(opts)
            if isempty(opts1)
                error(message('signal:filterAnalyzer:OptsMustBeSpecified'))
            else
                opts = opts1;
            end
        else
            if isempty(displayNums) && ~isscalar(opts)
                error(message('signal:filterAnalyzer:InvalidNumberOfOptsSelectedDisplay'))
            elseif numel(displayNums) && ~isscalar(opts) && numel(opts) ~= numel(displayNums)
                error(message('signal:filterAnalyzer:InvalidNumberOfOpts'))
            end
            if ~isempty(opts1)
                error(message('signal:filterAnalyzer:SimultaneousAnalysisOptions'))
            end
        end

        obj.FilterAnalyzerImpl.setAnalysisOptions(opts,displayNums);

    end

    function opts = getAnalysisOptions(obj,nvArgs)
        %getAnalysisOptions Get analysis options of displays in Filter Analyzer app
        %   OPTS = getAnalysisOptions(FA) returns the analysis options of
        %   the active display in Filter Analyzer app FA. OPTS is a
        %   filterAnalysisOptions object with the analysis options
        %   currently being used in the display.
        %
        %   OPTS = getAnalysisOptions(...,DisplayNums=NUMS) returns the
        %   analysis options of displays with numbers in vector NUMS. If
        %   NUMS is a scalar with one display number, OPTS is a
        %   filterAnalysisOptions object with the analysis options
        %   currently being used in the display. If NUMS is a vector, OPTS
        %   is a cell array with each element containing a
        %   filterAnalysisOptions object with the analysis settings of each
        %   display. If DisplayNums is not specified, OPTS corresponds to
        %   the analysis options of the active display.
        %
        %   % EXAMPLE:
        %      % Start Filter Analyzer app, get the analysis options of the display,
        %      % and update the analysis to have a two-sided frequency range and NFFT
        %      % equal to 1024.
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      FA = filterAnalyzer(d1);
        %      opts = getAnalysisOptions(FA);
        %      opts.NFFT = 1024;
        %      opts.FrequencyRange = "twosided";
        %      setAnalysisOptions(FA,opts)
        arguments
            obj
            nvArgs.DisplayNums {mustBeNumeric,mustBeVector,mustBeInteger,mustBePositive}
        end

        opts = obj.FilterAnalyzerImpl.getAnalysisOptions(nvArgs);
    end

    function dispNum = showFilters(obj,flag,nvArgs)
        %showFilters Toggle filters on displays in Filter Analyzer app
        %   showFilters(FA,FLAG) shows all the filters in the Filter
        %   Analyzer app on the active display when FLAG is set to true. If
        %   there is no display, Filter Analyzer adds a new one and plots
        %   filters on it. If you want to plot a subset of the filters in
        %   the app, use the FilterNames parameter. If FLAG is set to
        %   false, the app hides all filters in the active display.
        %
        %   showFilters(...,FilterNames=NAMES) shows or hides filters with
        %   names in string vector NAMES when FLAG is set to true or false,
        %   respectively. If FilterNames is not specified, the app plots or
        %   hides all filters.
        %
        %   showFilters(...,DisplayNums=NUMS) shows or hides filters on the
        %   displays with numbers in vector NUMS when FLAG is set to true
        %   or false, respectively. If DisplayNums is not specified, the
        %   app plots or hides the filters in the active display.
        %
        %   DISPNUM = showFilters(...) returns the ID number, DISPNUM,
        %   corresponding to the newly added display. When no displays are
        %   added, DISPNUM is an empty array.
        %
        %   % EXAMPLE:
        %      % Set up Filter Analyzer app to have three displays to analyze
        %      % magnitude response, phase response, and group delay of filters.
        %      % Visualize the responses of two lowpass filters.
        %
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      d2 = designfilt("lowpassfir",PassbandFrequency=0.25,StopbandFrequency=0.35);
        %      FA = filterAnalyzer(d1,d2,FilterNames=["LP1" "LP2"]);
        %
        %      % Add two displays to analyze phase response and group
        %      % delay. For the group delay display, set the number of
        %      % frequency points, NFFT, to 1024.
        %      phaseDispNum = addDisplays(FA,Analysis="phase");
        %      opts = filterAnalysisOptions("groupdelay");
        %      opts.NFFT = 1024;
        %      gDelayDispNum = addDisplays(FA,AnalysisOptions=opts);
        %
        %      % Show the filters in the phase response and group delay displays.
        %      showFilters(FA,true,FilterNames=["LP1" "LP2"],DisplayNums=[phaseDispNum gDelayDispNum]);
        %
        %      % Hide the filters from the group delay response display.
        %      showFilters(FA,false,DisplayNums=gDelayDispNum);
        arguments
            obj
            flag (1,1) logical
            nvArgs.DisplayNums {mustBeNumeric,mustBeVector,mustBeInteger,mustBePositive}
            nvArgs.FilterNames string {mustBeText,mustBeVector}
        end

        dispNum = obj.FilterAnalyzerImpl.showFilters(flag,nvArgs);
        if nargout == 0
            clear dispNum;
        end
    end

    function zoom(obj,zoomType,varargin)
        %zoom Zoom into a region of interest in displays in Filter Analyzer app
        %   zoom(FA,"xy",[XMIN XMAX YMIN YMAX]) zooms into the area
        %   specified by XMIN, XMAX, YMIN, and YMAX in the active display
        %   in Filter Analyzer app FA.
        %
        %   zoom(FA,"x",[XMIN XMAX]) constrains the zoom to the x-axis.
        %
        %   zoom(FA,"y",[YMIN YMAX]) constrains the zoom to the y-axis.
        %
        %   zoom(FA,"default") restores the axes limits to the default values.
        %
        %   zoom(FA,"passband") zoom into the passband of the filters. This
        %   feature does not apply if the filters do not have design metadata.
        %
        %   zoom(...,DisplayNums=NUMS) zooms all the displays with numbers
        %   in vector NUMS. If DisplayNums is not specified, the zoom
        %   operation is performed in the active display.
        %
        %   % EXAMPLE:
        %      % Visualize the magnitude responses of two lowpass filters
        %      % and zoom in x.
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      d2 = designfilt("lowpassfir",PassbandFrequency=0.25,StopbandFrequency=0.35);
        %      FA = filterAnalyzer(d1,d2,FilterNames=["LP1" "LP2"]);
        %      zoom(FA,"x",[0.2 0.46])

        if nargin < 2
            error(message('signal:filterAnalyzer:NotEnoughInputs'))
        end
        
        [varargin{:}] = convertCharsToStrings(varargin{:});
        limits = [];
        if strcmpi(zoomType,"xy")
            if isempty(varargin) || ~isnumeric(varargin{1}) || numel(varargin{1}) ~= 4
                error(message('signal:filterAnalyzer:ZoomInvalidLimits'))
            end
            limits = varargin{1};
            varargin(1) = [];
            if limits(2) <= limits(1)
                error(message('signal:filterAnalyzer:ZoomInvalidIncreasingXLimits'))
            end
            if limits(4) <= limits(3)
                error(message('signal:filterAnalyzer:ZoomInvalidIncreasingYLimits'))
            end
        elseif any(strcmpi(zoomType,["x","y"]))
            if isempty(varargin) || ~isnumeric(varargin{1}) || numel(varargin{1}) ~= 2
                error(message('signal:filterAnalyzer:ZoomInvalidXY'))
            end
            limits = varargin{1};
            varargin(1) = [];
            if limits(2) <= limits(1)
                if strcmpi(zoomType,"x")
                    error(message('signal:filterAnalyzer:ZoomInvalidIncreasingXLimits'))
                end
                if strcmpi(zoomType,"y")
                    error(message('signal:filterAnalyzer:ZoomInvalidIncreasingYLimits'))
                end
            end
        elseif ~any(strcmpi(zoomType,["default","passband"]))
            error(message('signal:filterAnalyzer:ZoomInvalidInputs'))
        end

        obj.FilterAnalyzerImpl.zoom(zoomType,limits,varargin{:});
    end

    function showLegend(obj,flag,nvArgs)
        %showLegend Toggle the legend of displays in Filter Analyzer app
        %   showLegend(FA,FLAG) shows or hides the legend of the active
        %   display in the Filter Analyzer app FA when FLAG is set to true
        %   or false, respectively. By default, the Filter Analyzer app
        %   uses the filter names to set up the legend strings.
        %
        %   showLegend(...,DisplayNums=NUMS) shows or hides the legend of
        %   the displays with numbers in vector NUMS. If DisplayNums is not
        %   specified, the legends of the active display is toggled.
        %
        %   % EXAMPLE:
        %      % Start Filter Analyzer and add a display to analyze group
        %      % delay. Turn on the legend of the second display.
        %
        %      % Start Filter Analyzer app.
        %      FA = filterAnalyzer;
        %
        %      % Add one display to analyze group delay, add a filter, and
        %      % turn off the legend.
        %      dispNum = addDisplays(FA,Analysis="groupdelay");
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      addFilters(FA,d1,FilterNames="LP1");
        %      showLegend(FA,false,DisplayNums=dispNum)

        arguments
            obj
            flag (1,1) logical
            nvArgs.DisplayNums {mustBeNumeric,mustBeVector,mustBeInteger,mustBePositive}
        end

        obj.FilterAnalyzerImpl.showLegend(flag,nvArgs);
    end

    function setLegendStrings(obj,strs,nvArgs)
        %setLegendStrings Define alternative legend strings for filters in Filter Analyzer app
        %   setLegendStrings(FA,STRS) appends strings, STRS, to the default
        %   legend strings for all the filters plotted in the active
        %   display in Filter Analyzer app, FA. Legend strings are set to
        %   the filter names by default.
        %
        %   setLegendStrings(...,FilterNames=NAMES) appends strings to the
        %   default legend strings for filters with names in string vector
        %   NAMES. If FilterNames is not specified, strings are appended to
        %   the default legend strings of all the plotted filters in a
        %   display.
        %
        %   setLegendStrings(...,DisplayNums=NUMS) appends strings to the
        %   default legend strings of the filters plotted in the displays
        %   with numbers in vector NUMS. If DisplayNums is not specified,
        %   legend strings are appended to the default legend strings of
        %   the filters plotted in the active display.
        %
        %   % EXAMPLE:
        %      % Start Filter Analyzer with two filters and set the names
        %      % of the filters to "LowpassFilter1" and "LowpassFilter2".
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      d2 = designfilt("lowpassfir",PassbandFrequency=0.25,StopbandFrequency=0.35);
        %      FA = filterAnalyzer(d1,d2,FilterNames=["LowpassFilter1" "LowpassFilter2"]);
        %
        %      % Update the legend strings of the filters.
        %      setLegendStrings(FA,["0.45 passband" "0.25 passband"])

        arguments
            obj
            strs string {mustBeText,mustBeVector}
            nvArgs.FilterNames string {mustBeText,mustBeVector}
            nvArgs.DisplayNums {mustBeNumeric,mustBeVector,mustBeInteger,mustBePositive}
        end

        obj.FilterAnalyzerImpl.setLegendStrings(strs,nvArgs);
    end

    function clearLegendStrings(obj,nvArgs)
        %clearLegendStrings Clear alternative legend strings for filters in Filter Analyzer app
        %   clearLegendStrings(FA) clears strings that were added to the
        %   legends using the setLegendStrings method for all the filters
        %   plotted in the active display of the Filter Analyzer app, FA.
        %   Legends are set to their default values which correspond to the
        %   filter names.
        %
        %   clearLegendStrings(...,FilterNames=NAMES) clears strings that
        %   were added to the legends using the setLegendStrings method for
        %   the filters with names in string vector NAMES. If FilterNames
        %   is not specified, the legend strings of all the plotted filters
        %   in a display are set to their default values.
        %
        %   clearLegendStrings(...,DisplayNums=NUMS) clears strings that
        %   were added to the legends using the setLegendStrings method for
        %   the filters plotted in the displays with numbers in vector
        %   NUMS. If DisplayNums is not specified, the legend strings of
        %   the filters plotted in the active display are set to their
        %   default values.
        %
        %   % EXAMPLE:
        %      % Start Filter Analyzer with two filters and set the names
        %      % of the filters to "LowpassFilter1" and "LowpassFilter2".
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      d2 = designfilt("lowpassfir",PassbandFrequency=0.25,StopbandFrequency=0.35);
        %      FA = filterAnalyzer(d1,d2,FilterNames=["LowpassFilter1" "LowpassFilter2"]);
        %
        %      % Update the legend strings of the filters.
        %      setLegendStrings(FA,["0.45 passband" "0.25 passband"])
        %
        %      % Clear the appended legend strings for each filter.
        %      clearLegendStrings(FA)

        arguments
            obj
            nvArgs.FilterNames string {mustBeText,mustBeVector}
            nvArgs.DisplayNums {mustBeNumeric,mustBeVector,mustBeInteger,mustBePositive}
        end

        obj.FilterAnalyzerImpl.clearLegendStrings(nvArgs);
    end
    
    function showSpecificationMask(obj,flag,nvArgs)
        %showSpecificationMask Toggle filter specification mask of displays in Filter Analyzer app
        %   showSpecificationMask(FA,FLAG) shows or hides the filter
        %   specification mask of the active display in the Filter Analyzer
        %   app FA, depending on the logical value specified by FLAG. A
        %   specification mask is shown when there is only one filter
        %   plotted in a display, the analysis is magnitude response or
        %   magnitude response estimate, there is no overlaid analysis,
        %   and the plotted filter has design metadata.
        %
        %   showSpecificationMask(...,DisplayNums=NUMS) shows or hides the
        %   filter specification masks of the displays with numbers in
        %   vector NUMS. If DisplayNums is not specified, the specification
        %   mask of the active display is toggled.
        %
        %   % EXAMPLE:
        %      % Start Filter Analyzer and add a lowpass filter.
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      FA = filterAnalyzer(d1);
        %
        %      % Turn the specification mask off and on
        %      showSpecificationMask(FA,false)
        %      showSpecificationMask(FA,true)

        arguments
            obj
            flag (1,1) logical
            nvArgs.DisplayNums {mustBeNumeric,mustBeVector,mustBeInteger,mustBePositive}
        end

        obj.FilterAnalyzerImpl.showSpecificationMask(flag,nvArgs);
    end

    function showUserDefinedMask(obj,flag,mask,nvArgs)
        %showUserDefinedMask Specify and toggle user-defined spectral mask of displays in Filter Analyzer app
        %   showUserDefinedMask(FA,FLAG,MASK) shows or hides a user-defined
        %   spectral mask, MASK, on the active display in the Filter
        %   Analyzer app FA when FLAG is set to true or false respectively.
        %   A mask is shown only when the analysis is magnitude response or
        %   frequency response estimate and there is no overlaid analysis.
        %   When FLAG is false, MASK must be omitted. MASK must be a
        %   two-column matrix of finite numeric values. The first column
        %   contains the normalized frequency values in rad/sample and the
        %   second column contains the magnitude values of the mask in dB.
        %
        %   NOTE: If you specify mask using the showUserDefinedMask method,
        %   the user-defined settings in the app are overwritten.
        %
        %   showUserDefinedMask(...,DisplayNums=NUMS) shows or hides a user
        %   defined specification mask, MASK, on the displays with numbers
        %   in vector NUMS. If DisplayNums is not specified, the mask of
        %   the active display is toggled.
        %
        %   % EXAMPLE:
        %      % Start Filter Analyzer and add a lowpass filter.
        %      d1 = designfilt("lowpassfir",PassbandFrequency=0.45,StopbandFrequency=0.55);
        %      FA = filterAnalyzer(d1);
        %
        %      % Define a mask
        %      mask = [[0 0.45 0.55 1]' [0 0 -40 -40]'];
        %      showUserDefinedMask(FA,true,mask)
        %
        %      % Turn off the user-defined spectral mask
        %      showUserDefinedMask(FA,false)

        arguments
            obj
            flag (1,1) logical
            mask = [];
            nvArgs.DisplayNums {mustBeNumeric,mustBeVector,mustBeInteger,mustBePositive}
        end
        if flag
            if nargin > 2 && (~allfinite(mask) || width(mask) ~= 2)
                error(message('signal:filterAnalyzer:InvalidUserDefinedMask'))
            end
        elseif ~isempty(mask)
            error(message('signal:filterAnalyzer:UserDefinedMaskNotRequired'))
        end

        obj.FilterAnalyzerImpl.showUserDefinedMask(flag,mask,nvArgs);
    end

    function saveSession(obj,fileName)
        %saveSession Save the Filter Analyzer app session
        %   saveSession(FA,FILENAME) saves the Filter Analyzer app session
        %   in the MAT-file with name FILENAME.
        arguments
            obj
            fileName string {mustBeTextScalar}
        end
        
        obj.FilterAnalyzerImpl.saveSession(fileName);
    end

    function newSession(obj)
        %newSession Clear Filter Analyzer app session and start a new one
        %   newSession(FA) clears the current Filter Analyzer session of
        %   all filter data and displays and starts a new one. Save the
        %   Filter Analyzer session using saveSession before calling this
        %   function to avoid losing any work.

        arguments
            obj
        end

        obj.FilterAnalyzerImpl.newSession();
    end

    function close(obj)
        %close Close the Filter Analyzer app
        %   close(FA) closes the Filter Analyzer app.
        arguments
            obj
        end
        
        obj.FilterAnalyzerImpl.close();
    end

end % End of public methods

methods(Static, Hidden)
    function [obj, dispNums] = setGetInstance(filterArg,parsedSettings,obj,varargin)
        % Persistence of a filterAnalyzer class instance
        mlock;
        persistent filterAnalyzerInstance;
        dispNums = [];
        if nargin == 0
            % no-op. Return the persistent object
        elseif nargin == 1 && isempty(filterArg)
            % Set to empty when app is closed.
            munlock("filterAnalyzer.setGetInstance");
            filterAnalyzerInstance = filterArg;
        elseif isempty(filterAnalyzerInstance)
            % Create a new instance of filterAnalyzer when starting the
            % app.
            filterAnalyzerInstance = obj;
            filterAnalyzerInstance.FilterAnalyzerImpl = signal.internal.filteranalyzer.filterAnalyzerImpl(varargin{:});
            dispNums = filterAnalyzerInstance.FilterAnalyzerImpl.import(filterArg,parsedSettings);
        elseif nargin && ~isempty(filterArg)
            % In case there is an input passed, import filters.
            dispNums = filterAnalyzerInstance.FilterAnalyzerImpl.import(filterArg,parsedSettings);
        elseif isvalid(filterAnalyzerInstance.FilterAnalyzerImpl)
            % In all other cases, bring to front.
            filterAnalyzerInstance.FilterAnalyzerImpl.bringToFront();
        end
        obj = filterAnalyzerInstance;
    end
end
end % End of class

%--------------------------------------------------------------------------
% Helper functions
function [filters,argsCell] = findFilters(varargin)
    filterIdx = find(cellfun(@isstring,varargin),1);
    if isempty(filterIdx)
        filters = varargin;
        argsCell = {};
    else
        filters = varargin(1:filterIdx-1);
        argsCell = varargin(filterIdx:end);
    end
end

function throwWarnings(warningIDs,warningMsgs)
    for idx = 1:numel(warningIDs)
        warning(warningIDs(idx),warningMsgs(idx));
    end
end
