classdef (StrictDefaults)ParametricEqualizerWithUDP < matlab.System & audioPlugin
%ParametricEqualizerWithUDP Tune the frequency response of an audio signal.
%   This example shows a 3-band parametric equalizer audio plugin. Each
%   band provides a center frequency in Hertz, a Q factor, and a gain in
%   decibels.
%
%   This plugin also uses dsp.UDPSender to send the equalizer 
%   coefficients over UDP every time step is called. This allows the
%   deployed VST plugin to communicate from a digital audio workstation
%   (DAW) it runs in to MATLAB in real time. It allows, as an example,
%   visualizing the magnitude response of the equalizer in real time as the
%   equalizer processes audio in a DAW. For more details, consult
%   'Communicate Between a DAW and MATLAB Using UDP' in the documentation.
%
%   Note that in this example, the filter is redesigned even if data
%   processing has not yet begun, i.e., the object is unlocked. The code
%   can be simplified if it is only desired to redesign the filter once
%   data processing has begun. For an example of such a scenario, see
%   audiopluginexample.HighpassIIRFilter.
%
%   This is an example of an audio plugin that is also a System object.
%
%   % Example: Visualize equalizer response while executing.
%   equalizer = audiopluginexample.ParametricEqualizerWithUDP;
%   visualize(equalizer);
%   audioTestBench(equalizer);
%
% See also: audiopluginexample.UDPSender, designParamEQ,
% multibandParametricEQ
    
    %   Copyright 2015-2022 The MathWorks, Inc.
    %#codegen
    
    %----------------------------------------------------------------------
    % Public properties
    %----------------------------------------------------------------------
    properties
        % Center frequencies for each band
        CenterFrequency1 = 100
        CenterFrequency2 = 1000
        CenterFrequency3 = 10000
        
        % Q factors for each band
        QualityFactor1 = 2
        QualityFactor2 = 2
        QualityFactor3 = 2
        
        % dB gain for each band
        PeakGain1 = 0
        PeakGain2 = 0
        PeakGain3 = 0
                
    end
    
    %----------------------------------------------------------------------
    % Constant properties
    %----------------------------------------------------------------------
    properties (Constant, Hidden)
        % audioPluginInterface manages the number of input/output channels
        % and also instantiates the value class, audioPluginParameter to
        % generate plugin UI parameters.
        PluginInterface = audioPluginInterface(...
            'InputChannels',2,...
            'OutputChannels',2,...
            'PluginName','ParametricEQ',...
            audioPluginParameter('PeakGain1','DisplayName','Low Gain','Label','dB',...
            'Mapping',{'lin',-20,20},'Style','vslider','Layout',[1 1]),...
            audioPluginParameter('CenterFrequency1','DisplayName','Low Frequency','Label','Hz',...
            'Mapping',{'log',20,20e3},'Style','rotaryknob','Layout',[3 1]),...
            audioPluginParameter('QualityFactor1','DisplayName','Low Q',...
            'Mapping',{'log',0.2,700},'Style','rotaryknob','Layout',[5 1]),...
            audioPluginParameter('PeakGain2','DisplayName','Mid Gain','Label','dB',...
            'Mapping',{'lin',-20,20},'Style','vslider','Layout',[1 2]),...
            audioPluginParameter('CenterFrequency2','DisplayName','Mid Frequency','Label','Hz',...
            'Mapping',{'log',20,20e3},'Style','rotaryknob','Layout',[3 2]),...
            audioPluginParameter('QualityFactor2','DisplayName','Mid Q',...
            'Mapping',{'log',0.2,700},'Style','rotaryknob','Layout',[5 2]),...
            audioPluginParameter('PeakGain3','DisplayName','High Gain','Label','dB',...
            'Mapping',{'lin',-20,20},'Style','vslider','Layout',[1 3]),...
            audioPluginParameter('CenterFrequency3','DisplayName','High Frequency','Label','Hz',...
            'Mapping',{'log',20,20e3},'Style','rotaryknob','Layout',[3 3]),...
            audioPluginParameter('QualityFactor3','DisplayName','High Q',...
            'Mapping',{'log',0.2,700},'Style','rotaryknob','Layout',[5 3]), ...
            audioPluginGridLayout('RowHeight', [200 20 100 20 100 20], ...
            'ColumnWidth', [100 100 100], 'Padding', [10 10 10 30]), ...
            'BackgroundImage', audiopluginexample.private.mwatlogo);
    end
    
    %----------------------------------------------------------------------
    % Private properties
    %----------------------------------------------------------------------
    properties (Access = protected)
         % Properties to store numerator and denominator coefficient
        % matrices. One column for each band. Leading 1 in denominator
        % coefficients is omitted
        Num 
        Den 
    end
    
    %----------------------------------------------------------------------
    % Private properties
    %----------------------------------------------------------------------
    properties (Access = private, Hidden)
       
        % Property to store Biquad filter
        sos        
               
        % Flag to indicate whether filter re-design is needed
        AreFiltersDesigned = false;
        
        % Handle to objects used for visualization
        visualObj
                
        % Property to store UDP sender System object
        udpsend
        
        OrderForViz
    end
    
    properties (Access = private, Nontunable)
        % 
        FilterOrder
    end
    
    %----------------------------------------------------------------------
    % public methods
    %----------------------------------------------------------------------
    methods
        
        function plugin = ParametricEqualizerWithUDP(N)
            % Construct biquad filter
            plugin.sos =  dsp.SOSFilter('CoefficientSource','Input port',...
                'HasScaleValues',false);
            
            if nargin < 1
                N = 2;
            end
            plugin.FilterOrder = N;
            plugin.OrderForViz = N;
            
            plugin.Num = [ones(1,3*ceil(N/2));zeros(2,3*ceil(N/2))];
            plugin.Den = [ones(1,3*ceil(N/2));zeros(2,3*ceil(N/2))];
            
            % Construct UDP sender
            plugin.udpsend = dsp.UDPSender('RemoteIPPort', 20000);
        end
        
        function set.CenterFrequency1(plugin,value)                                    
            plugin.CenterFrequency1 = value;
            needToDesignFilters(plugin);
        end
        function set.CenterFrequency2(plugin,value)                                    
            plugin.CenterFrequency2 = value;
            needToDesignFilters(plugin);
        end
        function set.CenterFrequency3(plugin,value)                                    
            plugin.CenterFrequency3 = value;
            needToDesignFilters(plugin);
        end
        
        function set.QualityFactor1(plugin,value)            
            plugin.QualityFactor1 = value;
            needToDesignFilters(plugin);
        end
        function set.QualityFactor2(plugin,value)            
            plugin.QualityFactor2 = value;
            needToDesignFilters(plugin);
        end
        function set.QualityFactor3(plugin,value)            
            plugin.QualityFactor3 = value;
            needToDesignFilters(plugin);
        end
        
        function set.PeakGain1(plugin,value)            
            plugin.PeakGain1 = value;       
            needToDesignFilters(plugin);
        end
        function set.PeakGain2(plugin,value)            
            plugin.PeakGain2 = value;
            needToDesignFilters(plugin);
        end
        function set.PeakGain3(plugin,value)            
            plugin.PeakGain3 = value;
            needToDesignFilters(plugin);
        end
        
        function varargout = visualize(plugin,NFFT)
            %VISUALIZE Visualize magnitude response of equalizer
            %   VISUALIZE(plugin) shows the magnitude response of the
            %   multiband equalizer. If shelving filters and/or
            %   lowpass/highpass filters are enabled, they will be included
            %   in the magnitude response.
            %
            %   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);                               
                designFilters(plugin);
                plugin.visualObj = dsp.DynamicFilterVisualizer(...
                    NFFT,Fs,[20 20e3], ...
                    'XScale','Log', ...
                    'YLimits', [-25 25], ...
                    'Title', 'Three-band Parametric Equalizer',...
                    'ShowLegend', true, ...
                    'FilterNames', {'Band 1','Band 2','Band 3','Overall 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
                designFilters(plugin);
            end
            b = plugin.Num;
            a = plugin.Den;
        end
    end
    
    methods (Access = protected)
        function  setupImpl(plugin)
            % Make sure filters are designed at setup
            if ~plugin.AreFiltersDesigned
                % Re-design filters when necessary
                designFilters(plugin);                
            end
        end
        
        function Output = stepImpl(plugin, Input)  
            % Process using biquad filter
            Output = plugin.sos(Input,plugin.Num,plugin.Den);
             % Send coefficients through UDP
            B = plugin.Num;
            A = plugin.Den;
            plugin.udpsend([B(:); A(:)]);
        end
        
        function processTunedPropertiesImpl(plugin)
            % Every time a tunable parameter is called after locking
            if ~plugin.AreFiltersDesigned
                % Re-design filters when necessary
                designFilters(plugin);                
            end
        end               
        
        function resetImpl(plugin)
            designFilters(plugin);
            refreshVisual(plugin);
            % Reset Biquad states
            reset(plugin.sos);
        end                

        function flag = isInputSizeMutableImpl(~,~)
            flag = true;
        end
        function s = saveObjectImpl(obj)
            s = saveObjectImpl@matlab.System(obj);
            s = savePluginProps(obj,s);
            if isLocked(obj)
                s.sos = matlab.System.saveObject(obj.sos);
                s.udpsend = matlab.System.saveObject(obj.udpsend);
                s.Num = obj.Num;
                s.Den = obj.Den;
                s.FilterOrder = obj.FilterOrder;
                s.OrderForViz = obj.OrderForViz;
                s.AreFiltersDesigned = obj.AreFiltersDesigned;
            end
        end

        function loadObjectImpl(obj, s, wasLocked)
            if wasLocked
                obj.sos = matlab.System.loadObject(s.sos);
                obj.udpsend = matlab.System.loadObject(s.udpsend);
                obj.Num = s.Num;
                obj.Den = s.Den;
                obj.FilterOrder = s.FilterOrder;
                obj.OrderForViz = s.OrderForViz;
                obj.AreFiltersDesigned = s.AreFiltersDesigned;
            end
            loadObjectImpl@matlab.System(obj,s,wasLocked);
            reload(obj,s);
        end
    end
    
    methods (Hidden)        
        function varargout = getFilterCoefficients(plugin)
            N = plugin.OrderForViz;
            varargout{1} = plugin.Num(1:ceil(N/2),:);
            varargout{2} = plugin.Den(1:ceil(N/2),:);
            varargout{3} = plugin.Num(ceil(N/2)+1:2*ceil(N/2),:);
            varargout{4} = plugin.Den(ceil(N/2)+1:2*ceil(N/2),:);
            varargout{5} = plugin.Num(2*ceil(N/2)+1:3*ceil(N/2),:);
            varargout{6} = plugin.Den(2*ceil(N/2)+1:3*ceil(N/2),:);
            varargout{7} = plugin.Num;
            varargout{8} = plugin.Den;
        end
    end
    
    methods (Access = private)
        function needToDesignFilters(plugin)
            plugin.AreFiltersDesigned = false; 
            % Update visual if visualize has been called
            if isempty(coder.target) && ~isempty(plugin.visualObj) && isvalid(plugin.visualObj) 
                designFilters(plugin);
                refreshVisual(plugin);
            end
        end
        
        function refreshVisual(plugin)
            if isempty(coder.target) && ~isempty(plugin.visualObj)
                [num1,den1,num2,den2,num3,den3,num4,den4] = getFilterCoefficients(plugin);
                step(plugin.visualObj, ...
                     num1, den1, ...
                     num2, den2, ...
                     num3, den3, ...
                     num4, den4);
                plugin.visualObj.SampleRate = plugin.getSampleRate;
            end
        end
        
        function designFilters(plugin)
            
            % Calculating sample rate.
            Fs = plugin.getSampleRate;
            
            N = plugin.FilterOrder;
            G = [plugin.PeakGain1,plugin.PeakGain2,plugin.PeakGain3];
            Wo = [plugin.CenterFrequency1,plugin.CenterFrequency2,plugin.CenterFrequency3]/(Fs/2);
            Q = [plugin.QualityFactor1,plugin.QualityFactor2,plugin.QualityFactor3];
            BW = Wo./Q;
            [B,A] = designParamEQ(N,G,Wo,BW,'Orientation','row');
            plugin.Num = B;
            plugin.Den = A;              
            plugin.AreFiltersDesigned = true;
        end
    end
end