classdef SpeechPitchDetector < audioPlugin
%SpeechPitchDetector Determine pitch of speech signal
%   sPD = audiopluginexample.SpeechPitchDetector returns a basic plugin,
%   sPD, that calculates pitch using an autocorrelation algorithm,
%   spectral periodicity algorithm, or both. The algorithms are described
%   in "Digital Speech Processing" by Rabiner and Schaffer.  This is a
%   passthrough plugin (input==output) used for analysis purposes. It is
%   only compatible with mono input.
%
%   SpeechPitchDetector methods:
%   
%   process   - Determine pitch of speech signal
%   visualize - Visualize pitch decision
%   reset     - Reset internal states to initial conditions
%
%   SpeechPitchDetector properties:
%
%   LowCutoff          - Low cutoff of frequency detected (Hz)
%   HighCutoff         - High cutoff of frequency detected (Hz)
%   AnalysisFrameTime  - Analysis frame time (s)
%   Overlap            - Analysis frame overlap (%)
%   FrequencyThreshold - Frequency threshold for voiced speech detection (Hz)
%   PowerThreshold     - Power threshold for voiced speech detection (dB)
%   DetectionMethod    - Method used for pitch detection
%   ClipLevel          - Center clip level for pitch detection (%)
%
%   % Example 1 -----------------------------------------------------------
%   sPD = audiopluginexample.SpeechPitchDetector;
%   visualize(sPD)
%
%   audioTestBench(sPD)
%   % Choose the 'Counting-16-44p1-mono-15secs.wav' as input.
%
%   % Example 2 -----------------------------------------------------------
%   % Visualize the pitch contour of a speech file.
%
%     reader = dsp.AudioFileReader('Counting-16-44p1-mono-15secs.wav', ...
%                   'SamplesPerFrame',1024);
%     writer = audioDeviceWriter('SampleRate',reader.SampleRate);
%    
%     pitchDetector = audiopluginexample.SpeechPitchDetector( ...
%                   'HighCutoff',750,...
%                   'AnalysisFrameTime', 0.06,...
%                   'Overlap',75,...
%                   'DetectionMethod',audioexample.PitchDetectionEnum.Spectral);
%   
%     setSampleRate(pitchDetector,reader.SampleRate)
%     reset(pitchDetector)
%    
%     visualize(pitchDetector,'pitch contour');
%
%     while ~isDone(reader)
%        audioIn = reader();
%        [audioOut,pD] = process(pitchDetector,audioIn);
%        writer(audioOut);
%     end
%    
%     release(reader);
%     release(writer);
%
%   % Example 3 -------------------------------------------------------
%   % Use the autocorrelation pitch detection method to approximate the
%   % pitch of a sine wave. Visualize the pitch decision using the pitch
%   % meter. The actual pitch is displayed in the Command Window.
%
%     osc = audioOscillator
%
%     pitchDetector = audiopluginexample.SpeechPitchDetector( ...
%                           'LowCutoff',10, ...
%                           'HighCutoff',600, ...
%                           'AnalysisFrameTime',0.04, ...
%                           'Overlap',0, ...
%                           'PowerThreshold',-20, ...
%                           'FrequencyThreshold',1500, ...
%                           'DetectionMethod',audioexample.PitchDetectionEnum.Autocorrelation, ...
%                           'ClipLevel',90);
%
%     setSampleRate(pitchDetector,osc.SampleRate)
%     reset(pitchDetector)
%
%     visualize(pitchDetector,'pitch meter');
%
%     counter = 0;
%     while (counter < 1e4)
%       process(pitchDetector,osc());
%       counter = counter + 1;
%       if mod(counter,1100)==0
%       	osc.Frequency = osc.Frequency + 50;
%           fprintf('True frequency = %d\n',osc.Frequency)
%       end
%     end
%
% See also: audiopluginexample.BeatDetector,
% audiopluginexample.PitchShifter

