classdef (StrictDefaults)ShelvingEqualizer < matlab.System & audioPlugin
%ShelvingEqualizer Apply shelving filters to an audio signal.
% This object is used in the "Tunable Filtering and Visualization Using
% Audio Plugins" example.
%
% See also: audiopluginexample.VarSlopeBandpassFilter,
% audiopluginexample.ParametricEqualizerWithUDP

    %   Copyright 2015-2022 The MathWorks, Inc.
    
    %#codegen
    properties        
        LowShelfCutoff = 200 % Low Shelf Cutoff (Hz)
        LowShelfSlope = 1.5 % Low Shelf Slope Coefficient
        LowShelfGain = 0 % Low Shelf Gain (dB)
        
        HighShelfCutoff = 200 % High Shelf Cutoff (Hz)
        HighShelfSlope = 1.5 % High Shelf Slope Coefficient
        HighShelfGain = 0 % High Shelf Gain (dB)
    end
    
    properties (Constant, Hidden)
        % Define the plugin interface
        PluginInterface = audioPluginInterface( ...
            'InputChannels',2,...
            'OutputChannels',2,...
            'PluginName','Shelving Equalizer',...
            audioPluginParameter('LowShelfCutoff', ...
            'DisplayName',  'Low Shelf Cutoff', ...
            'Label',  'Hz', ...
            'Mapping', { 'log', 20, 20000}, ...
            'Style', 'rotaryknob', 'Layout', [3 1]),...
            audioPluginParameter('LowShelfGain', ...
            'DisplayName',  'Low Shelf Gain', ...
            'Mapping', { 'lin', -12, 12}, ...
            'Style', 'vslider', 'Layout', [1 1]),...
            audioPluginParameter('LowShelfSlope', ...
            'DisplayName',  'Low Shelf Slope', ...
            'Mapping', { 'lin', 0.2, 5}, ...
            'Style', 'rotaryknob', 'Layout', [5 1]),...
            audioPluginParameter('HighShelfCutoff', ...
            'DisplayName',  'High Shelf Cutoff', ...
            'Label',  'Hz', ...
            'Mapping', { 'log', 20, 20000}, ...
            'Style', 'rotaryknob', 'Layout', [3 2]),...
            audioPluginParameter('HighShelfGain', ...
            'DisplayName',  'High Shelf Gain', ...
            'Mapping', { 'lin', -12, 12}, ...
            'Style', 'vslider', 'Layout', [1 2]),...
            audioPluginParameter('HighShelfSlope', ...
            'DisplayName',  'High Shelf Slope', ...            
            'Mapping', { 'lin', 0.2, 5}, ...
            'Style', 'rotaryknob', 'Layout', [5 2]), ...
            audioPluginGridLayout('RowHeight', [200 20 100 20 100 20], ...
            'ColumnWidth', [100 100], 'Padding', [10 10 10 30]), ...
            'BackgroundImage', audiopluginexample.private.mwatlogo);
    end    
    
    properties (Access = private)    
        
         % Numerator coefficients
        Num
        
        % Denominator coefficients
        Den
        
        % Biquad filter
        SOSObj     
        
        % Visual object
        visualObj
        
        % Flag to indicate whether filter re-design is needed
        AreFiltersDesigned = false;
    end
    
    methods
        % Constructor
        function plugin = ShelvingEqualizer
            
            plugin.SOSObj = dsp.SOSFilter('CoefficientSource','Input port',...
                'HasScaleValues',false);
            
            calculateCoefficients(plugin);
        end
        
        function varargout = visualize(plugin,NFFT)
            %VISUALIZE Visualize magnitude response of equalizer
            %   VISUALIZE(plugin) shows the magnitude response of the
            %   filter.
            %
            %   VISUALIZE(plugin,NFFT) uses NFFT points to display the
            %   magnitude response.
            %
            %   VIS = VISUALIZE(plugin,...) returns a handle to the
            %   visualizer.
            if nargin < 2
                NFFT = 2048;
            end
            
            if isempty(plugin.visualObj) || ~isvalid(plugin.visualObj) || nargin > 1
                Fs = getSampleRate(plugin);                               
                
                plugin.visualObj = dsp.DynamicFilterVisualizer(...
                    NFFT,Fs,[20 20e3], ...
                    'XScale', 'Log', ...
                    'YLimits', [-15 15], ...
                    'Title', 'Shelving Equalizer'); 
            else
                if ~isVisible(plugin.visualObj)
                    show(plugin.visualObj);
                end
            end
            % Step the visual object with the filter
            step(plugin.visualObj, plugin);
            
            if nargout>0
                varargout{1} = plugin.visualObj;
            end
        end
        
        function [b,a] = coeffs(plugin)
            if ~plugin.AreFiltersDesigned
                % Re-design filters when necessary
                calculateCoefficients(plugin);
            end
            b = plugin.Num;
            a = plugin.Den;
        end
        
        function set.LowShelfGain(plugin,val)
            % Check that the high cutoff is a finite, real,
            % nonnegative scalar
            validateattributes(val, {'numeric'}, ...
                {'finite','real','scalar'},...
                'set.LowShelfGain','LowShelfGain');
            
            % Set value if it meets required attributes
            plugin.LowShelfGain = val;
            needToDesignFilters(plugin);
        end
        
        function set.LowShelfCutoff(plugin,val)
            % Check that the low slope is a finite, real,
            % nonnegative scalar
            validateattributes(val, {'numeric'}, ...
                {'finite','real','scalar','nonnegative'},...
                'set.LowShelfCutoff','LowShelfCutoff');
                        
            plugin.LowShelfCutoff = val;
            needToDesignFilters(plugin);
        end
        
        function set.LowShelfSlope(plugin,val)
            % Check that the high slope is a finite, real,
            % nonnegative scalar
            validateattributes(val, {'numeric'}, ...
                {'finite','real','scalar','nonnegative'},...
                'set.LowShelfSlope','LowShelfSlope');
            
            % Set value if it meets required attributes
            plugin.LowShelfSlope = val;
            needToDesignFilters(plugin);
        end
        
        function set.HighShelfGain(plugin,val)
            % Check that the high cutoff is a finite, real,
            % nonnegative scalar
            validateattributes(val, {'numeric'}, ...
                {'finite','real','scalar'},...
                'set.HighShelfGain','HighShelfGain');
            
            % Set value if it meets required attributes
            plugin.HighShelfGain = val;
            needToDesignFilters(plugin);
        end
        
        function set.HighShelfCutoff(plugin,val)
            % Check that the low slope is a finite, real,
            % nonnegative scalar
            validateattributes(val, {'numeric'}, ...
                {'finite','real','scalar','nonnegative'},...
                'set.HighShelfCutoff','HighShelfCutoff');
                        
            plugin.HighShelfCutoff = val;
            needToDesignFilters(plugin);
        end
        
        function set.HighShelfSlope(plugin,val)
            % Check that the high slope is a finite, real,
            % nonnegative scalar
            validateattributes(val, {'numeric'}, ...
                {'finite','real','scalar','nonnegative'},...
                'set.HighShelfSlope','HighShelfSlope');
            
            % Set value if it meets required attributes
            plugin.HighShelfSlope = val;
            needToDesignFilters(plugin);
        end
    end
    
    methods(Access = protected)
        
        function out = stepImpl(plugin, in)
            % Main processing algorithm
            out = plugin.SOSObj(in, plugin.Num, plugin.Den);
        end
        
        function resetImpl(plugin)
            % Reset the states
            reset(plugin.SOSObj);
            % Re-design filters
            calculateCoefficients(plugin);
        end
        
        function processTunedPropertiesImpl(plugin)
            % Recalculate coefficients when cutoff is changed
            if ~plugin.AreFiltersDesigned
                % Re-design filters when necessary
                calculateCoefficients(plugin);                
            end            
        end
        
        function [B, A] = getFilterCoefficients(plugin)
            calculateCoefficients(plugin);
            B = plugin.Num;
            A = plugin.Den;
        end

        function flag = isInputSizeMutableImpl(~,~)
            flag = true;
        end

        function s = saveObjectImpl(obj)
            s = saveObjectImpl@matlab.System(obj);
            s = savePluginProps(obj,s);
            if isLocked(obj)
                s.SOSObj = matlab.System.saveObject(obj.SOSObj);
                s.Num = obj.Num;
                s.Den = obj.Den;
                s.AreFiltersDesigned = obj.AreFiltersDesigned;
            end
        end

        function loadObjectImpl(obj, s, wasLocked)
            if wasLocked
                obj.SOSObj = matlab.System.loadObject(s.SOSObj);
                obj.Num = s.Num;
                obj.Den = s.Den;
                obj.AreFiltersDesigned = s.AreFiltersDesigned;
            end
            loadObjectImpl@matlab.System(obj,s,wasLocked);
            reload(obj,s);
        end
    end
        
    methods (Access = private)
        function calculateCoefficients(plugin)
            % Function to compute filter coefficients
            Fs = getSampleRate(plugin);
            [B1,A1] = designShelvingEQ(plugin.LowShelfGain,plugin.LowShelfSlope,plugin.LowShelfCutoff/(Fs/2),'lo','Orientation','row');
            [B2,A2] = designShelvingEQ(plugin.HighShelfGain,plugin.HighShelfSlope,plugin.HighShelfCutoff/(Fs/2),'hi','Orientation','row');
            plugin.Num = [B1;B2];
            plugin.Den = [A1;A2];
            plugin.AreFiltersDesigned = true;
        end
        
        function needToDesignFilters(plugin)
            plugin.AreFiltersDesigned = false; 
            % Update visual if visualize has been called
            if isempty(coder.target) && ~isempty(plugin.visualObj) && isvalid(plugin.visualObj) 
                calculateCoefficients(plugin);
                num = plugin.Num;
                den = plugin.Den;
                step(plugin.visualObj, num, den); 
                plugin.visualObj.SampleRate = plugin.getSampleRate;
            end
        end
    end
end