classdef Phaser < audioPlugin 
%Phaser Add phaser effect to an audio signal.
%
%   PHASER = audiopluginexample.Phaser() returns an object PHASER with
%   properties set to their default values.
%
%   process method syntax:
%
%   Y = process(PHASER, X) adds phaser effect to the audio input X based on
%   the properties specified in the object PHASER and returns the output Y.
%   Each column of X is treated as an individual input channel.
%
%   Phaser methods:
%   
%   process   - See above description for use of this method
%   reset     - Reset the internal states to initial conditions
%   visualize - Visualize the magnitude response of the phaser
%
%   Phaser properties:
%
%   Rate             - Rate of oscillation of notch frequency (Hz)
%   CenterFrequency  - Frequency of the lowest notch (Hz)
%   Depth            - Range of oscillation of notch frequency
%   QualityFactor    - Quality factor of each notch filter
%   ModulationSource - Oscillator type
%   NumNotches       - Number of notch frequencies
%
%   % Example 1: Construct and use a phaser with 4 notches
%   reader = dsp.AudioFileReader(...
%       "RockDrums-44p1-stereo-11secs.mp3", ...
%       SamplesPerFrame=1024, PlayCount=1);
%
%   player = audioDeviceWriter(SampleRate=reader.SampleRate);
%
%   phaser = audiopluginexample.Phaser;
%   phaser.NumNotches = '4';
%   setSampleRate(phaser, reader.SampleRate);
%   parameterTuner(phaser)
%
%   while ~isDone(reader)
%       x = reader();
%       y = process(phaser, x);
%       player(y);
%       drawnow limitrate;
%   end
%
%   release(reader)
%   release(player)
%
%   % Example 2: Launch a test bench for the Phaser object
%   phaser = audiopluginexample.Phaser;
%   visualize(phaser);
%   audioTestBench(phaser);
%
%   See also: audiopluginexample.MultiNotchFilter, multibandParametricEQ

%   Copyright 2016-2023 The MathWorks, Inc.
    
