classdef Chorus < audioPlugin
%Chorus Add chorus effect to an audio signal.
%
%   CHORUS = audiopluginexample.Chorus() returns an object CHORUS with
%   properties set to their default values.
%
%   Chorus methods:
%
%   Y = process(CHORUS, X) adds chorus effect to the audio input X based on
%   the properties specified in the object CHORUS and returns it as output
%   Y. Each column of X is treated as individual input channels.
%
%   Chorus properties:
%
%   Depth1      - Amplitude of first sine wave modulator
%   Rate1       - Frequency of first sine wave modulator (Hz)
%   Depth2      - Amplitude of second sine wave modulator
%   Rate2       - Frequency of second sine wave modulator (Hz)
%   WetDryMix   - Wet/dry signal mix
%   Delay       - Base delay (s)
%
%   % Example 1: Simulate Chorus in MATLAB.
%   reader = dsp.AudioFileReader(...
%       "RockDrums-44p1-stereo-11secs.mp3", ...
%       SamplesPerFrame=1024, PlayCount=1);
%
%   player = audioDeviceWriter(SampleRate=reader.SampleRate);
%
%   chorus = audiopluginexample.Chorus;
%
%   parameterTuner(chorus)
%   while ~isDone(reader)
%       x = reader();
%       y = process(chorus, x);
%       player(y);
%       drawnow limitrate;
%   end
%
%   release(reader)
%   release(player)
%
%   % Example 2: Launch a test bench for the Chorus object
%   chorus = audiopluginexample.Chorus;
%   audioTestBench(chorus);
%
%   See also: audiopluginexample.Echo, audiopluginexample.Flanger

%   Copyright 2015-2023 The MathWorks, Inc.

