/********************************************************************

PhatBeat - A simple beat-counter for producers, DJs, or anybody else.
Copyright (C) 2004 Jack Chatterton <chattj AT postreal.org>

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
02111-1307, USA.

*********************************************************************/

// These are more or less a bunch of functions usable by any beat counter.

// The goal is to have no includes here, as we want this class
// usable by *any* beatcounter frontend. Of course, header 
// include is ok.

#include "BeatCounter.h"

// Some global (to class) variables.
// Any and all constants are defined in BeatCounter.h
double beatholder[MAX_BEATS];
double beatdifferences[MAX_BEATS];
int timesig;
int nextbeat;

BeatCounter::BeatCounter(int sig) {
	timesig = sig;
	nextbeat = 0;
}

BeatCounter::~BeatCounter() {
	// Nothing happens.
}

void BeatCounter::AddBeat(double time) {
	if (nextbeat <= MAX_BEATS) {		
		beatholder[nextbeat] = time;
		nextbeat++;
	} 
	else { }
}

// Creates an array of (effective) size nextbeat - 1 filled with 
// the period of time in between each beat.
// This is a private function, should never even be needed by
// external applications.
void getBeatDifferences() {
       	for(int i = 0; i < (MAX_BEATS - 1); i++) 
		beatdifferences[i] = beatholder[i + 1] - beatholder[i];
}

// Sorts the beatdifferences array. Insertion sort should be fine.
void sortBeatDifferences(int low, int high) {
	int i, j;
	double temp;
	for (i = low + 1; i <= high; i++) {
		temp = beatdifferences[i];
		j = i;
		while ((j > low) && (beatdifferences[j-1] > temp)) {
			beatdifferences[j] = beatdifferences[j-1];
			j--;
		}
		beatdifferences[j] = temp;
	}
}	

// Get the last difference that will count -- i.e. any that has 
// something else to difference with.
double BeatCounter::getLastDiff() {
	if (nextbeat >= 2)
		return(beatholder[nextbeat - 1] - beatholder[nextbeat - 2]);
	return 0;
}

// Internal fn to get a BPM given whatever kind of avg, whether
// it is raw, or an interquartile mean, or whatever.
// Notice, avg must be measured in seconds for this to work.
double getBPM(double avg) {
	// 60 seconds in 1 minute.
	return( 60 / avg );
}

// Bleh, this doesn't remove any outliers at all, giving a fairly
// poor estimate of the actual BPM.
double getRA() {
	getBeatDifferences();
	double total = 0;
	// Goes to nextbeat - 1 as the last time difference, which is
	// between a real time value and 0, must not be counted.
	for(int i = 0; i < (nextbeat - 1); i++) 
		total += beatdifferences[i];
	total = total / (nextbeat - 1);
	return total;
}

// Returns the interquartile mean, internally.
double getIQM() {
	getBeatDifferences();
	sortBeatDifferences(0, nextbeat-2);
	int start = (nextbeat / 4);
	int end = ((3*(nextbeat-1)) / 4);
	
	// Conservatively knocks another value off of the end of
	// what is included. In taking eightbeat counts this makes
	// for two outliers removed from each side.
	if(end > 0)
		end--;
	
	int counter = 0;
	double total = 0;
	for(int i = start; i <= end; i++) {
		total += beatdifferences[i];
		counter++;
	}
	return(total / counter);
}

// Return the interquartile mean to the user.
double BeatCounter::getIQM_BPM() {
	return(getBPM(getIQM()));
}

// Get the Raw Average method BPM. Crappay.
double BeatCounter::getRA_BPM() {
	return(getBPM(getRA()));
}

// (s / measure) = (s / beat) * (beat / measure)
double BeatCounter::getMeasureSecs() {
	return( getRA() * timesig );
}
