classdef AnalysisAndSynthesisBuffer < handle
    %AnalysisAndSynthesisBuffer Buffering object for batch analysis
    %   aBuff = AnalysisAndSynthesisBuffer(sampleInput,Capacity) returns a
    %   buffer object for use with audio plugins. The buffer object
    %   maintains an input buffer and an output buffer as necessary for
    %   batch processing. The AnalysisAndSynthesisBuffer uses objects of
    %   dsp.AsyncBuffer and synthesisBuffer to maintain input and output
    %   buffers.
    %
    %   AnalysisAndSynthesisBuffer methods:
    %
    %   writeAnalysisBuffer  - Write to input buffer
    %   readAnalysisBuffer   - Read from input buffer
    %   writeSynthesisBuffer - Write to output buffer
    %   readSynthesisBuffer  - Read from output buffer
    %   reset                - Reset states and memory
    %
    %
    %   AnalysisAndSynthesisBuffer properties:
    %
    %   Capacity               - Number of writable/readable rows in buffer
    %   FrameTime              - Time span for input buffer chunking (s)
    %   AnalysisFrameOverlap   - Overlap of analysis frames (%)
    %   SynthesisFrameOverlap  - Overlap of synthesis frames (%)
    %   WindowType             - Type of analysis window
    %   SampleRate             - Sample rate (Hz)
    %   SynthesisOverlapAction - Specify action for overlapped portions
    %   SynthesisGainSmooth    - Apply windowing-based gain smoothing (true/false)
    %
    %   % Example 1 -------------------------------------------------------
    %   % AnalysisAndSynthesisBuffer is suited for batch processing of variable
    %   % input/output systems.  For an example implementation of this
    %   % buffer, see SpectralSubtractor.
    %     edit SpectralSubtractor

    %   Copyright 2016-2022 The MathWorks, Inc.
    %#codegen
    
    %-----------------------------------------------------------------------
    % Public Properties
    %-----------------------------------------------------------------------
    properties
        %FrameTime Time span for input buffer chunking (s)
        %   Time span of analysis frame, specified as a scalar in the range
        %   [0.005,1].
        %   The default is 0.03 seconds. This property is tunable.
        FrameTime = 0.03;
        
        %AnalysisFrameOverlap Overlap of analysis frames
        %   Overlap of analysis frames returned when you write to the input
        %   buffer, specified as a scalar in the range 1% to 99%.
        %   The default is 50%. This property is tunable.
        AnalysisFrameOverlap  = 50;
        
        %SynthesisFrameOverlap Overlap of synthesis frames
        %   Overlap of analysis frames returned when you write to the input
        %   buffer, specified as a scalar in the range 1% to 99%.
        %   The default is 50%. This property is tunable.
        SynthesisFrameOverlap = 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;
        
        %SampleRate Sample rate of input audio signal (Hz)
        %   Sample rate of the input audio signal in Hz, specified as a
        %   positive scalar in the range [1,192000].
        %   The default is 44100 Hz. This property is tunable.
        SampleRate = 44100;

        %SynthesisOverlapAction Specify action for overlapped portion
        %	Specify action for overlapped portion of the synthesis buffer
        %	as 'average' or 'add'.
        SynthesisOverlapAction = 'average';
        
        %SynthesisGainSmooth Apply windowing-based gain smoothing (true/false)
        %
        SynthesisGainSmooth = true;
    end
    
    %-----------------------------------------------------------------------
    % Protected Properties
    %-----------------------------------------------------------------------
    properties (Access = protected)
        AnalysisWindow
        AnalysisBuffer
        SynthesisBuffer
        WindowGain
    end
    properties (SetAccess = private, GetAccess = public)
        AnalysisSamplesPerFrame
        
        AnalysisNumHopsAvailable
        
        AnalysisSamplesPerHop
        SynthesisSamplesPerHop
        
        SynthesisNumSamplesOverlap
        AnalysisNumSamplesOverlap
    end
    methods
        %-------------------------------------------------------------------
        % Constructor
        %-------------------------------------------------------------------
        function obj = AnalysisAndSynthesisBuffer(sampleInput,capacity,varargin)
            obj.AnalysisBuffer  = dsp.AsyncBuffer(capacity);
            obj.SynthesisBuffer = audiopluginexample.private.SynthesisBuffer(capacity);
            obj.WindowGain      = audiopluginexample.private.SynthesisBuffer(capacity);

            for i = 1:2:nargin-2
                obj.(varargin{i}) = varargin{i+1};
            end

            obj.SynthesisBuffer.OverlapAction = obj.SynthesisOverlapAction;
            setup(obj.AnalysisBuffer,sampleInput);
            setup(obj.SynthesisBuffer,sampleInput);
            setup(obj.WindowGain,sampleInput(:,1));

            reset(obj)
        end
        
        %-------------------------------------------------------------------
        % Update analysis properties
        %-------------------------------------------------------------------
        function reset(obj)
            updateSampleBasedProperties(obj)
            reset(obj.AnalysisBuffer);
            reset(obj.SynthesisBuffer);
        end
        
        %------------------------------------------------------------------
        % Write to Analysis Buffer
        %------------------------------------------------------------------
        function writeAnalysisBuffer(obj,in)
            write(obj.AnalysisBuffer,in);
        end
        
        %------------------------------------------------------------------
        % Read from Analysis Buffer
        %------------------------------------------------------------------
        function aF = readAnalysisBuffer(obj)
            aF = read(obj.AnalysisBuffer,obj.AnalysisSamplesPerFrame,obj.AnalysisNumSamplesOverlap);
            
            analysisWindow = cast(obj.AnalysisWindow,'like',aF);
            aF = bsxfun(@times,aF,analysisWindow);
        end

        %------------------------------------------------------------------
        % Write to Synthesis Buffer
        %------------------------------------------------------------------
        function writeSynthesisBuffer(obj,in)
            write(obj.SynthesisBuffer,in);
            write(obj.WindowGain,obj.AnalysisWindow);
        end
        
        %------------------------------------------------------------------
        % Read from Synthesis Buffer
        %------------------------------------------------------------------
        function x = readSynthesisBuffer(obj,outputSize)
            x = read(obj.SynthesisBuffer,outputSize,0);
            
            if obj.SynthesisGainSmooth
                gain = read(obj.WindowGain,outputSize,0);
                x = bsxfun(@times,x,gain);
            end
        end

       function s = saveobj(obj)
            s.AnalysisBuffer = saveobj(clone(obj.AnalysisBuffer));
            s.SynthesisBuffer = matlab.System.saveObject(obj.SynthesisBuffer);
            s.WindowGain = matlab.System.saveObject(obj.WindowGain);
            s.AnalysisWindow = obj.AnalysisWindow;
            s.FrameTime = obj.FrameTime;
            s.AnalysisFrameOverlap = obj.AnalysisFrameOverlap;
            s.SynthesisFrameOverlap = obj.SynthesisFrameOverlap;
            s.WindowType = obj.WindowType;
            s.SampleRate = obj.SampleRate;
            s.SynthesisOverlapAction = obj.SynthesisOverlapAction;
            s.SynthesisGainSmooth = obj.SynthesisGainSmooth;
            s.AnalysisWindow = obj.AnalysisWindow;
        end
        function obj = reload(obj,s)
            obj.AnalysisBuffer = loadobj(s.AnalysisBuffer);
            obj.SynthesisBuffer = matlab.System.loadObject(s.SynthesisBuffer);
            obj.WindowGain = matlab.System.loadObject(s.WindowGain);
            obj.AnalysisWindow = s.AnalysisWindow;
            obj.FrameTime = s.FrameTime;
            obj.AnalysisFrameOverlap = s.AnalysisFrameOverlap;
            obj.SynthesisFrameOverlap = s.SynthesisFrameOverlap;
            obj.WindowType = s.WindowType;
            obj.SampleRate = s.SampleRate;
            obj.SynthesisOverlapAction = s.SynthesisOverlapAction;
            obj.SynthesisGainSmooth = s.SynthesisGainSmooth;
            obj.AnalysisWindow = s.AnalysisWindow;
        end
    end
    
    %----------------------------------------------------------------------
    % PUBLIC METHODS
    %----------------------------------------------------------------------
    methods
        %------------------------------------------------------------------
        % Update sampled-based properties
        %------------------------------------------------------------------
        function updateSampleBasedProperties(obj)
            % Force AnalysisSamplesPerFrame to be even (protects against
            % requiring a number of if-statements downstream)
            obj.AnalysisSamplesPerFrame    = 2*floor(obj.SampleRate*obj.FrameTime/2); %double
            
            obj.AnalysisSamplesPerHop      = floor(obj.AnalysisSamplesPerFrame*(100-obj.AnalysisFrameOverlap)/100); %double
            obj.SynthesisSamplesPerHop     = floor(obj.AnalysisSamplesPerFrame*(100-obj.SynthesisFrameOverlap)/100); %double
            
            obj.SynthesisNumSamplesOverlap = obj.AnalysisSamplesPerFrame - obj.SynthesisSamplesPerHop; %double
            obj.AnalysisNumSamplesOverlap  = obj.AnalysisSamplesPerFrame - obj.AnalysisSamplesPerHop; %double
            
            obj.SynthesisBuffer.Overlap    = int32(obj.SynthesisNumSamplesOverlap);
            obj.WindowGain.Overlap         = int32(obj.SynthesisNumSamplesOverlap);
            
            setWindow(obj)
        end
        
        %------------------------------------------------------------------
        % Update dependent parameters
        %------------------------------------------------------------------
        function set.AnalysisFrameOverlap(obj,val)
            obj.AnalysisFrameOverlap = val;
            updateSampleBasedProperties(obj)
        end     
        function set.SynthesisFrameOverlap(obj,val)
            obj.SynthesisFrameOverlap = val;
            updateSampleBasedProperties(obj)
        end    
        function set.FrameTime(obj,val)
            obj.FrameTime = val;
            updateSampleBasedProperties(obj)
        end
        function set.WindowType(obj,val)
            obj.WindowType = val;
            setWindow(obj);
        end
        
        %------------------------------------------------------------------
        % Check available unread memory
        %------------------------------------------------------------------
        function numUnreadSamples = checkInputBufferMemory(obj)
            numUnreadSamples = obj.AnalysisBuffer.NumUnreadSamples; %int32
        end
        function numUnreadSamples = checkSynthesisBufferMemory(obj)
            numUnreadSamples = obj.SynthesisBuffer.NumUnreadSamples; %int32
        end
        
        %------------------------------------------------------------------
        % Get number of available hops available in Analysis buffer
        %------------------------------------------------------------------
        function numHops = get.AnalysisNumHopsAvailable(obj)
            numHops = floor(double(obj.AnalysisBuffer.NumUnreadSamples)/obj.AnalysisSamplesPerHop); %double
        end
    end
            
    
    %----------------------------------------------------------------------
    % PRIVATE METHODS: Calculate analysis window
    %----------------------------------------------------------------------
    methods (Access = private)
        function setWindow(obj)
            n = obj.AnalysisSamplesPerFrame; %double
            m = n/2;
            x = (0:m-1)'/(n-1);
            
            w = ones(m,1);
            switch obj.WindowType
                case audioexample.WindowEnum.Triangle
                   	w = ((2*(1:(n+1)/2)-1)/n)';
                case audioexample.WindowEnum.Hann
                    w = 0.5 - 0.5*cos(2*pi*x);
                case audioexample.WindowEnum.Hamming
                    w = 0.54 - 0.46*cos(2*pi*x);
                case audioexample.WindowEnum.Blackman
                    w = 0.42 - 0.5*cos(2*pi*x) + 0.08*cos(4*pi*x);
                    w(1) = 0;
            end            
            w = [w; w(end:-1:1)];
            
            obj.AnalysisWindow = w;
        end
    end

    methods(Static)
        function obj = loadobj(s)
            if isstruct(s)
                obj = audiopluginexample.private.AnalysisAndSynthesisBuffer(1,44100);
                obj = reload(obj,s);
            end
        end     
    end
end