%#codegen
    
    %----------------------------------------------------------------------
    % Public properties
    %----------------------------------------------------------------------
    properties
        %Rate Frequency of modulator (Hz)
        %   Specify the modulation frequency as a positive scalar
        %   value in Hz. This value must be in the range between
        %   100 and 1000 Hz. The default value is 170.
        Rate = 170

        %CenterFrequency Center frequency (Hz)
        %   Specify the center frequency as a positive scalar
        %   value in Hz. This value must be in the range between
        %   100 and 800 Hz. The default value is 400.
        CenterFrequency = 400

        %Depth Modulation depth
        %   Specify the modulation depth as a positive scalar value. This value
        %   must be in the range between 0.1 and 2. The default value is 1.
        Depth = 1

        %QualityFactor Quality factor
        %   Specify the quality factor as a positive scalar value. This value
        %   must be in the range between 0.5 and 25. The default value is 1.6.
        QualityFactor = 1.6;

        %ModulationSource Modulation source
        %   Specify the modulation source as either 'Sin',
        %   'Triangle', or 'Ramp'. The default value is 'Sin'.
        ModulationSource {mustBeMember(ModulationSource,{'Sin','sin','Triangle','triangle','Ramp','ramp'})} = 'Sin'

        %NumNotches Number of notches
        %   Specify the number of notches as either '2', '4', '6',
        %   '8', '10', or 'Bypass'. The default value is '2'.
        % number of notches for multiband parametric equalizer
        NumNotches {mustBeMember(NumNotches,{'2','4','6','8','10','Bypass','bypass'})} = '2'
    end
    
    %----------------------------------------------------------------------
    % Private properties
    %----------------------------------------------------------------------
    properties(Access = private)
        % handle for MultiBandFilter object
        pNotch
        
        % handles for audio oscillator objects as modulation source
        pModSignalSin % sine
        pModSignalSaw % triangle & ramp
        
        % private numeric property corresponding to ModulationSource
        pNumNotchesNumeric = 2;
        pModulationSource = 0
    end
    
    %----------------------------------------------------------------------
    % Constant properties
    %----------------------------------------------------------------------
    properties (Constant)
        % audioPluginInterface manages the number of input/output channels
        % and uses audioPluginParameter to generate plugin UI parameters.
        PluginInterface = audioPluginInterface( ...
            audioPluginParameter("Rate", DisplayName="Rate", Label="Hz", ...
                DisplayNameLocation="above", Mapping={"lin",100,1000}, ...
                Style="rotaryknob", Layout=[2 1]), ...
            audioPluginParameter("CenterFrequency", DisplayName="Center Frequency", ...
                Label="Hz", DisplayNameLocation="above", Mapping={"log",100,800}, ...
                Style="rotaryknob", Layout=[2 2]), ...
            audioPluginParameter("Depth", DisplayName="Depth", ...
                DisplayNameLocation="above", Mapping={"lin",.1,2}, ...
                Style="rotaryknob", Layout=[4 1]), ...
            audioPluginParameter("QualityFactor", DisplayName="Quality Factor", ...
                DisplayNameLocation="above", Mapping={"lin",.5,25}, ...
                Style="rotaryknob", Layout=[4 2]), ...
            audioPluginParameter("ModulationSource", DisplayName="Modulation Source", ...
                Mapping={"enum",'Sin','Triangle','Ramp'}, ...
                DisplayNameLocation="above", Layout=[6 1]), ...
            audioPluginParameter("NumNotches", DisplayName="Notches", ...
                Mapping={"enum",'2','4','6','8','10','Bypass'}, ...
                DisplayNameLocation="above", Layout=[6 2]), ...
            audioPluginGridLayout(RowHeight=[20 100 20 100 20 30], ...
                ColumnWidth=[120 120], Padding=[10 10 10 30]), ...
            BackgroundImage=audiopluginexample.private.mwatlogo, ...
            PluginName="Phaser", ...
            VendorName="MathWorks", ...
            VendorVersion="3.2.1", ...
            UniqueId="foXd", ...
            InputChannels=2, ...
            OutputChannels=2);
    end
    
    %----------------------------------------------------------------------
    % Public methods
    %----------------------------------------------------------------------    
    methods
        function plugin = Phaser() 
            % Initialize a multinotch filter
            plugin.pNotch = audiopluginexample.MultiNotchFilter();
            plugin.pNotch.NumNotches = '2';
            plugin.pNotch.QualityFactor = 1.6;
            fs = plugin.getSampleRate;
            setSampleRate(plugin.pNotch, fs);
            
            % Initialize modulation sources
            plugin.pModSignalSin = audioOscillator(SamplesPerFrame=1, ...
                                                   SignalType="sine", ...
                                                   Frequency=170, ...
                                                   SampleRate=fs);
            plugin.pModSignalSaw = audioOscillator(SamplesPerFrame=1, ...
                                                   SignalType="sawtooth", ...
                                                   Frequency=170, ...
                                                   SampleRate=fs);
        end

        function set.NumNotches(plugin,val)
            if strcmpi(val,'bypass')
                plugin.pNumNotchesNumeric = 0; %#ok<*MCSUP>
            else
                plugin.pNumNotchesNumeric = real(str2double(val));
            end
            plugin.pNotch.NumNotches = val;
        end
        
        function val = get.NumNotches(plugin)
            in = plugin.pNumNotchesNumeric;
            if in == 0
                val = 'Bypass';
            else
                val = sprintf('%d',int16(in));
            end
        end

        function set.QualityFactor(plugin,val)
            plugin.pNotch.QualityFactor = val;
        end
        
        function val = get.QualityFactor(plugin)
            val = plugin.pNotch.QualityFactor;
        end

        function set.Rate(plugin,val)
            plugin.pModSignalSaw.Frequency = val;
            plugin.pModSignalSin.Frequency = val;
        end
        
        function val = get.Rate(plugin)
            val = plugin.pModSignalSaw.Frequency;
        end

        function set.ModulationSource (plugin, val)
            validatestring(val,{'Triangle','Ramp','Sin'},...
                "set.ModulationSource','ModulationSource");
            switch lower(val)
                case 'triangle'
                    plugin.pModulationSource = 1;
                    plugin.pModSignalSaw.Width = 0.5;
                case 'ramp'
                    plugin.pModulationSource = 2;
                    plugin.pModSignalSaw.Width = 1;
                otherwise
                    plugin.pModulationSource = 0;
            end
        end
        
        function val = get.ModulationSource(plugin)
            in = plugin.pModulationSource;
            if in == 1
                val = 'Triangle';
            elseif in == 2 
                val = 'Ramp';
            else
                val = 'Sin';
            end
        end

        function varargout = visualize(plugin,NFFT)
            %VISUALIZE Visualize magnitude response of phaser
            %   VISUALIZE(PHASER) shows the magnitude response of the
            %   phaser.
            %
            %   VISUALIZE(PHASER,NFFT) uses NFFT points to display the
            %   magnitude response. The default is 2048 points.
            % 
            %   VIS = VISUALIZE(PHASER,...) returns a handle to the
            %   visualizer.
            %
            % % EXAMPLE: Create a 4-notch phaser and view its filter 
            % % response.
            %      phaser = audiopluginexample.Phaser;
            %      phaser.NumNotches = '4';
            %      visualize(notch);
            if nargin < 2
                NFFT = 2048;
            end
            h = plugin.pNotch.visualize(NFFT);

            if nargout>0
                varargout{1} = h;
            end
        end

        function y = process(plugin,x)
            % Y = process(PHASER, X) applies the phaser effect to the input
            % X and returns the output in Y.
            
            y = zeros(size(x));
            if (plugin.pNumNotchesNumeric ~= 0)
                numFrames = size(x,1);
                for idx = 1:ceil(numFrames/1024)
                    % Divide input into chunks of 1024 samples. Recompute
                    % notch frequencies every 1024 samples
                    f = getNotchFrequencies(plugin);
                    plugin.pNotch.Frequency1  = f(1);
                    plugin.pNotch.Frequency2  = f(2);
                    plugin.pNotch.Frequency3  = f(3);
                    plugin.pNotch.Frequency4  = f(4);
                    plugin.pNotch.Frequency5  = f(5);
                    plugin.pNotch.Frequency6  = f(6);
                    plugin.pNotch.Frequency7  = f(7);
                    plugin.pNotch.Frequency8  = f(8);
                    plugin.pNotch.Frequency9  = f(9);
                    plugin.pNotch.Frequency10 = f(10);
                    
                    endIdx = min(idx*1024, numFrames);
                    y((idx-1)*1024+1:endIdx,:) = ...
                        process(plugin.pNotch, x((idx-1)*1024+1:endIdx,:));
                end
            else
                % Bypass
                y = x;
            end
        end

        function reset(plugin)
            % reset(PHASER) resets the sample rate and the internal states
            % of the PHASER plugin.
            
            fs = plugin.getSampleRate;
            setSampleRate(plugin.pNotch, fs);
            plugin.pModSignalSin.SampleRate = fs;
            plugin.pModSignalSaw.SampleRate = fs;
            
            reset(plugin.pNotch)
            reset(plugin.pModSignalSin)
            reset(plugin.pModSignalSaw)
        end  

        function s = saveobj(obj)
            s = saveobj@audioPlugin(obj);
            s.pNotch = saveobj(obj.pNotch);
            s.pModSignalSin = matlab.System.saveObject(obj.pModSignalSin);
            s.pModSignalSaw = matlab.System.saveObject(obj.pModSignalSaw);
            s.CenterFrequency = obj.CenterFrequency;
            s.Depth = obj.Depth;
            s.Rate = obj.Rate;
            s.QualityFactor = obj.QualityFactor;
            s.NumNotches = obj.NumNotches;
            s.ModulationSource = obj.ModulationSource;
            s.pNumNotchesNumeric = obj.pNumNotchesNumeric;
            s.pModulationSource = obj.pModulationSource;

        end
        function obj = reload(obj,s)
            obj = reload@audioPlugin(obj,s);
            obj.CenterFrequency = s.CenterFrequency;
            obj.Depth = s.Depth;
            obj.Rate = s.Rate;
            obj.QualityFactor = s.QualityFactor;
            obj.NumNotches = s.NumNotches;
            obj.ModulationSource = s.ModulationSource;
            obj.pNumNotchesNumeric = s.pNumNotchesNumeric;
            obj.pModulationSource = s.pModulationSource;
            obj.pNotch = audiopluginexample.MultiNotchFilter.loadobj(s.pNotch);
            obj.pModSignalSin = matlab.System.loadObject(s.pModSignalSin);
            obj.pModSignalSaw = matlab.System.loadObject(s.pModSignalSaw);
        end
        
    end
    %----------------------------------------------------------------------
    % Protected methods
    %----------------------------------------------------------------------    
    methods (Access=protected)
        function f = getNotchFrequencies(plugin)
            % Get the modulation input
            if (plugin.pModulationSource == 0)
                modValue = plugin.pModSignalSin();
            else
                modValue = plugin.pModSignalSaw();
            end
            
            % Compute the notch frequencies by using the CenterFrequency,
            % Depth, and modulation input.
            depth = plugin.Depth;
            f0   = plugin.CenterFrequency;
            f    = zeros(1,10);
            f1   = max(f0 + (depth * f0 * modValue),20);
            f(1) = f1;
            fbase = depth * f0 * modValue;

            switch plugin.pNumNotchesNumeric
                case 2
                    f2       =  max((8*f0) + ((2) * fbase),20);
                    f([1,2]) =  [f1, f2];
                    
                case 4
                    f2       =  max(( 4*f0) + (2*fbase),20);
                    f3       =  max(( 8*f0) + (4*fbase),20);
                    f4       =  min((16*f0) + (8*fbase),20000);
                    f(1:4)   =  [f1 f2 f3 f4];

                case 6
                    f2       =  max(( 4*f0) + ( 2*fbase),20);
                    f3       =  max(( 8*f0) + ( 4*fbase),20);
                    f4       =  min((16*f0) + ( 8*fbase),20000);
                    f5       =  min((20*f0) + (10*fbase),20000);
                    f6       =  min((24*f0) + (12*fbase),20000);
                    f(1:6)   =  [f1 f2 f3 f4 f5 f6];

                case 8
                    f2       =  max(( 4*f0) + ( 2*fbase),20);
                    f3       =  max(( 8*f0) + ( 4*fbase),20);
                    f4       =  min((12*f0) + ( 6*fbase),20000);
                    f5       =  min((14*f0) + ( 7*fbase),20000);
                    f6       =  min((16*f0) + ( 8*fbase),20000);
                    f7       =  min((20*f0) + (10*fbase),20000);
                    f8       =  min((24*f0) + (12*fbase),20000);
                    f(1:8)   =  [f1 f2 f3 f4 f5 f6 f7 f8];

                case 10
                    f2       =  max(( 4*f0) + ( 2*fbase),20);
                    f3       =  max(( 8*f0) + ( 4*fbase),20);
                    f4       =  min((12*f0) + ( 6*fbase),20000);
                    f5       =  min((14*f0) + ( 7*fbase),20000);
                    f6       =  min((16*f0) + ( 8*fbase),20000);
                    f7       =  min((18*f0) + ( 9*fbase),20000);
                    f8       =  min((20*f0) + (10*fbase),20000);
                    f9       =  min((24*f0) + (12*fbase),20000);
                    f10      =  min((28*f0) + (14*fbase),20000);
                    f(1:10)  =  [f1 f2 f3 f4 f5 f6 f7 f8 f9 f10];
            end
        end
    end
    methods(Static)
        function obj = loadobj(s)
            if isstruct(s)
                obj = audiopluginexample.Phaser;
                obj = reload(obj,s);
            end
        end
    end
end
