// Discards 'outlier' beats in a desperate attempt to obtain a better estimate of the beats and BPM.
// Outlier beats are identified as those beats which have 'fewer than expected' taps, where 'fewer
// than expected' is defined as less than a third of the median.
//
// It's an ad hoc estimation because interquartile range and other similar statistical outlier detection
// techniques seem to work better with human supervision, at least in this particular application.
//
// The clustering weights for the initial estimates are not retained in the Beats struct so the
// heuristics should only be used when the forgetting factor is zero i.e. all taps are equally
// weighted.
Discards 'outlier' beats in a desperate attempt to obtain a better estimate of the beats and BPM.
Outlier beats are identified as those beats which have 'fewer than expected' taps, where 'fewer
than expected' is defined as less than a third of the median.

It's an ad hoc estimation because interquartile range and other similar statistical outlier detection
techniques seem to work better with human supervision, at least in this particular application.

The clustering weights for the initial estimates are not retained in the Beats struct so the
heuristics should only be used when the forgetting factor is zero i.e. all taps are equally
weighted.
// Discards ' outlier ' beats in a desperate attempt to obtain a better estimate of the beats and BPM . // Outlier beats are identified as those beats which have ' fewer than expected ' taps where ' fewer // than expected ' is defined as less than a third of the median . // // It ' s an ad hoc estimation because interquartile range and other similar statistical outlier detection // techniques seem to work better with human supervision at least in this particular application . // // The clustering weights for the initial estimates are not retained in the Beats struct so the // heuristics should only be used when the forgetting factor is zero i . e . all taps are equally // weighted .
func (beats *Beats) Clean() (Beats, error) {
	taps := []int{}
	for _, beat := range beats.Beats {
		if N := len(beat.Taps); N > 0 {
			taps = append(taps, N)
		}
	}
	sort.Ints(taps)
	N := len(taps)
	median := 1.0
	switch {
	case N == 0:
		median = 1.0
	case N%2 == 0:
		median = float64(taps[N/2-1]+taps[N/2+1]) / 2.0
	default:
		median = float64(taps[N/2])
	}
	fence := median / 3.0
	data := []float64{}
	for _, beat := range beats.Beats {
		if float64(len(beat.Taps)) >= fence {
			for _, t := range beat.Taps {
				data = append(data, t.Seconds())
			}
		}
	}
	weights := make([]float64, len(data))
	for i := 0; i < len(weights); i++ {
		weights[i] = 1.0
	}
	clusters := ckmeans.CKMeans1dDp(data, weights)
	cleaned := make([]Beat, len(clusters))
	for i, cluster := range clusters {
		cleaned[i] = makeBeat(cluster.Center, cluster)
	}
	BPM, offset := bpm(cleaned)
	sort.SliceStable(cleaned, func(i, j int) bool { return cleaned[i].At < cleaned[j].At })
	result := Beats{
		BPM:    BPM,
		Offset: offset,
		Beats:  cleaned,
	}
	if len(cleaned) > 0 {
		variance := 0.0
		for _, b := range cleaned {
			variance += b.Variance.Seconds()
		}
		variance = variance / float64(len(cleaned))
		result.Variance = &variance
	}
	return result, nil
}
func ( beats * Beats ) Clean ( ) ( Beats , error ) { taps := [ ] int { } for _ , beat := range beats . Beats { if N := len ( beat . Taps ) ; N > 0 { taps = append ( taps , N ) } } sort . Ints ( taps ) N := len ( taps ) median := 1.0 switch { case N == 0 : median = 1.0 case N % 2 == 0 : median = float64 ( taps [ N / 2 - 1 ] + taps [ N / 2 + 1 ] ) / 2.0 default : median = float64 ( taps [ N / 2 ] ) } fence := median / 3.0 data := [ ] float64 { } for _ , beat := range beats . Beats { if float64 ( len ( beat . Taps ) ) >= fence { for _ , t := range beat . Taps { data = append ( data , t . Seconds ( ) ) } } } weights := make ( [ ] float64 , len ( data ) ) for i := 0 ; i < len ( weights ) ; i ++ { weights [ i ] = 1.0 } clusters := ckmeans . CKMeans1dDp ( data , weights ) cleaned := make ( [ ] Beat , len ( clusters ) ) for i , cluster := range clusters { cleaned [ i ] = makeBeat ( cluster . Center , cluster ) } BPM , offset := bpm ( cleaned ) sort . SliceStable ( cleaned , func ( i , j int ) bool { return cleaned [ i ] . At < cleaned [ j ] . At } ) result := Beats { BPM : BPM , Offset : offset , Beats : cleaned , } if len ( cleaned ) > 0 { variance := 0.0 for _ , b := range cleaned { variance += b . Variance . Seconds ( ) } variance = variance / float64 ( len ( cleaned ) ) result . Variance = & variance } return result , nil }
Discards 'outlier' beats in a desperate attempt to obtain a better estimate of the beats and BPM.
Discards ' outlier ' beats in a desperate attempt to obtain a better estimate of the beats and BPM .
// ... calculate the median taps per beat // ... discard any beats with 'too few taps'