classdef SpectralSubtractor < audioPlugin
    %SpectralSubtractor Perform spectral subtraction
    %   noiseReducer = audiopluginexample.SpectralSubtractor returns a
    %   basic audio plugin, noiseReducer, that performs spectral
    %   subtraction on a mono input signal.
    %
    %   SpectralSubtractor methods:
    %   process           - Perform spectral subtraction
    %   reset             - Reset internal states to initial conditions
    %
    %   SpectralSubtractor properties:
    %   WindowType        - Type of analysis window
    %   Mag2Subtract      - Magnitude subtracted from frequency bins
    %   FrameTime         - Analysis period (s)
    %   Overlap           - Analysis frame overlap (%)
    %
    % % EXAMPLE 1 ---------------------------------------------------------
    % % Modify plugin properties in real time using the Audio Test Bench.
    % % Run the SpectralSubtractor plugin using the Audio Test Bench.
    % % Navigate the UI to set your input signal to 'NoisySpeech-16-22p5-mono-5secs.wav'.
    %   audioTestBench(audiopluginexample.SpectralSubtractor);
    % % Use a SamplesPerFrame input in the range 64-512 for good results.
    %
    % See also audioTestBench, generateAudioPlugin, validateAudioPlugin,
    % audioPlugin, audioPluginInterface, audioPluginParameter,
    % configureMIDI.
    
    %   Copyright 2016-2022 The MathWorks, Inc.
    %#codegen    
 
    %----------------------------------------------------------------------
    % Public Properties - The end-user tunes these properties 
    %----------------------------------------------------------------------
    properties
        %Mag2Subtract Magnitude subtracted from frequency bins
        %   Magnitude subtracted from frequency bins, specified as a real
        %   scalar in the range 0 to 3. So that the resulting magnitude
        %   spectrum is nonnegative, the spectral subtraction has a floor
        %   of zero.
        %   The default is 0. This property is tunable.
        Mag2Subtract = 0;
    end
    
    properties
        %FrameTime Analysis period (s)
        %   Period of analysis frame in seconds, specified as a positive
        %   scalar between 0.02 and 0.04.
        %   The default is 0.03 seconds. This property is tunable.
        FrameTime = 0.03
        
        %Overlap Analysis frame overlap (%)
        %   Analysis frame overlap, specified in the range 0% to 80%.
        %   The default is 50%. This property is tunable.
        Overlap  = 50
        
        %WindowType Type of analysis window
        %   Type of analysis window, specified as 'Triangle', 'Hann',
        %   'Hamming', 'Blackman', or 'Rectangle'.
        %   The default is 'Rectangle'. This property is tunable.
        WindowType = audioexample.WindowEnum.Triangle;
    end
    
    %----------------------------------------------------------------------
    % PRIVATE PROPERTIES : Used for internal processing and storage 
    %----------------------------------------------------------------------
    properties (Access = private)
        Buffer
    end

    %----------------------------------------------------------------------
    % CONSTANT PROPERTIES : These properties define the interface between
    % generated plugin, DAW, and end-user
    %----------------------------------------------------------------------
    properties (Constant)
        PluginInterface = audioPluginInterface( ...
            audioPluginParameter('WindowType',...
                'DisplayName','Analysis Window',...
                'Mapping',{'enum','Rectangle','Triangle','Hann','Hamming','Blackman'}),...
            audioPluginParameter('Mag2Subtract', ...
                'DisplayName','Noise Estimate', ...
                'Mapping',{'lin',0,6}), ...
            audioPluginParameter('FrameTime',...
                'DisplayName','Analysis Frame',...
                'Label','s',...
                'Mapping',{'lin',0.01,0.04}),...
            audioPluginParameter('Overlap',...
                'DisplayName','Frame Overlap',...
                'Label','%',...
                'Mapping',{'int',0,80}),...
            'InputChannels',1, ...
            'OutputChannels',1, ...
            'PluginName','SpectralSubtractor', ...
            'BackgroundImage', audiopluginexample.private.mwatlogo);
    end
    
    %----------------------------------------------------------------------
    % PUBLIC METHODS : Main processing algorithms
    %----------------------------------------------------------------------    
    methods
        %------------------------------------------------------------------
        % Construct analysis buffer
        %------------------------------------------------------------------
        function plugin = SpectralSubtractor
            sampleInput = zeros(1,1);
            plugin.Buffer = audiopluginexample.private.AnalysisAndSynthesisBuffer(sampleInput,44100,...
                                    'FrameTime',0.03,...
                                    'AnalysisFrameOverlap',50,...
                                    'SynthesisFrameOverlap',50,...
                                    'WindowType',  audioexample.WindowEnum.Triangle);
        end
        %-------------------------------------------------------------------
        % Main processing function
        %-------------------------------------------------------------------
        function out = process(plugin,in)
            %process Perform spectral subtraction
            % y = process(noiseReducer,x) applies spectral subtraction to a
            % mono input, x, and returns a mono output, y.
            %
            
            % Write to input buffer
            writeAnalysisBuffer(plugin.Buffer,in);
            
            % Read from input buffer, process signal, and then write to
            % output buffer
            while (plugin.Buffer.AnalysisNumHopsAvailable > 0)
                x = readAnalysisBuffer(plugin.Buffer);
                y = processSpectrum(plugin,x);
                writeSynthesisBuffer(plugin.Buffer,y);
            end
            
            % Check available unread data in output buffer
            NumUnreadSamples = checkSynthesisBufferMemory(plugin.Buffer);
            
            % Read from output buffer
            if (NumUnreadSamples > size(in,1)) && ...
                    (NumUnreadSamples > plugin.Buffer.AnalysisSamplesPerFrame)
                y = readSynthesisBuffer(plugin.Buffer,size(in,1));
                out = y(1:size(in,1),1:size(in,2));
            else
                out = zeros(size(in));
            end
            
        end
        
        function reset(plugin)
            %reset Reset internal states to initial conditions
            % reset(noiseReducer) returns all private properties to their
            % initial conditions. In a DAW environment, reset is called
            % between uses or if the environment sample rate changes. In
            % MATLAB, call reset before an audio stream loop to mimic the
            % DAW environment.
            %
            plugin.Buffer.SampleRate = getSampleRate(plugin);
            reset(plugin.Buffer);
        end

        function s = saveobj(obj)
            s = saveobj@audioPlugin(obj);
            s.Buffer = saveobj(obj.Buffer);
            s.Mag2Subtract = obj.Mag2Subtract;
            s.FrameTime = obj.FrameTime;
            s.Overlap = obj.Overlap;
            s.WindowType = obj.WindowType;
        end
        function obj = reload(obj,s)
            obj = reload@audioPlugin(obj,s);
            obj.Mag2Subtract = s.Mag2Subtract;
            obj.FrameTime = s.FrameTime;
            obj.Overlap = s.Overlap;
            obj.WindowType = s.WindowType;
            obj.Buffer = audiopluginexample.private.AnalysisAndSynthesisBuffer.loadobj(s.Buffer);
        end
        
    end
    

    %----------------------------------------------------------------------
    % PRIVATE METHODS : Perform spectral subtraction
    %----------------------------------------------------------------------
    methods (Access=private)
        function out = processSpectrum(plugin,x)
            [N,numChan] = size(x);
            X           = fft(x,N*2);
            XHalf       = X(1:N+1,:);
            
            out = zeros(size(x));
            for i = 1:numChan
                XmagHalf     = abs(XHalf(:,i));
                YmagHalf     = max(0,XmagHalf-plugin.Mag2Subtract);
                YHalf        = YmagHalf.*exp(1i*angle(XHalf));
                reverseYHalf = flipud( conj(YHalf(2:end-1)) );
                Y            = [YHalf;reverseYHalf];
                y            = real(ifft(Y));
                out(:,i)     = y(1:N);
            end
        end
    end
    
    %----------------------------------------------------------------------
    % PUBLIC METHODS : Listen for parameter tuning and update dependent
    % properties
    %----------------------------------------------------------------------
    methods
        function set.FrameTime(plugin,val)
            plugin.Buffer.FrameTime = val;%#ok<MCSUP>
        end
        function val = get.FrameTime(plugin)
            val = plugin.Buffer.FrameTime;
        end
        function set.Overlap(plugin,val)
            plugin.Buffer.AnalysisFrameOverlap = val;%#ok<MCSUP>
            plugin.Buffer.SynthesisFrameOverlap = val;%#ok<MCSUP>
        end
        function val = get.Overlap(plugin)
            val = plugin.Buffer.AnalysisFrameOverlap;
        end
        function set.WindowType(plugin,val)
            switch val
                case audioexample.WindowEnum.Triangle
                    plugin.Buffer.WindowType = audioexample.WindowEnum.Triangle;%#ok<MCSUP>
                case audioexample.WindowEnum.Rectangle
                    plugin.Buffer.WindowType = audioexample.WindowEnum.Rectangle;%#ok<MCSUP>
                case audioexample.WindowEnum.Hann
                    plugin.Buffer.WindowType = audioexample.WindowEnum.Hann;%#ok<MCSUP>
                case audioexample.WindowEnum.Hamming
                    plugin.Buffer.WindowType = audioexample.WindowEnum.Hamming;%#ok<MCSUP>
                case audioexample.WindowEnum.Blackman
                    plugin.Buffer.WindowType = audioexample.WindowEnum.Blackman;%#ok<MCSUP>
            end
        end
         function val = get.WindowType(plugin)
            val = plugin.Buffer.WindowType;
        end
    end

    methods(Static)
        function obj = loadobj(s)
            if isstruct(s)
                obj = audiopluginexample.SpectralSubtractor;
                obj = reload(obj,s);
            end
        end
    end
end