%   Copyright 2016-2022 The MathWorks, Inc.

%#codegen
    
    %-----------------------------------------------------------------------
    % Public Properties - End user interacts with these
    %-----------------------------------------------------------------------
    properties
        %LowCutoff Low cutoff of frequency detected (Hz)
        %   Specify low cutoff frequency in the range [1,200]. The pitch decision only
        %   considers pitch candidates above the cutoff, and the speech is
        %   prefiltered by a bandpass filter with specified a low cutoff.
        %   Male voices are typically in the range 85 to 180 Hz.
        %   Female voices are typically in the range 165 to 255 Hz.
        %   The default is 50. This property is tunable.
        LowCutoff = 50;
        
        %HighCutoff High cutoff of frequency detected (Hz)
        %   Specify high cutoff frequency in the range [201,1500]. The pitch decision only
        %   considers pitch candidates below the cutoff, and the speech is
        %   prefiltered by a bandpass filter with specified a high cutoff.
        %   Because the spectral method uses harmonics, the high cutoff
        %   should be approximately 3 times the assumed max pitch.
        %   Male voices are typically in the range 85 to 180 Hz.
        %   Female voices are typically in the range 165 to 255 Hz.
        %   The default is 600. This property is tunable.        
        HighCutoff = 600;
        
        %AnalysisFrameTime Analysis frame time (s)
        %   Specify the analysis frame time for pitch detection in the
        %   range 0.01 to 0.06.
        %   The default is 0.03. This property is tunable.
        AnalysisFrameTime = 0.03;

        %Overlap Analysis frame overlap (%)
        %   Specify analysis frame overlap in the range 0% to 90%.
        %   The default is 75%. This property is tunable.
        Overlap = 75;
        
        %FrequencyThreshold Frequency threshold for voiced speech detection (Hz)
        %   Specify frequency threshold in Hz. Frequency threshold must be
        %   in the range 500 to 2500. The frequency threshold is used to
        %   decide if the input signal contains a region of voiced speech.
        %   The default is 1000. This property is tunable.
        FrequencyThreshold = 1000; 
        
        %PowerThreshold Power threshold for voiced speech detection (dB)
        %   Specify power threshold in the range -40 to -5. The power
        %   threshold is used to decide if the input signal contains a
        %   region of voiced speech.
        %   The default is -20. This property is tunable.
        PowerThreshold = -20;  
    
        %ClipLevel Center clip level for pitch detection (%)
        %   Specify the clip level percent in the range 0 to 100. This
        %   property applies to the autocorrelation detection method.
        %   To emphasize regions of high energy, the middle region of the
        %   audio signal is set to zero.
        %   The default is 68. This property is tunable.
        ClipLevel = 68;
        
        %DetectionMethod Method used for pitch detection
        %   Specify DetectionMethod as
        %   audioexample.DetectionEnum.Autocorrelation,
        %   audioexample.DetectionEnum.Spectral, or
        %   audioexample.DetectionEnum.Both. The default is
        %   audioexample.DetectionEnum.Both. This property is tunable.
        DetectionMethod = audioexample.PitchDetectionEnum.Both;
    end
        
    %-----------------------------------------------------------------------
    % Private Properties - Used for internal storage
    %-----------------------------------------------------------------------
    properties (Access = private)
        pNumOldSamplesUnread = 0;
        
        pSamplesPerAnalysisFrame
        pSamplesPerHop
        pSamplesOverlap
        pBFDFlag = true;

        pBuffer
        
        % (Max sample rate) * (max analysis time (s))
        pMaxSamplesOverlap = 192000*0.06;
        
        pLastKnownPitch = nan;
        pPastDecision   = nan;
        
        pBandpassFilter
        pScope
        
        pFrameSize = 512;
        
        pFIRInterpolatorFour
        pFIRInterpolatorEight
        pFIRInterpolatorTwelve
        pFIRInterpolatorSixteen
        pFIRInterpolatorTwenty
    end
    
    %----------------------------------------------------------------------
    % Protected Properties - Used by pitch meter visualization
    %----------------------------------------------------------------------
    properties (Access = protected)
        poVisualizer
        poElapsedTime = 0;
    end
    %----------------------------------------------------------------------
    % Constant Properties - Used to define plugin interface
    %----------------------------------------------------------------------
    properties (Constant)
        PluginInterface = audioPluginInterface( ...
            'InputChannels',1, ...
            'OutputChannels',1, ...
            'PluginName','Pitch Detector', ...
            audioPluginParameter('LowCutoff', ...
            'DisplayName', 'Low cutoff', ...
            'Label','Hz', ...
            'Mapping',{'int',1,200}), ...
            audioPluginParameter('HighCutoff', ...
            'DisplayName', 'High cutoff', ...
            'Label','Hz', ...
            'Mapping',{'int',201,1500}), ...
            audioPluginParameter('AnalysisFrameTime', ...
            'DisplayName','Analysis Frame Time', ...
            'Label','s', ...
            'Mapping',{'lin',0.01,0.06}), ...
            audioPluginParameter('Overlap', ...
            'DisplayName','Overlap', ...
            'Label','%', ...
            'Mapping',{'lin',0,90}), ...
            audioPluginParameter('FrequencyThreshold', ...
            'DisplayName', 'Frequency Threshold', ...
            'Mapping',{'lin',500,2500}, ...
            'Label','Hz'), ...
            audioPluginParameter('PowerThreshold', ...
            'DisplayName','Power Threshold', ...
            'Mapping',{'lin',-40,-5}, ...
            'Label', 'dB'), ...
            audioPluginParameter('DetectionMethod', ...
            'DisplayName','Detection Method',...
            'Mapping',{'enum','Both','Autocorrelation','Spectral'}),...
            audioPluginParameter('ClipLevel', ...
            'Label','%', ...
            'Mapping',{'lin',0,100}, ...
            'DisplayName', 'Auto Correlation - Clip Level'), ...
            'BackgroundImage', audiopluginexample.private.mwatlogo);
    end
    
    %----------------------------------------------------------------------
    % Public Methods - End user interacts with these
    %----------------------------------------------------------------------
    methods
        %------------------------------------------------------------------
        % Construct objects used in plugin
        %------------------------------------------------------------------
        function plugin = SpeechPitchDetector(varargin)
            for i = 1:2:nargin
                plugin.(varargin{i}) = varargin{i+1};
            end
            
            plugin.pBandpassFilter = audiopluginexample.VarSlopeBandpassFilter( ...
                plugin.LowCutoff, ...
                plugin.HighCutoff, ...
                48,48);
            
            plugin.pFIRInterpolatorFour    = dsp.FIRInterpolator(4,designMultirateFIR(4,1));
            plugin.pFIRInterpolatorEight   = dsp.FIRInterpolator(8,designMultirateFIR(8,1));
            plugin.pFIRInterpolatorTwelve  = dsp.FIRInterpolator(12,designMultirateFIR(12,1));
            plugin.pFIRInterpolatorSixteen = dsp.FIRInterpolator(16,designMultirateFIR(16,1));
            plugin.pFIRInterpolatorTwenty  = dsp.FIRInterpolator(20,designMultirateFIR(20,1));

            plugin.pBuffer = audioexample.Stack(plugin.pMaxSamplesOverlap*4,1,'double');
            
            % In a DAW environment, reset is called automatically during
            % initialization. If generating this code to a DAW, comment out
            % the below call to reset.
            reset(plugin)
        end
        
        %------------------------------------------------------------------
        % Main processing algorithm
        %------------------------------------------------------------------
        function [out,pitchVector] = process(plugin,newSamples)
            % Validate input attributes. In a DAW environment, the DAW is
            % responsible for passing valid inputs. You can comment-out the
            % input validation if generating the plugin.
            validateattributes(newSamples,{'double'},{'column'},'process','audioIn',2)
            
            out           = newSamples;
            numNewSamples = size(newSamples,1);
            
            pitch                      = plugin.pPastDecision;
            numSamplesPerAnalysisFrame = plugin.pSamplesPerAnalysisFrame;
            numSamplesPerHop           = plugin.pSamplesPerHop;
            numLeftover                = plugin.pNumOldSamplesUnread;
            numOverlap                 = plugin.pSamplesOverlap;
            numSnaps                   = floor((numNewSamples+numLeftover-1)/numSamplesPerHop);
            pitchVector                = pitch*ones(numNewSamples,1);
            
            if plugin.pBFDFlag || numSnaps >0 % Need to update pitch decision
                start = numSamplesPerAnalysisFrame +1;
                
                % Initialize indices --------------------------------------
                if plugin.pBFDFlag
                    % Samples per frame or samples per overlap changed
                    numSnaps        = floor((numNewSamples-1)/numSamplesPerHop) + 1;
                    numLeftover     = 0;
                    plugin.pBFDFlag = false;
                    
                    numHops         = numSnaps - 1;
                    numOld          = numSamplesPerAnalysisFrame;
                    samplesToHold   = 0;
                else
                    numHops       = numSnaps;
                    numOld        = numOverlap + numLeftover;
                    samplesToHold = start - numOld;
                end
                % ---------------------------------------------------------
                
                % Connect old and new samples -----------------------------
                oldSamples = read(plugin.pBuffer,numOld);
                samples    = [oldSamples;newSamples];
                %----------------------------------------------------------
                
                % Determine the pitch vector to output --------------------
                pitchVector(1:samplesToHold,1) = pitch*ones(samplesToHold,1);
                for i = 1:numSnaps
                    signalToAnalyze = samples((start-numSamplesPerAnalysisFrame):start);
                    fs = getSampleRate(plugin);
                    if plugin.isVoicedSpeech(signalToAnalyze,fs,plugin.PowerThreshold,plugin.FrequencyThreshold)
                        pitch = pitchDetection(plugin,signalToAnalyze,pitch);
                    else
                        pitch = nan;
                    end
                    
                    if i ~= numSnaps
                        start = start + numSamplesPerHop;
                        pitchVector(samplesToHold + 1 + (i-1)*numSamplesPerHop:samplesToHold+i*numSamplesPerHop) ...
                            = pitch*ones(numSamplesPerHop,1);
                    end
                    
                end
                numLeftover = numLeftover + numNewSamples - numHops*numSamplesPerHop;
                pitchVector(end-numLeftover+1:end) = pitch*ones(numLeftover,1);
                %----------------------------------------------------------
                
            else % Not enough samples for a snap ======================
                numLeftover = numLeftover + numNewSamples;
            end
        
            % Update buffer -----------------------------------------------
            numToWrite = min(numNewSamples,plugin.pMaxSamplesOverlap);
            write(plugin.pBuffer,newSamples(end-numToWrite+1:end));
            % -------------------------------------------------------------

            % Update pitch contour visualization --------------------------
            if isempty(coder.target) && ~isempty(plugin.pScope) && isvalid(plugin.pScope)
                if plugin.pFrameSize(1) ~= numNewSamples
                    release(plugin.pScope)
                end
                plugin.pScope(out,pitchVector)
            end
            % -------------------------------------------------------------
            
            % Update pitch meter visualization ----------------------------
            plugin.poElapsedTime = plugin.poElapsedTime + numNewSamples/getSampleRate(plugin);
            if isempty(coder.target) && ~isempty(plugin.poVisualizer) && isvalid(plugin.poVisualizer)
                pitchDisplay = sprintf('%0.1f',plugin.pLastKnownPitch);
                timeDisplay = char(duration(0,0,plugin.poElapsedTime,'Format','hh:mm:ss'));
                update(plugin.poVisualizer,timeDisplay,pitchDisplay);
            end
            %--------------------------------------------------------------
            
            % Update properties -------------------------------------------
            plugin.pFrameSize           = numNewSamples;
            plugin.pNumOldSamplesUnread = numLeftover;
            plugin.pPastDecision        = pitch;
            % -------------------------------------------------------------
        end
        
        %------------------------------------------------------------------
        % Reset memory states and update frequency-dependent parameters
        %------------------------------------------------------------------
        function reset(plugin)
            fs = getSampleRate(plugin);
            
            plugin.updateSampleBasedProperties();

            setSampleRate(plugin.pBandpassFilter,fs);
            reset(plugin.pBandpassFilter);
            
            reset(plugin.pBuffer);
            
            reset(plugin.pFIRInterpolatorFour)
            reset(plugin.pFIRInterpolatorEight)
            reset(plugin.pFIRInterpolatorTwelve)
            reset(plugin.pFIRInterpolatorSixteen)
            reset(plugin.pFIRInterpolatorTwenty)
        
            plugin.pPastDecision = nan;
            
            if isempty(coder.target) && ~isempty(plugin.pScope)
                release(plugin.pScope)
                plugin.pScope.SampleRate = fs;
                plugin.pScope.BufferLength = fs*plugin.pScope.TimeSpan;
            end
            if isempty(coder.target) && ~isempty(plugin.poVisualizer) && isvalid(plugin.poVisualizer)
                reset(plugin.poVisualizer)
                plugin.poElapsedTime = 0;
            end
        end
        
    %----------------------------------------------------------------------
    % Visualize 
    %----------------------------------------------------------------------
    function varargout = visualize(plugin,varargin)
        %visualize Visualize pitch decision
        %   
        %   visualize(pitchDetector,'pitch contour') opens a time Scope and
        %   plots the pitch contour and audio signal.
        %
        %   visualize(pitchDetector,'pitch meter') opens a pitch meter
        %   that display the most recent valid pitch decision.
        %
        %   visualize(pitchDetector) opens both the pitch contour plotter
        %   and the pitch meter display.
        %
        %   vis = visualize(pitchDetector,...) returns a handle to the
        %   pitch contour plot when it applies.
        %
        %   [vis,meter] = visualize(pitchDetector,...) returns handles to
        %   the pitch contour plot and the pitch meter when they both
        %   apply.
        %
        %   meter = visualize(pitchDetector,...) returns a handle to the
        %   pitch meter when the pitch meter applies and the pitch contour
        %   plot is disabled.
        %
        %   The visualization methods can only be used in the MATLAB
        %   environment.
        
        displayPitchContour = false;
        displayPitchMeter = false;
        if nargin > 1
            switch convertStringsToChars(varargin{1})
                case 'pitch contour'
                    displayPitchContour = true;
                case 'pitch meter'
                    displayPitchMeter = true;
            end
        end

        if isempty(varargin) || displayPitchContour
            timeSpan = 5;
            fs = getSampleRate(plugin);
            if isempty(plugin.pScope) || ~isvalid(plugin.pScope)
                plugin.pScope = timescope( ...
                    'SampleRate',fs, ...
                    'TimeSpanSource','property', ...
                    'TimeSpan',timeSpan, ...
                    'YLimits',[-1,1], ...
                    'LayoutDimensions',[2,1], ...
                    'ActiveDisplay',2, ...
                    'YLimits',[50,200], ...
                    'YLabel','Hz');
            end
            show(plugin.pScope);
        end
        
        if isempty(varargin) || displayPitchMeter
            if ~isempty(plugin.poVisualizer) && isvalid(plugin.poVisualizer) && ...
                    isFigureValid(plugin.poVisualizer)
                show(plugin.poVisualizer);
            else
                plugin.poVisualizer = audiopluginexample.private.MeterUI('Pitch Meter','Elapsed Time','Pitch (Hz)');
                update(plugin.poVisualizer,plugin.pPastDecision,plugin.poElapsedTime);
            end
        end

        if nargout>0
            n = 1;
            if isempty(varargin) || displayPitchContour 
                varargout{n} = plugin.pScope;
                n = n+1;
            end
            if isempty(varargin) || displayPitchMeter
                varargout{n} = getFigureHandle(plugin.poVisualizer);
            end
        end
        
    end

    function s = saveobj(obj)
        s = saveobj@audioPlugin(obj);
        s.pBandpassFilter = matlab.System.saveObject(obj.pBandpassFilter);
        s.pFIRInterpolatorFour = matlab.System.saveObject(obj.pFIRInterpolatorFour);
        s.pFIRInterpolatorEight = matlab.System.saveObject(obj.pFIRInterpolatorEight);
        s.pFIRInterpolatorTwelve = matlab.System.saveObject(obj.pFIRInterpolatorTwelve);
        s.pFIRInterpolatorSixteen = matlab.System.saveObject(obj.pFIRInterpolatorSixteen);
        s.pFIRInterpolatorTwenty = matlab.System.saveObject(obj.pFIRInterpolatorTwenty);
        s.pBuffer = matlab.System.saveObject(obj.pBuffer);
        s.LowCutoff = obj.LowCutoff;
        s.HighCutoff = obj.HighCutoff;
        s.AnalysisFrameTime = obj.AnalysisFrameTime;
        s.Overlap = obj.Overlap;
        s.FrequencyThreshold = obj.FrequencyThreshold;
        s.PowerThreshold = obj.PowerThreshold;
        s.ClipLevel = obj.ClipLevel;
        s.DetectionMethod = obj.DetectionMethod;
        s.pNumOldSamplesUnread = obj.pNumOldSamplesUnread;
        s.pSamplesPerAnalysisFrame = obj.pSamplesPerAnalysisFrame;
        s.pSamplesPerHop = obj.pSamplesPerHop;
        s.pSamplesOverlap = obj.pSamplesOverlap;
        s.pBFDFlag = obj.pBFDFlag;
        s.pMaxSamplesOverlap = obj.pMaxSamplesOverlap;
        s.pLastKnownPitch = obj.pLastKnownPitch;
        s.pPastDecision = obj.pPastDecision;
        s.pFrameSize = obj.pFrameSize;
    end
    function obj = reload(obj,s)
        obj = reload@audioPlugin(obj,s);
        obj.LowCutoff = s.LowCutoff;
        obj.HighCutoff = s.HighCutoff;
        obj.AnalysisFrameTime = s.AnalysisFrameTime;
        obj.Overlap = s.Overlap;
        obj.FrequencyThreshold = s.FrequencyThreshold;
        obj.PowerThreshold = s.PowerThreshold;
        obj.ClipLevel = s.ClipLevel;
        obj.DetectionMethod = s.DetectionMethod;
        obj.pNumOldSamplesUnread = s.pNumOldSamplesUnread;
        obj.pSamplesPerAnalysisFrame = s.pSamplesPerAnalysisFrame;
        obj.pSamplesPerHop = s.pSamplesPerHop;
        obj.pSamplesOverlap = s.pSamplesOverlap;
        obj.pBFDFlag = s.pBFDFlag;
        obj.pMaxSamplesOverlap = s.pMaxSamplesOverlap;
        obj.pLastKnownPitch = s.pLastKnownPitch;
        obj.pPastDecision = s.pPastDecision;
        obj.pFrameSize = s.pFrameSize;
        obj.pBandpassFilter = matlab.System.loadobj(s.pBandpassFilter);
        obj.pFIRInterpolatorFour = matlab.System.loadobj(s.pFIRInterpolatorFour);
        obj.pFIRInterpolatorEight = matlab.System.loadobj(s.pFIRInterpolatorEight);
        obj.pFIRInterpolatorTwelve = matlab.System.loadobj(s.pFIRInterpolatorTwelve);
        obj.pFIRInterpolatorSixteen = matlab.System.loadobj(s.pFIRInterpolatorSixteen);
        obj.pFIRInterpolatorTwenty = matlab.System.loadobj(s.pFIRInterpolatorTwenty);
        obj.pBuffer = matlab.System.loadobj(s.pBuffer);
    end

    end
    
    %----------------------------------------------------------------------
    % Private Methods - Used for internal processing
    %----------------------------------------------------------------------
    methods (Access = private)
        
        %------------------------------------------------------------------
        % Perform pitch detection if voiced speech detected
        %------------------------------------------------------------------
        function pD = pitchDetection(plugin,x,pastPitch)
            xFiltered = step(plugin.pBandpassFilter,x(:,1));
            
            pDall = nan(2,1);
            if plugin.DetectionMethod == audioexample.PitchDetectionEnum.Both ...
                    || plugin.DetectionMethod == audioexample.PitchDetectionEnum.Autocorrelation
                fs = getSampleRate(plugin);
                a = plugin.autoCorrelationPitchDecision(xFiltered,fs,...
                    plugin.ClipLevel,plugin.AnalysisFrameTime,plugin.pFIRInterpolatorFour,...
                    plugin.pFIRInterpolatorEight,plugin.pFIRInterpolatorTwelve,...
                    plugin.pFIRInterpolatorSixteen,plugin.pFIRInterpolatorTwenty,...
                    plugin.HighCutoff,plugin.LowCutoff);
                pDall(1) = plugin.penalizeJumps(pastPitch,a,20);
            end
            if plugin.DetectionMethod == audioexample.PitchDetectionEnum.Both || ...
                    plugin.DetectionMethod == audioexample.PitchDetectionEnum.Spectral
                b = spectralPitchDecision(plugin,xFiltered);
                pDall(2) = plugin.penalizeJumps(pastPitch,b,20);
            end
            pD = mean(pDall,'omitnan');
            
            plugin.pLastKnownPitch = pD;
        end
    end
    
    methods (Static, Hidden)
        %------------------------------------------------------------------
        % Drop pitch decisions if over 20 Hz jump from previous decision
        %------------------------------------------------------------------
        function out = penalizeJumps(pastPD,pD,maxJump)
            if ~isnan(pastPD) && ~isnan(pD) && abs(pD-pastPD)>maxJump
                out = nan;
            else
                out = pD;
            end
        end
        
        %------------------------------------------------------------------
        % Detect region of voiced speech
        %------------------------------------------------------------------
        function voiced = isVoicedSpeech(x,fs,pwrThreshold,freqThreshold)
            numSamples    = size(x,1);
            
            pwr = 10*log10(var(x));

            x(x==0)   = [];
            zc        = nnz(diff(sign(x)));
            frequency = (zc*fs)/(2*numSamples);
            
            % The following if-statement contains several redundant
            % scenarios that can be expanded for a more complicated
            % decision weighting.
            if zc < 3
                voiced = false; % Not enough data
            elseif (pwr(1) > pwrThreshold) && (frequency < freqThreshold)
                voiced = true;
            elseif (pwr(1) > pwrThreshold) && (frequency > freqThreshold)
                voiced = false; % Likely unvoiced speech
            elseif (pwr(1) < pwrThreshold) && (frequency < freqThreshold)
                voiced = false; % Likely silence
            elseif (pwr(1) < pwrThreshold) && (frequency > freqThreshold)
                voiced = false; % Likely silence
            else
                voiced = true; % Catch required for compiling to C code.
            end
        end
        
        %------------------------------------------------------------------
        % Auto Correlation Pitch Detection
        %------------------------------------------------------------------
        function decision = autoCorrelationPitchDecision(x,fs,...
                clipLevel,analysisFrameTime,FIRInterpolatorFour,...
                FIRInterpolatorEight,FIRInterpolatorTwelve,...
                FIRInterpolatorSixteen,FIRInterpolatorTwenty,...
                highCutoff,lowCutoff)
            
            % Determine center clip threshold
            third     = round(size(x,1)/3);
            clipLevel = clipLevel/100 * min( max(abs(x(1:third,1)),max(abs(x(end-third-1:end,1)) ) ) );
            
            % Center-clip signal
            x(abs(x)<clipLevel) = 0;

            % Upsample the signal for increased resolution.
            upRough = 1/(4*analysisFrameTime);
            [~,upSwitch] = min(abs(upRough - [4,8,12,16,20]));
            switch upSwitch
                case 1
                    upFactor = 4;
                    x = step(FIRInterpolatorFour,x(:,1));
                case 2
                    upFactor = 8;
                    x = step(FIRInterpolatorEight,x(:,1));
                case 3
                    upFactor = 12;
                    x = step(FIRInterpolatorTwelve,x(:,1));
                case 4
                    upFactor = 16;
                    x = step(FIRInterpolatorSixteen,x(:,1));
                otherwise % case 5
                    upFactor = 20;
                    x = step(FIRInterpolatorTwenty,x(:,1));
            end

            % Perform autocorrelation
            y = xcorr(x);
            
            % Look at second half
            y = y(round(size(y,1)/2):end,1);
            
            % Find zero crossings
            zc = diff( sign(y) );

            start = find(zc~=0);
            if isempty(start)
                decision = nan;
            else
                [~,pitchPeriod] = max( y(start(1):end) );
                pitchPeriod2 = pitchPeriod + start(1) - 1;

                decision = upFactor*fs/pitchPeriod2;
                
                % Sanity check
                if decision > highCutoff || decision < lowCutoff
                    decision = nan;
                end 
            end
        end
    end
    
    methods(Access=private)
        
        %------------------------------------------------------------------
        % Spectral Periodicity Pitch Detection
        %------------------------------------------------------------------
        function decision = spectralPitchDecision(plugin,x)
            %Spectral pitch decision relies on the harmonic structure of
            %voice, so pure tones will fail.

            % Interpolate FFT so each frequency bin is 1 Hz
            X_1 = abs(fft(x,getSampleRate(plugin)));
            
            % Compress FFT by 2 and 3.
            X_2 = X_1(1:2:end,1);
            X_3 = X_1(1:3:end,1);
            
            maxFrequency = plugin.HighCutoff;
            P = X_1(1:maxFrequency,1) + X_2(1:maxFrequency,1) + X_3(1:maxFrequency,1);
            [~,peakLoc] = max(P(51:end,1));

            decision = peakLoc + 50;
        end
    end
    
    %----------------------------------------------------------------------
    % Public Methods - Used to set dependent property
    %----------------------------------------------------------------------
    methods
        function set.AnalysisFrameTime(plugin,val)
            plugin.AnalysisFrameTime  = val;
            plugin.updateSampleBasedProperties();
        end
        function set.Overlap(plugin,val)
            plugin.Overlap = val;
            plugin.updateSampleBasedProperties();
        end
        function updateSampleBasedProperties(plugin)
            fs                              = getSampleRate(plugin);
            plugin.pSamplesPerAnalysisFrame = floor(fs*plugin.AnalysisFrameTime);
            plugin.pSamplesPerHop           = floor(plugin.pSamplesPerAnalysisFrame*(100-plugin.Overlap)/100);
            plugin.pSamplesOverlap          = plugin.pSamplesPerAnalysisFrame - plugin.pSamplesPerHop;
            plugin.pBFDFlag                 = true;
        end
        function set.HighCutoff(plugin,val)
            plugin.HighCutoff                 = val;
            plugin.pBandpassFilter.HighCutoff = val; %#ok<MCSUP>
        end
        function set.LowCutoff(plugin,val)
            plugin.LowCutoff                  = val;
            plugin.pBandpassFilter.LowCutoff  = val; %#ok<MCSUP>
        end
    end

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