%#codegen
    
    %----------------------------------------------------------------------
    % Public properties
    %----------------------------------------------------------------------
    properties 
        %Depth1 Amplitude of first sine wave modulator
        %   Specify the amplitude of the first modulating sine wave as a
        %   positive scalar value. The sine wave is added to the base delay
        %   value to modulate the delay sinusoidally. This value must be
        %   in the range between 0 and 10. The default value is 0.01.
        Depth1 = 0.01
        
        %Rate1 Frequency of first sine wave modulator (Hz)
        %   Specify the frequency of the first sine wave as a positive scalar
        %   value in Hz. This property controls the chorus rate. This value must
        %   be in the range between 0 and 10 Hz. The default value is 0.01.
        Rate1 = 0.01
        
        %Depth2 Amplitude of second sine wave modulator
        %   Specify the amplitude of the second modulating sine wave as a
        %   positive scalar value. The sine wave is added to the base delay
        %   value to modulate the delay sinusoidally. This value must be
        %   in the range between 0 and 10. The default value is 0.03.
        Depth2 = 0.03
        
        %Rate2 Frequency of second sine wave modulator (Hz)
        %   Specify the frequency of the second sine wave as a positive scalar
        %   value in Hz. This property controls the chorus rate. This value must
        %   be in the range between 0 and 10 Hz. The default value is 0.02.
        Rate2 = 0.02

        %WetDryMix Wet/dry signal mix
        %   Specify the wet/dry signal mix as a positive scalar. This value must
        %   be in the range between 0 to 1. For example, a wet/dry mix value of
        %   0.6 outputs a signal that is 60% wet (with effect applied) and 40%
        %   dry (unprocessed). The default value is 0.5.
        WetDryMix = 0.5

        %Delay Base delay (s)
        %   Specify the base delay for the chorus effect as positive
        %   scalar value in seconds. This value value must be in the
        %   range between 0 and 0.1 seconds. The default value is 0.02.
        Delay = 0.02
    end
    
    properties (Constant)
        % audioPluginInterface manages the number of input/output channels
        % and uses audioPluginParameter to generate plugin UI parameters.
        PluginInterface = audioPluginInterface( ...
            audioPluginParameter("Depth1", DisplayName="Depth 1", ...
                DisplayNameLocation="above", Mapping={"lin",0,10}, ...
                Style="vslider", Layout=[2 1; 3 1]), ...
            audioPluginParameter("Rate1", DisplayName="Rate 1", Label="Hz", ...
                DisplayNameLocation="above", Mapping={"lin",0,10}, ...
                Style="rotaryknob", Layout=[5 1]), ...
            audioPluginParameter("Depth2", DisplayName="Depth 2", ...
                DisplayNameLocation="above", Mapping={"lin",0,10}, ...
                Style="vslider", Layout=[2 2; 3 2]), ...
            audioPluginParameter("Rate2", DisplayName="Rate 2", Label="Hz", ...
                DisplayNameLocation="above", Mapping={"lin",0,10}, ...
                Style="rotaryknob", Layout=[5 2]), ...
            audioPluginParameter("WetDryMix", DisplayName="Wet/dry mix", ...
                DisplayNameLocation="above", Mapping={"lin",0,1}, ...
                Style="rotaryknob", Layout=[3 3]), ...
            audioPluginParameter("Delay", DisplayName="Base delay", Label="s",...
                DisplayNameLocation="above", Mapping={"lin",0,.2}, ...
                Style="rotaryknob", Layout=[5 3]), ...
            audioPluginGridLayout(RowHeight=[30 90 100 30 100], ...
                ColumnWidth=[100 100 100], Padding=[10 10 10 30]), ...
            BackgroundImage=audiopluginexample.private.mwatlogo, ...
            PluginName="Chorus", ...
            VendorName="MathWorks", ...
            VendorVersion="3.2.1", ...
            UniqueId="ipsg", ...
            InputChannels=2, ...
            OutputChannels=2);
    end
    
    %----------------------------------------------------------------------
    % Private properties
    %----------------------------------------------------------------------
    properties (Access = private, Hidden)
        %pFractionalDelay Delay Filter object for fractional delay with
        %linear interpolation
        pFractionalDelay

        %pSine1 and pSine2 Oscillators
        pSine1
        pSine2
        
        %pSR Sample rate
        pSR
    end
    
    %----------------------------------------------------------------------
    % public methods
    %----------------------------------------------------------------------
    methods
        function obj = Chorus
            fs = getSampleRate(obj);
            
            % Create the modulators
            obj.pSine1 = audioOscillator('Frequency', 0.01,...
                'Amplitude', 0.01, 'SampleRate', fs);
            obj.pSine2 = audioOscillator('Frequency', 0.02,...
                'Amplitude', 0.03, 'SampleRate', fs);
            
            % Create fractional delay
            obj.pFractionalDelay = dsp.VariableFractionalDelay(...
                'MaximumDelay',65000);
            
            obj.pSR = fs;
        end
        
        function set.Depth1(obj, val)
            obj.pSine1.Amplitude = val;%#ok<MCSUP>
        end
        function val = get.Depth1(obj)
            val = obj.pSine1.Amplitude;
        end
        
        function set.Rate1(obj, val)
            obj.pSine1.Frequency = val;%#ok<MCSUP>
        end
        function val = get.Rate1(obj)
            val = obj.pSine1.Frequency;
        end
        
        function set.Depth2(obj, val)
            obj.pSine2.Amplitude = val;%#ok<MCSUP>
        end
        function val = get.Depth2(obj)
            val = obj.pSine2.Amplitude;
        end
        
        function set.Rate2(obj, val)
            obj.pSine2.Frequency = val;%#ok<MCSUP>
        end
        function val = get.Rate2(obj)
            val = obj.pSine2.Frequency;
        end
        
        function reset(obj)
            % Reset sample rate
            fs = getSampleRate(obj);
            obj.pSR = fs;
            
            % Reset oscillators
            obj.pSine1.SampleRate = fs;
            obj.pSine2.SampleRate = fs;
            reset(obj.pSine1);
            reset(obj.pSine2);
            
            % Reset delay
            reset(obj.pFractionalDelay);  
        end
        
        function out = process(obj, x)
            fs = obj.pSR;
            oscillator1 = obj.pSine1;
            oscillator2 = obj.pSine2;
            
            numSamples = size(x,1);

            % Compute the base delay value in samples
            delayInSamples = obj.Delay*fs;
            
            % Set frame size of oscillator objects
            oscillator1.SamplesPerFrame = numSamples;
            oscillator2.SamplesPerFrame = numSamples;
            
            % Create modulated delay vectors
            [d1 , d2] = size(x);

            % Get delayed input
            delayVector = zeros(d1,d2,2);
            delayVector(:,:,1) = repmat(delayInSamples+oscillator1(),1,2);
            delayVector(:,:,2) = repmat(delayInSamples+oscillator2(),1,2);
            y = obj.pFractionalDelay(x,delayVector);

            % Calculating output by adding wet and dry signal in
            % appropriate ratio
            mix = obj.WetDryMix;
            out = ((1-mix).*x) + (mix.*sum(y,3));
        end

        function s = saveobj(obj)
            s = saveobj@audioPlugin(obj);
            s.pFractionalDelay = matlab.System.saveObject(obj.pFractionalDelay);
            s.pSine1 = matlab.System.saveObject(obj.pSine1);
            s.pSine2 = matlab.System.saveObject(obj.pSine2);
            s.pSR = obj.pSR;
            s.Delay = obj.Delay;
            s.Depth1 = obj.Depth1;
            s.Rate1 = obj.Rate1;
            s.Depth2 = obj.Depth2;
            s.Rate2 = obj.Rate2;
            s.WetDryMix = obj.WetDryMix;
        end
        function obj = reload(obj,s)
            obj = reload@audioPlugin(obj,s);
            obj.pSR = s.pSR;
            obj.Delay = s.Delay;
            obj.Depth1 = s.Depth1;
            obj.Rate1 = s.Rate1;
            obj.Depth2 = s.Depth2;
            obj.Rate2 = s.Rate2;
            obj.WetDryMix = s.WetDryMix;
            obj.pFractionalDelay = matlab.System.loadObject(s.pFractionalDelay);
            obj.pSine1 = matlab.System.loadObject(s.pSine1);
            obj.pSine2 = matlab.System.loadObject(s.pSine2);
        end
    end
    methods(Static)
        function obj = loadobj(s)
            if isstruct(s)
                obj = audiopluginexample.Chorus;
                obj = reload(obj,s);
            end
        end
    end
end
