classdef Strobe < audioPlugin
    %Strobe Add audio strobe effect
    %	addStrobe = audiopluginexample.Strobe returns a plugin object,
    %	addStrobe, that adds an audio strobing effect. The strobe effect
    %	alternates between the input signal and a specified strobe fill.
    %	This plugin is compatible with audio data of type double only.
    %
    %	Strobe methods:
    %
    %   process      - Apply strobing effect to audio signal
    %   reset        - Reset internal states to initial conditions
    %
    %	Strobe properties:
    %
    %   SyncDynamics - Synchronize strobe with dynamics (true/false)
    %   Period       - Strobe period (seconds)
    %   Threshold    - Gain threshold (%)
    %   FillType     - Strobe fill type
    %
    % % EXAMPLE 1: Run Strobe Using Audio Test Bench
    %     audioTestBench(audiopluginexample.Strobe)
    %
    % % EXAMPLE 2: Apply Strobing Effect in Audio Stream Loop
    %   % Create I/O objects and Strobe object
    %     fileReader = dsp.AudioFileReader(...
    %         'FunkyDrums-44p1-stereo-25secs.mp3');
    %     deviceWriter = audioDeviceWriter(...
    %         'SampleRate',fileReader.SampleRate);
    %     applyStrobe = audiopluginexample.Strobe;
    %     
    %   % Set the sample rate to run the plugin at.
    %     setSampleRate(applyStrobe,fileReader.SampleRate);
    %
    %   % Listen to the audio without the Strobe effect
    %     for i = 1:300
    %         in = step(fileReader);
    %         play(deviceWriter,in);
    %     end
    %     release(fileReader);
    %
    %   % Listen to the audio with the strobe effect
    %     for i = 1:300
    %         in = step(fileReader);
    %         out = process(applyStrobe,in);
    %         play(deviceWriter,out);
    %     end
    %     release(fileReader);
    %     release(deviceWriter);
    %
    %   See also audioTestBench, audioPlugin, audioPluginParameter,
    %   audioPluginInterface, configureMIDI
    %

    %   Copyright 2016-2023 The MathWorks, Inc.
    %#codegen

    %-----------------------------------------------------------------------
    % Public Properties
    %-----------------------------------------------------------------------
    properties
        %SyncDynamics Synchronize strobe with dynamics
        %   Specify as true or false. When set to true, the strobe period
        %   is synchronized to the dynamics of the audio input. When set to
        %   false, the strobe period is determined by the Period property.
        %   Instead, the strobe period is determined by the variance of the
        %   input.
        %   The default is false. This property is tunable.
        SyncDynamics = false;
        
        %Period Strobe period (s)
        %   Specify strobe period in seconds.  Strobe period must be in the
        %   range 0.025 to 0.01. Values outside of the range are saturated.
        %   The strobe period is the hold duration between gain switching.
        %   This property is enabled when SyncDynamics is set to true.
        %   The default is 0.05 seconds. This property is tunable.
        Period = 0.05;
        
        %Threshold Gain threshold (%)
        %   Gain threshold for application of strobe effect.  The mean
        %   variance of the frame must be greater than
        %   (x*Threshold)/100 for a strobe effect to be applied, where
        %   x is mean variance of the last 100 input frames.
        %   The default is 0 %. This property is tunable.
        Threshold = 0;
        
        %FillType Strobe fill
        %   Strobe fill, specified as StrobeFillEnum.Silence,
        %   StrobeFillEnum.SineModulation, or
        %   StrobeFillEnum.SawtoothModulation. The strobe effect causes the
        %   output to alternate between periods of the input signal and the
        %   strobe fill.
        %   The default is 'StrobeFillEnum.Silence'. This property is tunable.
        FillType = audioexample.StrobeFillEnum.Silence;
    end
    
    %-----------------------------------------------------------------------
    % Private Properties
    %-----------------------------------------------------------------------
    properties (Access = private)
        StrobeIncrement = 1;
        SyncIncrRange   = zeros(1,100);
        Toggle          = 1;
        ToggleCount     = 1;
        DynamicsMemory  = zeros(1,100);
        DynamicsCount   = 1;
        Fill            = [1,1];
        CurrentEnergy   = 0;
        Sine
        Sawtooth
    end
    
    %-----------------------------------------------------------------------
    % Constant Properties
    %-----------------------------------------------------------------------
    properties (Constant)
        PluginInterface = audioPluginInterface(...
            'PluginName','Strobe',...
            audioPluginParameter(...
                'Period',...
                'DisplayName','Period',...
                'Label','s',...
                'Mapping',{'lin',0.025,0.1},...
                'Style', 'rotaryknob', 'Layout', [2 1], 'DisplayNameLocation', 'above'),...
            audioPluginParameter(...
                'FillType',...
                'DisplayName','Fill',...
                'Mapping',{'enum', 'Silence','Sine Modulation','Sawtooth Modulation'}, ...
                'Layout', [4 1], 'DisplayNameLocation', 'above'),...
            audioPluginParameter(...
                'Threshold',...
                'Label','%',...
                'Mapping',{'lin',0,100},...
                'Style', 'rotaryknob', 'Layout', [2 2], 'DisplayNameLocation', 'above'),...
            audioPluginParameter(...
                'SyncDynamics',...
                'DisplayName','Sync Period with Dynamics', ...
                'Mapping', {'enum', 'Disable', 'Sync to Dynamics'}, ...
                'Style', 'vrocker', 'Layout', [3 2; 5 2], 'DisplayNameLocation', 'none'),...
                audioPluginGridLayout('RowHeight', [20 100 25 30 25],...
                'Padding', [10 10 10 30]), ...
                'BackgroundImage', audiopluginexample.private.mwatlogo);
    end
    
    %-----------------------------------------------------------------------
    % Public Methods
    %-----------------------------------------------------------------------
    methods
        
        %-------------------------------------------------------------------
        % Construction: Instantiate objects used in plugin
        %-------------------------------------------------------------------
        function plugin = Strobe
            plugin.Sine     = audioOscillator;
            plugin.Sawtooth = audioOscillator(...
                'SignalType','sawtooth',...
                'Frequency',24000,...
                'Amplitude',2);
        end
        
        %-------------------------------------------------------------------
        % Audio Processing
        %-------------------------------------------------------------------
        function out = process(plugin,in)
            %process Apply strobing effect to audio signal
            % process(addStrobe,in) applies a strobing effect to an audio
            % signal. The gain is switched between zero and one in
            % increments determined by the StrobePeriod property. If
            % SyncDynamics is set to true, the strobe period is determined
            % by mapping the variance of the current audio signal frame to
            % the strobe period in the range [25:100].
            %
            
            updateDynamicsMemory(plugin,in)
            
            thresholdCheck = checkDynamicsThreshold(plugin);
            if thresholdCheck == false
                out = in;
            else
                setStrobeIncrement(plugin)
                setFill(plugin,in);
                out = applyStrobe(plugin,in);
            end
            
        end
        
        %-------------------------------------------------------------------
        % Reset & Initialization
        %-------------------------------------------------------------------
        function reset(plugin)
            %reset Reset values of Strobe properties
            % reset(addStrobe) resets the memory of audio signal
            % dynamics and the position of the strobe toggle to their
            % initial values. The reset function is called by the Audio
            % Test Bench or a digital audio workstation (DAW) when the
            % sample rate changes, and between uses.
            %
            Fs = getSampleRate(plugin);        
            
            minIncr = floor(0.025*Fs);
            maxIncr = floor(0.1*Fs);
            plugin.SyncIncrRange    = floor(linspace(minIncr,maxIncr,100));
            
            plugin.Toggle           = 1;
            plugin.ToggleCount      = 1;
            plugin.DynamicsMemory   = zeros(1,100);
            plugin.DynamicsCount    = 1;
            plugin.Fill             = [1,1];
            plugin.CurrentEnergy    = 0;
            
        end
        function s = saveobj(obj)
            s = saveobj@audioPlugin(obj);
            s.Sine = matlab.System.saveObject(obj.Sine);
            s.Sawtooth = matlab.System.saveObject(obj.Sawtooth);
            s.SyncDynamics = obj.SyncDynamics;
            s.Period = obj.Period;
            s.Threshold = obj.Threshold;
            s.FillType = obj.FillType;
            s.StrobeIncrement = obj.StrobeIncrement;
            s.SyncIncrRange = obj.SyncIncrRange;
            s.Toggle = obj.Toggle;
            s.ToggleCount = obj.ToggleCount;
            s.DynamicsMemory = obj.DynamicsMemory;
            s.DynamicsCount = obj.DynamicsCount;
            s.Fill = obj.Fill;
            s.CurrentEnergy = obj.CurrentEnergy;
        end
        function obj = reload(obj,s)
            obj = reload@audioPlugin(obj,s);
            obj.Sine = matlab.System.loadObject(s.Sine);
            obj.Sawtooth = matlab.System.loadObject(s.Sawtooth);
            obj.SyncDynamics = s.SyncDynamics;
            obj.Period = s.Period;
            obj.Threshold = s.Threshold;
            obj.FillType = s.FillType;
            obj.StrobeIncrement = s.StrobeIncrement;
            obj.SyncIncrRange = s.SyncIncrRange;
            obj.Toggle = s.Toggle;
            obj.ToggleCount = s.ToggleCount;
            obj.DynamicsMemory = s.DynamicsMemory;
            obj.DynamicsCount = s.DynamicsCount;
            obj.Fill = s.Fill;
            obj.CurrentEnergy = s.CurrentEnergy;

        end
    end
    
    %-----------------------------------------------------------------------
    % Hidden Methods
    %-----------------------------------------------------------------------
    methods (Hidden = true)
        
        %-------------------------------------------------------------------
        % Update dynamics memory
        %-------------------------------------------------------------------
        function updateDynamicsMemory(plugin,in)
            plugin.CurrentEnergy = mean(var(in));
            
            count = plugin.DynamicsCount;
            plugin.DynamicsMemory(count) = plugin.CurrentEnergy;
            if count >= 99
                plugin.DynamicsCount = 1;
            else
                plugin.DynamicsCount = count + 1;
            end
        end
        
        %-------------------------------------------------------------------
        % Determine if signal gain is below threshold to apply strobe effect
        %-------------------------------------------------------------------
        function out = checkDynamicsThreshold(plugin)
            threshold = mean(plugin.DynamicsMemory) * plugin.Threshold * 1e-2;
            
            if plugin.CurrentEnergy <= threshold
                out = false;
            else
                out = true;
            end
        end
        
        %-------------------------------------------------------------------
        % Set strobe increment
        %-------------------------------------------------------------------      
        function setStrobeIncrement(plugin)
            if plugin.SyncDynamics == true
                dynamicRange = linspace(0,max(plugin.DynamicsMemory),100);
                
                [~,index] = min( abs(plugin.CurrentEnergy-dynamicRange) );
                plugin.StrobeIncrement = plugin.SyncIncrRange(101-index);
            else
                plugin.StrobeIncrement = floor(getSampleRate(plugin)*plugin.Period);
            end
        end
            
        %-------------------------------------------------------------------
        % Get fill for audio strobing
        %-------------------------------------------------------------------
        function setFill(plugin,in)
            switch plugin.FillType
                case audioexample.StrobeFillEnum.SawtoothModulation
                    plugin.Sawtooth.SampleRate = getSampleRate(plugin);
                    plugin.Sawtooth.SamplesPerFrame = size(in,1);
                    crackles = step(plugin.Sawtooth);
                    c = cast(crackles,class(in));
                    plugin.Fill = [c,c];
                case audioexample.StrobeFillEnum.SineModulation
                    plugin.Sine.SampleRate = getSampleRate(plugin);
                    plugin.Sine.SamplesPerFrame = size(in,1);
                    sinusoid = step(plugin.Sine);
                    s = cast(sinusoid,class(in));
                    plugin.Fill = [s,s];
                case audioexample.StrobeFillEnum.Silence
                    plugin.Fill = plugin.Toggle*in;
            end
        end
        
        %-------------------------------------------------------------------
        % Apply strobing effect
        %-------------------------------------------------------------------
        function out = applyStrobe(plugin,in)
            x   	= zeros(size(in));
            incr    = plugin.StrobeIncrement;
            toggle	= plugin.Toggle;
            fill   	= plugin.Fill;
            count  	= plugin.ToggleCount;
            
            for i = 1:size(in,1)
                if count >= incr
                    toggle = 1 - toggle;
                    count = 1;
                end
                
                if toggle == 0
                    x(i,:) = in(i,:).*fill(i,:);
                else
                    x(i,:) = in(i,:);
                end
                
                count = count + 1;   
            end
            
            plugin.ToggleCount = count;
            plugin.Toggle      = toggle;
            out                = x;
        end
    end
    methods(Static)
        function obj = loadobj(s)
            if isstruct(s)
                obj = audiopluginexample.Strobe;
                obj = reload(obj,s);
            end
        end
    end
end