I've got some basic filter code setup (but not yet tested).

Dependencies:   BLE_API Queue mbed nRF51822

Fork of BLE_HeartRate by Bluetooth Low Energy

Revision:
62:8e2fbe131b53
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/match.cpp	Sun Jun 28 03:06:00 2015 +0000
@@ -0,0 +1,856 @@
+/*****************************************************************************
+FILE:  match.cpp
+AUTHOR: Patrick S. Hamilton
+REVISED:    5/13/2002
+  ___________________________________________________________________________
+
+match.cpp: Match beats to previous beats.
+Copywrite (C) 2001 Patrick S. Hamilton
+
+This file is free software; you can redistribute it and/or modify it under
+the terms of the GNU Library General Public License as published by the Free
+Software Foundation; either version 2 of the License, or (at your option) any
+later version.
+
+This software is distributed in the hope that it will be useful, but WITHOUT ANY
+WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+PARTICULAR PURPOSE.  See the GNU Library General Public License for more
+details.
+
+You should have received a copy of the GNU Library General Public License along
+with this library; if not, write to the Free Software Foundation, Inc., 59
+Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+You may contact the author by e-mail (pat@eplimited.edu) or postal mail
+(Patrick Hamilton, E.P. Limited, 35 Medford St., Suite 204 Somerville,
+MA 02143 USA).  For updates to this software, please visit our website
+(http://www.eplimited.com).
+  __________________________________________________________________________
+
+Match.cpp contains functions for managing template matching of beats and
+managing of feature data associated with each beat type.  These
+functions are called functions in classify.cpp.  Beats are matched to
+previoiusly detected beats types based on how well they match point by point
+in a MATCH_LENGTH region centered on FIDMARK (R-wave location).  The following
+is a list of functions that are available for calling by classify.
+
+    ResetMatch -- Resets global variables used in template matching.
+    CompareBeats -- Measures the difference between two beats with
+                            beats scaled to produce the best match.
+    CompareBeats2 -- Measures the difference between two beats without
+                            beat scaling.
+    NewBeatType -- Start a new beat type with the present beat.
+    BestMorphMatch -- Finds the beat template that best matches a new beat.
+    UpdateBeatType -- Updates existing beat template and associated features
+                            based on a new beat.
+    GetDominantType -- Returns the NORMAL beat type that has occorred most often.
+    ClearLastNewType -- Removes the last new beat type from the possible beat
+                            types.
+    DomCompare -- Compares the template for a given beat type to the template
+                        of the dominant normal beat type.
+    DomCompare2 -- Compares a given beat template to the templat of the
+                        dominant normal beat type.
+
+    PostClassify -- Classifies beats based on preceding and following beats
+                        and R-to-R intervals.
+
+    ResetPostClassify -- Resets variables used for post classification.
+
+    CheckPostClass -- Check type classification based on last eight post
+                        classifications.
+
+    CheckPCClass -- Check post beat rhythm classification for the last eight
+                        beats.
+
+A number of simple functions allow access to beat features while maintaining
+some level of encapsulation:
+
+    GetTypesCount -- Returns number of beat types that have been detected.
+    GetBeatTypeCount -- Returns the number of beats of a given type
+                              that have been detected.
+    GetBeatWidth -- Returns the width estimate for a given beat type.
+    SetBeatClass -- Associates a beat classification with a beat type.
+    GetBeatBegin -- Returns the beginning point for a given beat type.
+    GetBeatEnd -- Returns the ending point for a given beat type.
+
+******************************************************************************/
+//#include <stdlib.h>
+//#include <stdio.h>
+#include <mbed.h>
+#include "ecgcodes.h"
+
+#include "bdac.h"
+#define MATCH_LENGTH    BEAT_MS300  // Number of points used for beat matching.
+#define MATCH_LIMIT 1.2         // Match limit used testing whether two
+                                            // beat types might be combined.
+#define COMBINE_LIMIT   0.8     // Limit used for deciding whether two types
+                                            // can be combined.
+
+#define MATCH_START (FIDMARK-(MATCH_LENGTH/2))  // Starting point for beat matching
+#define MATCH_END   (FIDMARK+(MATCH_LENGTH/2))      // End point for beat matching.
+#define MAXPREV 8   // Number of preceeding beats used as beat features.
+#define MAX_SHIFT   BEAT_MS40
+
+// Local prototypes.
+
+int NoiseCheck(int *beat) ;
+double CompareBeats(int *beat1, int *beat2, int *shiftAdj) ;
+double CompareBeats2(int *beat1, int *beat2, int *shiftAdj) ;
+void UpdateBeat(int *aveBeat, int *newBeat, int shift) ;
+void BeatCopy(int srcBeat, int destBeat) ;
+int MinimumBeatVariation(int type) ;
+
+// External prototypes.
+
+void AnalyzeBeat(int *beat, int *onset, int *offset, int *isoLevel,
+    int *beatBegin, int *beatEnd, int *amp) ;
+void AdjustDomData(int oldType, int newType) ;
+void CombineDomData(int oldType, int newType) ;
+
+// Global variables.
+
+int BeatTemplates[MAXTYPES][BEATLGTH] ;
+int BeatCounts[MAXTYPES] ;
+int BeatWidths[MAXTYPES] ;
+int BeatClassifications[MAXTYPES] ;
+int BeatBegins[MAXTYPES] ;
+int BeatEnds[MAXTYPES] ;
+int BeatsSinceLastMatch[MAXTYPES] ;
+int BeatAmps[MAXTYPES] ;
+int BeatCenters[MAXTYPES] ;
+double MIs[MAXTYPES][8] ;
+
+// Need access to these in postclas.cpp when beat types are combined
+// and moved.
+
+extern int PostClass[MAXTYPES][8] ;
+extern int PCRhythm[MAXTYPES][8] ;
+
+int TypeCount = 0 ;
+
+/***************************************************************************
+ResetMatch() resets static variables involved with template matching.
+****************************************************************************/
+
+void ResetMatch(void)
+    {
+    int i, j ;
+    TypeCount = 0 ;
+    for(i = 0; i < MAXTYPES; ++i)
+        {
+        BeatCounts[i] = 0 ;
+        BeatClassifications[i] = UNKNOWN ;
+        for(j = 0; j < 8; ++j)
+            {
+            MIs[i][j] = 0 ;
+            }
+        }
+    }
+
+/**************************************************************************
+    CompareBeats() takes two beat buffers and compares how well they match
+    point-by-point.  Beat2 is shifted and scaled to produce the closest
+    possible match.  The metric returned is the sum of the absolute
+    differences between beats divided by the amplitude of the beats.  The
+    shift used for the match is returned via the pointer *shiftAdj.
+***************************************************************************/
+
+#define MATCH_START (FIDMARK-(MATCH_LENGTH/2))
+#define MATCH_END   (FIDMARK+(MATCH_LENGTH/2))
+
+double CompareBeats(int *beat1, int *beat2, int *shiftAdj)
+    {
+    int i, max, min, magSum, shift ;
+    long beatDiff, meanDiff, minDiff, minShift ;
+    double metric, scaleFactor, tempD ;
+
+    // Calculate the magnitude of each beat.
+
+    max = min = beat1[MATCH_START] ;
+    for(i = MATCH_START+1; i < MATCH_END; ++i)
+        if(beat1[i] > max)
+            max = beat1[i] ;
+        else if(beat1[i] < min)
+            min = beat1[i] ;
+
+    magSum = max - min ;
+
+    i = MATCH_START ;
+    max = min = beat2[i] ;
+    for(i = MATCH_START+1; i < MATCH_END; ++i)
+        if(beat2[i] > max)
+            max = beat2[i] ;
+        else if(beat2[i] < min)
+            min = beat2[i] ;
+
+    // magSum += max - min ;
+    scaleFactor = magSum ;
+    scaleFactor /= max-min ;
+    magSum *= 2 ;
+
+    // Calculate the sum of the point-by-point
+    // absolute differences for five possible shifts.
+
+    for(shift = -MAX_SHIFT; shift <= MAX_SHIFT; ++shift)
+        {
+        for(i = FIDMARK-(MATCH_LENGTH>>1), meanDiff = 0;
+            i < FIDMARK + (MATCH_LENGTH>>1); ++i)
+            {
+            tempD = beat2[i+shift] ;
+            tempD *= scaleFactor ;
+            meanDiff += beat1[i]- tempD ; // beat2[i+shift] ;
+            }
+        meanDiff /= MATCH_LENGTH ;
+
+        for(i = FIDMARK-(MATCH_LENGTH>>1), beatDiff = 0;
+            i < FIDMARK + (MATCH_LENGTH>>1); ++i)
+            {
+            tempD = beat2[i+shift] ;
+            tempD *= scaleFactor ;
+            beatDiff += abs(beat1[i] - meanDiff- tempD) ; // beat2[i+shift]  ) ;
+            }
+
+
+        if(shift == -MAX_SHIFT)
+            {
+            minDiff = beatDiff ;
+            minShift = -MAX_SHIFT ;
+            }
+        else if(beatDiff < minDiff)
+            {
+            minDiff = beatDiff ;
+            minShift = shift ;
+            }
+        }
+
+    metric = minDiff ;
+    *shiftAdj = minShift ;
+    metric /= magSum ;
+
+    // Metric scales inversely with match length.
+    // algorithm was originally tuned with a match
+    // length of 30.
+
+    metric *= 30 ;
+    metric /= MATCH_LENGTH ;
+    return(metric) ;
+    }
+
+/***************************************************************************
+    CompareBeats2 is nearly the same as CompareBeats above, but beat2 is
+    not scaled before calculating the match metric.  The match metric is
+    then the sum of the absolute differences divided by the average amplitude
+    of the two beats.
+****************************************************************************/
+
+double CompareBeats2(int *beat1, int *beat2, int *shiftAdj)
+    {
+    int i, max, min, shift ;
+    int mag1, mag2 ;
+    long beatDiff, meanDiff, minDiff, minShift ;
+    double metric ;
+
+    // Calculate the magnitude of each beat.
+
+    max = min = beat1[MATCH_START] ;
+    for(i = MATCH_START+1; i < MATCH_END; ++i)
+        if(beat1[i] > max)
+            max = beat1[i] ;
+        else if(beat1[i] < min)
+            min = beat1[i] ;
+
+    mag1 = max - min ;
+
+    i = MATCH_START ;
+    max = min = beat2[i] ;
+    for(i = MATCH_START+1; i < MATCH_END; ++i)
+        if(beat2[i] > max)
+            max = beat2[i] ;
+        else if(beat2[i] < min)
+            min = beat2[i] ;
+
+    mag2 = max-min ;
+
+    // Calculate the sum of the point-by-point
+    // absolute differences for five possible shifts.
+
+    for(shift = -MAX_SHIFT; shift <= MAX_SHIFT; ++shift)
+        {
+        for(i = FIDMARK-(MATCH_LENGTH>>1), meanDiff = 0;
+            i < FIDMARK + (MATCH_LENGTH>>1); ++i)
+            meanDiff += beat1[i]- beat2[i+shift] ;
+        meanDiff /= MATCH_LENGTH ;
+
+        for(i = FIDMARK-(MATCH_LENGTH>>1), beatDiff = 0;
+            i < FIDMARK + (MATCH_LENGTH>>1); ++i)
+            beatDiff += abs(beat1[i] - meanDiff- beat2[i+shift]) ; ;
+
+        if(shift == -MAX_SHIFT)
+            {
+            minDiff = beatDiff ;
+            minShift = -MAX_SHIFT ;
+            }
+        else if(beatDiff < minDiff)
+            {
+            minDiff = beatDiff ;
+            minShift = shift ;
+            }
+        }
+
+    metric = minDiff ;
+    *shiftAdj = minShift ;
+    metric /= (mag1+mag2) ;
+
+    // Metric scales inversely with match length.
+    // algorithm was originally tuned with a match
+    // length of 30.
+
+    metric *= 30 ;
+    metric /= MATCH_LENGTH ;
+
+    return(metric) ;
+    }
+
+/************************************************************************
+UpdateBeat() averages a new beat into an average beat template by adding
+1/8th of the new beat to 7/8ths of the average beat.
+*************************************************************************/
+
+void UpdateBeat(int *aveBeat, int *newBeat, int shift)
+    {
+    int i ;
+    long tempLong ;
+
+    for(i = 0; i < BEATLGTH; ++i)
+        {
+        if((i+shift >= 0) && (i+shift < BEATLGTH))
+            {
+            tempLong = aveBeat[i] ;
+            tempLong *= 7 ;
+            tempLong += newBeat[i+shift] ;
+            tempLong >>= 3 ;
+            aveBeat[i] = tempLong ;
+            }
+        }
+    }
+
+/*******************************************************
+    GetTypesCount returns the number of types that have
+    been detected.
+*******************************************************/
+
+int GetTypesCount(void)
+    {
+    return(TypeCount) ;
+    }
+
+/********************************************************
+    GetBeatTypeCount returns the number of beats of a
+    a particular type have been detected.
+********************************************************/
+
+int GetBeatTypeCount(int type)
+    {
+    return(BeatCounts[type]) ;
+    }
+
+/*******************************************************
+    GetBeatWidth returns the QRS width estimate for
+    a given type of beat.
+*******************************************************/
+int GetBeatWidth(int type)
+    {
+    return(BeatWidths[type]) ;
+    }
+
+/*******************************************************
+    GetBeatCenter returns the point between the onset and
+    offset of a beat.
+********************************************************/
+
+int GetBeatCenter(int type)
+    {
+    return(BeatCenters[type]) ;
+    }
+
+/*******************************************************
+    GetBeatClass returns the present classification for
+    a given beat type (NORMAL, PVC, or UNKNOWN).
+********************************************************/
+
+int GetBeatClass(int type)
+    {
+    if(type == MAXTYPES)
+        return(UNKNOWN) ;
+    return(BeatClassifications[type]) ;
+    }
+
+/******************************************************
+    SetBeatClass sets up a beat classifation for a
+    given type.
+******************************************************/
+
+void SetBeatClass(int type, int beatClass)
+    {
+    BeatClassifications[type] = beatClass ;
+    }
+
+/******************************************************************************
+    NewBeatType starts a new beat type by storing the new beat and its
+    features as the next available beat type.
+******************************************************************************/
+
+int NewBeatType(int *newBeat )
+    {
+    int i, onset, offset, isoLevel, beatBegin, beatEnd ;
+    int mcType, amp ;
+
+    // Update count of beats since each template was matched.
+
+    for(i = 0; i < TypeCount; ++i)
+        ++BeatsSinceLastMatch[i] ;
+
+    if(TypeCount < MAXTYPES)
+        {
+        for(i = 0; i < BEATLGTH; ++i)
+            BeatTemplates[TypeCount][i] = newBeat[i] ;
+
+        BeatCounts[TypeCount] = 1 ;
+        BeatClassifications[TypeCount] = UNKNOWN ;
+        AnalyzeBeat(&BeatTemplates[TypeCount][0],&onset,&offset, &isoLevel,
+            &beatBegin, &beatEnd, &amp) ;
+        BeatWidths[TypeCount] = offset-onset ;
+        BeatCenters[TypeCount] = (offset+onset)/2 ;
+        BeatBegins[TypeCount] = beatBegin ;
+        BeatEnds[TypeCount] = beatEnd ;
+        BeatAmps[TypeCount] = amp ;
+
+        BeatsSinceLastMatch[TypeCount] = 0 ;
+
+        ++TypeCount ;
+        return(TypeCount-1) ;
+        }
+
+    // If we have used all the template space, replace the beat
+    // that has occurred the fewest number of times.
+
+    else
+        {
+        // Find the template with the fewest occurances,
+        // that hasn't been matched in at least 500 beats.
+
+        mcType = -1 ;
+
+        if(mcType == -1)
+            {
+            mcType = 0 ;
+            for(i = 1; i < MAXTYPES; ++i)
+                if(BeatCounts[i] < BeatCounts[mcType])
+                    mcType = i ;
+                else if(BeatCounts[i] == BeatCounts[mcType])
+                    {
+                    if(BeatsSinceLastMatch[i] > BeatsSinceLastMatch[mcType])
+                        mcType = i ;
+                    }
+            }
+
+        // Adjust dominant beat monitor data.
+
+        AdjustDomData(mcType,MAXTYPES) ;
+
+        // Substitute this beat.
+
+        for(i = 0; i < BEATLGTH; ++i)
+            BeatTemplates[mcType][i] = newBeat[i] ;
+
+        BeatCounts[mcType] = 1 ;
+        BeatClassifications[mcType] = UNKNOWN ;
+        AnalyzeBeat(&BeatTemplates[mcType][0],&onset,&offset, &isoLevel,
+            &beatBegin, &beatEnd, &amp) ;
+        BeatWidths[mcType] = offset-onset ;
+        BeatCenters[mcType] = (offset+onset)/2 ;
+        BeatBegins[mcType] = beatBegin ;
+        BeatEnds[mcType] = beatEnd ;
+        BeatsSinceLastMatch[mcType] = 0 ;
+      BeatAmps[mcType] = amp ;
+        return(mcType) ;
+        }
+    }
+
+/***************************************************************************
+    BestMorphMatch tests a new beat against all available beat types and
+    returns (via pointers) the existing type that best matches, the match
+    metric for that type, and the shift used for that match.
+***************************************************************************/
+
+void BestMorphMatch(int *newBeat,int *matchType,double *matchIndex, double *mi2,
+    int *shiftAdj)
+    {
+    int type, i, bestMatch, nextBest, minShift, shift, temp ;
+    int bestShift2, nextShift2 ;
+    double bestDiff2, nextDiff2;
+    double beatDiff, minDiff, nextDiff=10000 ;
+
+    if(TypeCount == 0)
+        {
+        *matchType = 0 ;
+        *matchIndex = 1000 ;        // Make sure there is no match so a new beat is
+        *shiftAdj = 0 ;         // created.
+        return ;
+        }
+
+    // Compare the new beat to all type beat
+    // types that have been saved.
+
+    for(type = 0; type < TypeCount; ++type)
+        {
+        beatDiff = CompareBeats(&BeatTemplates[type][0],newBeat,&shift) ;
+        if(type == 0)
+            {
+            bestMatch = 0 ;
+            minDiff = beatDiff ;
+            minShift = shift ;
+            }
+        else if(beatDiff < minDiff)
+            {
+            nextBest = bestMatch ;
+            nextDiff = minDiff ;
+            bestMatch = type ;
+            minDiff = beatDiff ;
+            minShift = shift ;
+            }
+        else if((TypeCount > 1) && (type == 1))
+            {
+            nextBest = type ;
+            nextDiff = beatDiff ;
+            }
+        else if(beatDiff < nextDiff)
+            {
+            nextBest = type ;
+            nextDiff = beatDiff ;
+            }
+        }
+
+    // If this beat was close to two different
+    // templates, see if the templates which template
+    // is the best match when no scaling is used.
+    // Then check whether the two close types can be combined.
+
+    if((minDiff < MATCH_LIMIT) && (nextDiff < MATCH_LIMIT) && (TypeCount > 1))
+        {
+        // Compare without scaling.
+
+        bestDiff2 = CompareBeats2(&BeatTemplates[bestMatch][0],newBeat,&bestShift2) ;
+        nextDiff2 = CompareBeats2(&BeatTemplates[nextBest][0],newBeat,&nextShift2) ;
+        if(nextDiff2 < bestDiff2)
+            {
+            temp = bestMatch ;
+            bestMatch = nextBest ;
+            nextBest = temp ;
+            temp = minDiff ;
+            minDiff = nextDiff ;
+            nextDiff = temp ;
+            minShift = nextShift2 ;
+            *mi2 = bestDiff2 ;
+            }
+        else *mi2 = nextDiff2 ;
+
+        beatDiff = CompareBeats(&BeatTemplates[bestMatch][0],&BeatTemplates[nextBest][0],&shift) ;
+
+        if((beatDiff < COMBINE_LIMIT) &&
+            ((*mi2 < 1.0) || (!MinimumBeatVariation(nextBest))))
+            {
+
+            // Combine beats into bestMatch
+
+            if(bestMatch < nextBest)
+                {
+                for(i = 0; i < BEATLGTH; ++i)
+                    {
+                    if((i+shift > 0) && (i + shift < BEATLGTH))
+                        {
+                        BeatTemplates[bestMatch][i] += BeatTemplates[nextBest][i+shift] ;
+                        BeatTemplates[bestMatch][i] >>= 1 ;
+                        }
+                    }
+
+                if((BeatClassifications[bestMatch] == NORMAL) || (BeatClassifications[nextBest] == NORMAL))
+                    BeatClassifications[bestMatch] = NORMAL ;
+                else if((BeatClassifications[bestMatch] == PVC) || (BeatClassifications[nextBest] == PVC))
+                    BeatClassifications[bestMatch] = PVC ;
+
+                BeatCounts[bestMatch] += BeatCounts[nextBest] ;
+
+                CombineDomData(nextBest,bestMatch) ;
+
+                // Shift other templates over.
+
+                for(type = nextBest; type < TypeCount-1; ++type)
+                    BeatCopy(type+1,type) ;
+
+                }
+
+            // Otherwise combine beats it nextBest.
+
+            else
+                {
+                for(i = 0; i < BEATLGTH; ++i)
+                    {
+                    BeatTemplates[nextBest][i] += BeatTemplates[bestMatch][i] ;
+                    BeatTemplates[nextBest][i] >>= 1 ;
+                    }
+
+                if((BeatClassifications[bestMatch] == NORMAL) || (BeatClassifications[nextBest] == NORMAL))
+                    BeatClassifications[nextBest] = NORMAL ;
+                else if((BeatClassifications[bestMatch] == PVC) || (BeatClassifications[nextBest] == PVC))
+                    BeatClassifications[nextBest] = PVC ;
+
+                BeatCounts[nextBest] += BeatCounts[bestMatch] ;
+
+                CombineDomData(bestMatch,nextBest) ;
+
+                // Shift other templates over.
+
+                for(type = bestMatch; type < TypeCount-1; ++type)
+                    BeatCopy(type+1,type) ;
+
+
+                bestMatch = nextBest ;
+                }
+            --TypeCount ;
+            BeatClassifications[TypeCount] = UNKNOWN ;
+            }
+        }
+    *mi2 = CompareBeats2(&BeatTemplates[bestMatch][0],newBeat,&bestShift2) ;
+    *matchType = bestMatch ;
+    *matchIndex = minDiff ;
+    *shiftAdj = minShift ;
+    }
+
+/***************************************************************************
+    UpdateBeatType updates the beat template and features of a given beat type
+    using a new beat.
+***************************************************************************/
+
+void UpdateBeatType(int matchType,int *newBeat, double mi2,
+     int shiftAdj)
+    {
+    int i,onset,offset, isoLevel, beatBegin, beatEnd ;
+    int amp ;
+
+    // Update beats since templates were matched.
+
+    for(i = 0; i < TypeCount; ++i)
+        {
+        if(i != matchType)
+            ++BeatsSinceLastMatch[i] ;
+        else BeatsSinceLastMatch[i] = 0 ;
+        }
+
+    // If this is only the second beat, average it with the existing
+    // template.
+
+    if(BeatCounts[matchType] == 1)
+        for(i = 0; i < BEATLGTH; ++i)
+            {
+            if((i+shiftAdj >= 0) && (i+shiftAdj < BEATLGTH))
+                BeatTemplates[matchType][i] = (BeatTemplates[matchType][i] + newBeat[i+shiftAdj])>>1 ;
+            }
+
+    // Otherwise do a normal update.
+
+    else
+        UpdateBeat(&BeatTemplates[matchType][0], newBeat, shiftAdj) ;
+
+    // Determine beat features for the new average beat.
+
+    AnalyzeBeat(&BeatTemplates[matchType][0],&onset,&offset,&isoLevel,
+        &beatBegin, &beatEnd, &amp) ;
+
+    BeatWidths[matchType] = offset-onset ;
+    BeatCenters[matchType] = (offset+onset)/2 ;
+    BeatBegins[matchType] = beatBegin ;
+    BeatEnds[matchType] = beatEnd ;
+    BeatAmps[matchType] = amp ;
+
+    ++BeatCounts[matchType] ;
+
+    for(i = MAXPREV-1; i > 0; --i)
+        MIs[matchType][i] = MIs[matchType][i-1] ;
+    MIs[matchType][0] = mi2 ;
+
+    }
+
+
+/****************************************************************************
+    GetDominantType returns the NORMAL beat type that has occurred most
+    frequently.
+****************************************************************************/
+
+int GetDominantType(void)
+    {
+    int maxCount = 0, maxType = -1 ;
+    int type, totalCount ;
+
+    for(type = 0; type < MAXTYPES; ++type)
+        {
+        if((BeatClassifications[type] == NORMAL) && (BeatCounts[type] > maxCount))
+            {
+            maxType = type ;
+            maxCount = BeatCounts[type] ;
+            }
+        }
+
+    // If no normals are found and at least 300 beats have occurred, just use
+    // the most frequently occurring beat.
+
+    if(maxType == -1)
+        {
+        for(type = 0, totalCount = 0; type < TypeCount; ++type)
+            totalCount += BeatCounts[type] ;
+        if(totalCount > 300)
+            for(type = 0; type < TypeCount; ++type)
+                if(BeatCounts[type] > maxCount)
+                    {
+                    maxType = type ;
+                    maxCount = BeatCounts[type] ;
+                    }
+        }
+
+    return(maxType) ;
+    }
+
+
+/***********************************************************************
+    ClearLastNewType removes the last new type that was initiated
+************************************************************************/
+
+void ClearLastNewType(void)
+    {
+    if(TypeCount != 0)
+        --TypeCount ;
+    }
+
+/****************************************************************
+    GetBeatBegin returns the offset from the R-wave for the
+    beginning of the beat (P-wave onset if a P-wave is found).
+*****************************************************************/
+
+int GetBeatBegin(int type)
+    {
+    return(BeatBegins[type]) ;
+    }
+
+/****************************************************************
+    GetBeatEnd returns the offset from the R-wave for the end of
+    a beat (T-wave offset).
+*****************************************************************/
+
+int GetBeatEnd(int type)
+    {
+    return(BeatEnds[type]) ;
+    }
+
+int GetBeatAmp(int type)
+    {
+    return(BeatAmps[type]) ;
+    }
+
+
+/************************************************************************
+    DomCompare2 and DomCompare return similarity indexes between a given
+    beat and the dominant normal type or a given type and the dominant
+    normal type.
+************************************************************************/
+
+double DomCompare2(int *newBeat, int domType)
+    {
+    int shift ;
+    return(CompareBeats2(&BeatTemplates[domType][0],newBeat,&shift)) ;
+    }
+
+double DomCompare(int newType, int domType)
+    {
+    int shift ;
+    return(CompareBeats2(&BeatTemplates[domType][0],&BeatTemplates[newType][0],
+        &shift)) ;
+    }
+
+/*************************************************************************
+BeatCopy copies beat data from a source beat to a destination beat.
+*************************************************************************/
+
+void BeatCopy(int srcBeat, int destBeat)
+    {
+    int i ;
+
+    // Copy template.
+
+    for(i = 0; i < BEATLGTH; ++i)
+        BeatTemplates[destBeat][i] = BeatTemplates[srcBeat][i] ;
+
+    // Move feature information.
+
+    BeatCounts[destBeat] = BeatCounts[srcBeat] ;
+    BeatWidths[destBeat] = BeatWidths[srcBeat] ;
+    BeatCenters[destBeat] = BeatCenters[srcBeat] ;
+    for(i = 0; i < MAXPREV; ++i)
+        {
+        PostClass[destBeat][i] = PostClass[srcBeat][i] ;
+        PCRhythm[destBeat][i] = PCRhythm[srcBeat][i] ;
+        }
+
+    BeatClassifications[destBeat] = BeatClassifications[srcBeat] ;
+    BeatBegins[destBeat] = BeatBegins[srcBeat] ;
+    BeatEnds[destBeat] = BeatBegins[srcBeat] ;
+    BeatsSinceLastMatch[destBeat] = BeatsSinceLastMatch[srcBeat];
+    BeatAmps[destBeat] = BeatAmps[srcBeat] ;
+
+    // Adjust data in dominant beat monitor.
+
+    AdjustDomData(srcBeat,destBeat) ;
+    }
+
+/********************************************************************
+    Minimum beat variation returns a 1 if the previous eight beats
+    have all had similarity indexes less than 0.5.
+*********************************************************************/
+
+int MinimumBeatVariation(int type)
+    {
+    int i ;
+    for(i = 0; i < MAXTYPES; ++i)
+        if(MIs[type][i] > 0.5)
+            i = MAXTYPES+2 ;
+    if(i == MAXTYPES)
+        return(1) ;
+    else return(0) ;
+    }
+
+/**********************************************************************
+    WideBeatVariation returns true if the average similarity index
+    for a given beat type to its template is greater than WIDE_VAR_LIMIT.
+***********************************************************************/
+
+#define WIDE_VAR_LIMIT  0.50
+
+int WideBeatVariation(int type)
+    {
+    int i, n ;
+    double aveMI ;
+
+    n = BeatCounts[type] ;
+    if(n > 8)
+        n = 8 ;
+
+    for(i = 0, aveMI = 0; i <n; ++i)
+        aveMI += MIs[type][i] ;
+
+    aveMI /= n ;
+    if(aveMI > WIDE_VAR_LIMIT)
+        return(1) ;
+    else return(0) ;
+    }
+
+
+