aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Visualization/vis_avs/bpm.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Visualization/vis_avs/bpm.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/Plugins/Visualization/vis_avs/bpm.cpp')
-rw-r--r--Src/Plugins/Visualization/vis_avs/bpm.cpp711
1 files changed, 711 insertions, 0 deletions
diff --git a/Src/Plugins/Visualization/vis_avs/bpm.cpp b/Src/Plugins/Visualization/vis_avs/bpm.cpp
new file mode 100644
index 00000000..1ab80f79
--- /dev/null
+++ b/Src/Plugins/Visualization/vis_avs/bpm.cpp
@@ -0,0 +1,711 @@
+/*
+ LICENSE
+ -------
+Copyright 2005 Nullsoft, Inc.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+ * Neither the name of Nullsoft nor the names of its contributors may be used to
+ endorse or promote products derived from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+*/
+#include <windows.h>
+#include <commctrl.h>
+#include <math.h>
+#include <stdio.h>
+#include "draw.h"
+#include "wnd.h"
+#include "r_defs.h"
+#include "render.h"
+#include "vis.h"
+#include "cfgwnd.h"
+#include "resource.h"
+#include "bpm.h"
+#include "../Agave/Language/api_language.h"
+
+int refineBeat(int isBeat);
+BOOL TCHistStep(BeatType *t, DWORD _Avg, int *_halfDiscriminated, int *_hdPos, DWORD *_lastTC, DWORD TC, int Type);
+void InsertHistStep(BeatType *t, DWORD TC, int Type, int i);
+void CalcBPM(void);
+BOOL ReadyToLearn(void);
+BOOL ReadyToGuess(void);
+void doubleBeat(void);
+void halfBeat(void);
+void ResetAdapt(void);
+void SliderStep(int Ctl, int *slide);
+void initBpm(void);
+extern int g_fakeinit;
+
+int cfg_smartbeat=0;
+int cfg_smartbeatsticky=1;
+int cfg_smartbeatresetnewsong=1;
+int cfg_smartbeatonlysticky=0;
+int sticked=0;
+int arbVal, skipVal; // Values of arbitrary beat and beat skip
+int Bpm, Confidence, Confidence1, Confidence2; // Calculated BPM (realtime), Confidence computation
+DWORD lastTC; // Last beat Tick count
+DWORD lastTC2; // Last beat tick count 2
+BeatType TCHist[8]; // History of last 8 beats
+BeatType TCHist2[8]; // History of last 8 beats
+int Smoother[8]; // History of last 8 Bpm values, used to smooth changes
+int halfDiscriminated[8]; // Discriminated beats table
+int halfDiscriminated2[8]; // Discriminated beats table
+int hdPos; // Position in discrimination table
+int hdPos2; // Position in discrimination table
+int smPtr, smSize; // Smoother pointer and size
+int TCHistPtr; // Tick count history pointer
+int TCHistSize; // Tick count history size
+int offIMax; // Max divisor/multiplier used to guess/discriminate beats
+int lastBPM; // Last calculated BPM, used by the smoother to detect new entry
+int insertionCount; // Remembers how many beats were guessed
+DWORD predictionLastTC; // Last tick count of guessed/accepted beat
+DWORD Avg; // Average tick count interval between beats
+DWORD Avg2; // Average tick count interval between beats
+int skipCount; // Beat counter used by beat skipper
+int inInc, outInc; // +1/-1, Used by the nifty beatsynced sliders
+int inSlide, outSlide; // Positions of sliders
+int oldInSlide, oldOutSlide; // Used by timer to detect changes in sliders
+int oldsticked; // Used by timer to detect changes in sticked state
+char txt[256]; // Contains txt about current BPM and confidence
+int halfCount, doubleCount; // Counter used to autodetect if double/half beat needed
+int TCUsed; // Remembers how many beats in the history were actually used for computation
+int predictionBpm; // Contains BPM actually used to prediction (eliminates Bpm driftings)
+int oldDisplayBpm, oldDisplayConfidence; // Detects stuff to redraw
+int bestConfidence; // Best confidence we had so far
+char lastSongName[256]; // Last song name, used to detect song changes in winamp
+HWND winampWnd; // Winamp window
+int forceNewBeat; // Force new bpm adoption
+int betterConfidenceCount; // Used to decide when to adpot new beat
+int topConfidenceCount; // Used to decide when to adpot new beat
+int stickyConfidenceCount; // Used to decided when to go sticky
+BOOL doResyncBpm=FALSE;
+
+// configuration dialog stuff
+BOOL CALLBACK DlgProc_Bpm(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
+{
+switch (uMsg)
+ {
+ case WM_INITDIALOG:
+ inInc = 1;
+ outInc = 1;
+ inSlide = 0;
+ outSlide = 0;
+ oldDisplayBpm=-1;
+ oldDisplayConfidence=-1;
+ oldInSlide=-1;
+ oldOutSlide=-1;
+ if (cfg_smartbeat) CheckDlgButton(hwndDlg,IDC_BPMADV,BST_CHECKED); else CheckDlgButton(hwndDlg,IDC_BPMSTD,BST_CHECKED);
+ if (cfg_smartbeatsticky) CheckDlgButton(hwndDlg,IDC_STICKY,BST_CHECKED);
+ if (cfg_smartbeatresetnewsong) CheckDlgButton(hwndDlg,IDC_NEWRESET,BST_CHECKED); else CheckDlgButton(hwndDlg,IDC_NEWADAPT,BST_CHECKED);
+ if (cfg_smartbeatonlysticky) CheckDlgButton(hwndDlg,IDC_ONLYSTICKY,BST_CHECKED);
+ SendDlgItemMessage(hwndDlg, IDC_IN, TBM_SETTICFREQ, 1, 0);
+ SendDlgItemMessage(hwndDlg, IDC_IN, TBM_SETRANGE, TRUE, MAKELONG(0, 8));
+ SendDlgItemMessage(hwndDlg, IDC_OUT, TBM_SETTICFREQ, 1, 0);
+ SendDlgItemMessage(hwndDlg, IDC_OUT, TBM_SETRANGE, TRUE, MAKELONG(0, 8));
+ if (predictionBpm)
+ {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_STICK), sticked ? SW_HIDE : SW_NORMAL);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_UNSTICK), sticked ? SW_NORMAL : SW_HIDE);
+ }
+ else
+ {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_STICK), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_UNSTICK), SW_HIDE);
+ }
+/* ShowWindow(GetDlgItem(hwndDlg, IDC_CURBPM), cfg_smartbeat ? SW_NORMAL : SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CURCONF), cfg_smartbeat ? SW_NORMAL : SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BPM), cfg_smartbeat ? SW_NORMAL : SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CONFIDENCE), cfg_smartbeat ? SW_NORMAL : SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_RESET), cfg_smartbeat ? SW_NORMAL : SW_HIDE);*/
+ SetTimer(hwndDlg, 0, 50, NULL);
+ return 1;
+ case WM_TIMER:
+ {
+ if (oldInSlide != inSlide) {
+ SendDlgItemMessage(hwndDlg, IDC_IN, TBM_SETPOS, TRUE, inSlide); oldInSlide=inSlide; }
+ if (oldOutSlide != outSlide) {
+ SendDlgItemMessage(hwndDlg, IDC_OUT, TBM_SETPOS, TRUE, outSlide); oldOutSlide=outSlide; }
+ if (oldDisplayBpm != predictionBpm || oldsticked != sticked) {
+ char lBuf[16];
+ wsprintf(txt, predictionBpm ? "%d%s"/*/%d"*/ : WASABI_API_LNGSTRING_BUF(IDS_LEARNING,lBuf,16), predictionBpm, cfg_smartbeatsticky && sticked ? WASABI_API_LNGSTRING(IDS_GOT_IT) : ""/*, Bpm*/);
+ SetDlgItemText(hwndDlg, IDC_BPM, txt);
+ oldDisplayBpm=predictionBpm;
+ oldsticked=sticked;
+ if (predictionBpm)
+ {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_STICK), sticked ? SW_HIDE : SW_NORMAL);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_UNSTICK), sticked ? SW_NORMAL : SW_HIDE);
+ }
+ else
+ {
+ ShowWindow(GetDlgItem(hwndDlg, IDC_STICK), SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_UNSTICK), SW_HIDE);
+ }
+ }
+ if (oldDisplayConfidence != Confidence) {
+ wsprintf(txt, "%d%%"/* (%d%%/%d%% - %d)"*/, Confidence/*, Confidence1, Confidence2, TCUsed*/);
+ SetDlgItemText(hwndDlg, IDC_CONFIDENCE, txt);
+ oldDisplayConfidence=Confidence;
+ }
+ }
+ return 0;
+ case WM_COMMAND:
+ if ((LOWORD(wParam) == IDC_BPMSTD) ||
+ (LOWORD(wParam) == IDC_BPMADV) ||
+ (LOWORD(wParam) == IDC_NEWRESET) ||
+ (LOWORD(wParam) == IDC_NEWADAPT) ||
+ (LOWORD(wParam) == IDC_ONLYSTICKY) ||
+ (LOWORD(wParam) == IDC_STICKY))
+ {
+ cfg_smartbeat=IsDlgButtonChecked(hwndDlg,IDC_BPMADV)?1:0;
+ cfg_smartbeatsticky=IsDlgButtonChecked(hwndDlg,IDC_STICKY)?1:0;
+ cfg_smartbeatresetnewsong=IsDlgButtonChecked(hwndDlg,IDC_NEWRESET)?1:0;
+ cfg_smartbeatonlysticky=IsDlgButtonChecked(hwndDlg,IDC_ONLYSTICKY)?1:0;
+ oldsticked=-1;
+/* ShowWindow(GetDlgItem(hwndDlg, IDC_CURBPM), cfg_smartbeat ? SW_NORMAL : SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CURCONF), cfg_smartbeat ? SW_NORMAL : SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_BPM), cfg_smartbeat ? SW_NORMAL : SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_CONFIDENCE), cfg_smartbeat ? SW_NORMAL : SW_HIDE);
+ ShowWindow(GetDlgItem(hwndDlg, IDC_RESET), cfg_smartbeat ? SW_NORMAL : SW_HIDE);*/
+ }
+ if (LOWORD(wParam) == IDC_2X)
+ doubleBeat();
+ if (LOWORD(wParam) == IDC_DIV2)
+ halfBeat();
+ if (LOWORD(wParam) == IDC_RESET)
+ ResetAdapt();
+ if (LOWORD(wParam) == IDC_STICK)
+ {
+ sticked=1;
+ stickyConfidenceCount=0;
+ }
+ if (LOWORD(wParam) == IDC_UNSTICK)
+ {
+ sticked=0;
+ stickyConfidenceCount=0;
+ }
+ return 0;
+ case WM_DESTROY:
+ KillTimer(hwndDlg, 0);
+ return 0;
+ }
+return 0;
+}
+
+void initBpm(void)
+{
+ if (g_fakeinit) return;
+ TCUsed=0;
+ *txt=0;
+ betterConfidenceCount=0;
+ topConfidenceCount=0;
+ forceNewBeat=0;
+ hdPos=0;
+ hdPos2=0;
+ inSlide=0;
+ outSlide=0;
+ oldDisplayBpm=-1;
+ oldDisplayConfidence=-1;
+ oldInSlide=0;
+ oldOutSlide=0;
+ Bpm = 0;
+ Avg = 0;
+ Avg2 = 0;
+ smPtr = 0;
+ smSize = 8;
+ offIMax = 8;
+ insertionCount = 0;
+ predictionLastTC = 0;
+ Confidence = 0;
+ Confidence1 = 0;
+ Confidence2 = 0;
+ halfCount=0;
+ doubleCount=0;
+ TCHistSize = 8;
+ predictionBpm=0;
+ lastTC=GetTickCount();
+ stickyConfidenceCount=0;
+ memset(TCHist, 0, TCHistSize*sizeof(BeatType));
+ memset(Smoother, 0, smSize*sizeof(int));
+ memset(halfDiscriminated, 0, TCHistSize*sizeof(int));
+ memset(halfDiscriminated2, 0, TCHistSize*sizeof(int));
+ winampWnd = FindWindow("Winamp v1.x", NULL);
+ *lastSongName=0;
+ sticked=0;
+ oldsticked=-1;
+}
+
+BOOL songChanged(DWORD TCNow)
+{
+static DWORD lastCheck=0;
+if (TCNow > lastCheck+1000)
+ {
+ char songName[256];
+ lastCheck=TCNow;
+ GetWindowText(winampWnd, songName, 255);
+ if (strcmp(songName, lastSongName))
+ {
+ strcpy(lastSongName, songName);
+ return TRUE;
+ }
+ }
+return FALSE;
+}
+
+void ResetAdapt(void)
+{
+// Reset adaptive learning
+*txt=0;
+TCUsed=0;
+hdPos=0;
+Avg = 0;
+Confidence=0;
+Confidence1=0;
+Confidence2=0;
+betterConfidenceCount=0;
+topConfidenceCount=0;
+Bpm = 0;
+smPtr = 0;
+smSize = 8;
+offIMax = 8;
+insertionCount = 0;
+predictionLastTC = 0;
+halfCount=0;
+doubleCount=0;
+TCHistSize = 8;
+predictionBpm=0;
+bestConfidence=0;
+lastTC=GetTickCount();
+sticked=0;
+oldsticked=-1;
+stickyConfidenceCount=0;
+memset(TCHist, 0, TCHistSize*sizeof(BeatType));
+memset(TCHist2, 0, TCHistSize*sizeof(BeatType));
+memset(Smoother, 0, smSize*sizeof(int));
+memset(halfDiscriminated, 0, TCHistSize*sizeof(int));
+}
+
+// Insert a beat in history table. May be either real beat or guessed
+void InsertHistStep(BeatType *t, DWORD TC, int Type, int i)
+{
+if (i >= TCHistSize) return;
+if (t == TCHist && insertionCount < TCHistSize*2) insertionCount++;
+memmove(t+i+1, t+i, sizeof(BeatType)*(TCHistSize-(i+1)));
+t[0].TC = TC;
+t[0].Type = Type;
+}
+
+// Doubles current beat
+void doubleBeat(void)
+{
+int i;
+int iv[8];
+
+if (sticked && Bpm > MIN_BPM) return;
+
+for (i=0;i<TCHistSize-1;i++)
+ iv[i] = TCHist[i].TC - TCHist[i+1].TC;
+
+for (i=1;i<TCHistSize;i++)
+ TCHist[i].TC = TCHist[i-1].TC-iv[i-1]/2;
+
+Avg /= 2;
+Bpm *= 2;
+doubleCount=0;
+memset(Smoother, 0, smSize*sizeof(int));
+memset(halfDiscriminated, 0, TCHistSize*sizeof(int));
+//forceNewBeat=1;
+}
+
+// Halfs current beat
+void halfBeat(void)
+{
+int i;
+int iv[8];
+
+if (sticked && Bpm < MIN_BPM) return;
+
+for (i=0;i<TCHistSize-1;i++)
+ iv[i] = TCHist[i].TC - TCHist[i+1].TC;
+
+for (i=1;i<TCHistSize;i++)
+ TCHist[i].TC = TCHist[i-1].TC-iv[i-1]*2;
+
+Avg *= 2;
+Bpm /= 2;
+halfCount=0;
+memset(Smoother, 0, smSize*sizeof(int));
+memset(halfDiscriminated, 0, TCHistSize*sizeof(int));
+}
+
+// Called whenever isBeat was true in render
+BOOL TCHistStep(BeatType *t, DWORD _Avg, int *_halfDiscriminated, int *_hdPos, DWORD *_lastTC, DWORD TC, int Type)
+{
+int i=0;
+int offI;
+DWORD thisLen;
+BOOL learning = ReadyToLearn();
+thisLen = TC-lastTC;
+
+// If this beat is sooner than half the average - 20%, throw it away
+if (thisLen < Avg/2 - Avg*0.2)
+ {
+ if (learning)
+ {
+ if (labs(Avg - (TC - t[1].TC)) < labs(Avg - (t[0].TC - t[1].TC)))
+ {
+/* if (predictionLastTC && t[0].Type == BEAT_GUESSED && Type == BEAT_REAL)
+ predictionLastTC += (TC - t[0].TC)/2;*/
+ t[0].TC = TC;
+ t[0].Type = Type;
+ return TRUE;
+ }
+ }
+ return FALSE;
+ }
+
+if (learning)
+ for (offI=2;offI<offIMax;offI++) // Try to see if this beat is in the middle of our current Bpm, or maybe 1/3, 1/4 etc... to offIMax
+ if ((float)labs((Avg/offI)-thisLen) < (float)(Avg/offI)*0.2)
+ {
+ _halfDiscriminated[(*_hdPos)++]=1; // Should test if offI==2 before doing that, but seems to have better results ? I'll have to investigate this
+ (*_hdPos)%=8;
+ return FALSE;
+ }
+
+// This beat is accepted, so set this discrimination entry to false
+_halfDiscriminated[hdPos++]=0;
+(*_hdPos)%=8;
+
+// Check if we missed some beats
+/*if (learning && thisLen > 1000 || (float)abs(Avg-thisLen) > (float)Avg*0.3)
+ for (offI=2;offI<offIMax;offI++)
+ {
+ if ((float)abs((Avg*offI)-thisLen) < (float)Avg*0.2)
+ {
+ for (j=1;j<offI;j++) // Oh yeah we definitly did, add one!
+ InsertHistStep(TC - (Avg*j), BEAT_GUESSED, offI-1); // beat has been guessed so report it so confidence can be calculated
+ break;
+ }
+ }*/
+
+
+// Remember this tick count
+*_lastTC = TC;
+// Insert this beat.
+InsertHistStep(t, TC, Type, 0);
+return TRUE;
+}
+
+// Am i ready to learn ?
+BOOL ReadyToLearn(void)
+{
+int i;
+for (i=0;i<TCHistSize;i++)
+ if (TCHist[i].TC==0) return FALSE;
+return TRUE;
+}
+
+// Am i ready to guess ?
+BOOL ReadyToGuess(void)
+{
+return insertionCount == TCHistSize*2;
+}
+
+void newBpm(int thisBpm)
+{
+Smoother[smPtr++] = thisBpm;
+smPtr %= smSize;
+}
+
+int GetBpm(void)
+{
+int i;
+int smN=0;
+int smSum=0;
+// Calculate smoothed Bpm
+for (i=0;i<smSize;i++)
+ if (Smoother[i] > 0) {
+ smSum += Smoother[i];
+ smN++;
+ }
+if (smN) return smSum / smN;
+return 0;
+}
+
+// Calculate BPM according to beat history
+void CalcBPM(void)
+{
+int i;
+int hdCount=0;
+int r=0;
+int totalTC=0, totalN=0;
+float rC, etC;
+int v;
+double sc=0;
+int mx=0;
+float et;
+int smSum=0, smN=0;
+
+if (!ReadyToLearn())
+ return;
+
+// First calculate average beat
+for (i=0;i<TCHistSize-1;i++)
+ totalTC += TCHist[i].TC - TCHist[i+1].TC;
+
+Avg = totalTC/(TCHistSize-1);
+
+// Count how many of then are real as opposed to guessed
+for (i=0;i<TCHistSize;i++)
+ if (TCHist[i].Type == BEAT_REAL)
+ r++;
+
+// Calculate part 1 of confidence
+rC = (float)min((float)((float)r / (float)TCHistSize) * 2, 1);
+
+// Calculate typical drift
+for (i=0;i<TCHistSize-1;i++)
+ {
+ v = TCHist[i].TC - TCHist[i+1].TC;
+ mx = max(mx, v);
+ sc += v*v;
+ }
+et = (float)sqrt(sc / (TCHistSize-1) - Avg*Avg);
+// Calculate confidence based on typical drift and max derivation
+etC = 1 - ((float)et / (float)mx);
+
+// Calculate confidence
+Confidence = max(0, (int)(((rC * etC) * 100.0) - 50) * 2);
+Confidence1 = (int)(rC * 100);
+Confidence2 = (int)(etC * 100);
+
+// Now apply second layer, recalculate average using only beats within range of typical drift
+// Also, count how many of them we are keeping
+totalTC=0;
+for (i=0;i<TCHistSize-1;i++)
+ {
+ v += TCHist[i].TC - TCHist[i+1].TC;
+ if (labs(Avg-v) < et)
+ {
+ totalTC += v;
+ totalN++;
+ v = 0;
+ }
+ else
+ if ((float)v > Avg)
+ v = 0;
+ }
+TCUsed = totalN;
+// If no beat was within typical drift (how would it be possible? well lets cover our ass) then keep the simple
+// average calculated earlier, else recalculate average of beats within range
+if (totalN)
+ Avg = totalTC/totalN;
+
+if (ReadyToGuess())
+ {
+ if (Avg) // Avg = 0 ? Ahem..
+ Bpm = 60000 / Avg;
+
+
+ if (Bpm != lastBPM)
+ {
+ newBpm(Bpm); // If realtime Bpm has changed since last time, then insert it in the smoothing tab;e
+ lastBPM = Bpm;
+
+ if (cfg_smartbeatsticky && predictionBpm && Confidence >= ((predictionBpm < 90) ? STICKY_THRESHOLD_LOW : STICKY_THRESHOLD))
+ {
+ stickyConfidenceCount++;
+ if (stickyConfidenceCount >= MIN_STICKY)
+ sticked=1;
+ }
+ else
+ stickyConfidenceCount=0;
+ }
+
+ Bpm = GetBpm();
+
+ // Count how many beats we discriminated
+ for (i=0;i<TCHistSize;i++)
+ if (halfDiscriminated[i]) hdCount++;
+
+ if (hdCount >= TCHistSize/2) // If we removed at least half of our beats, then we are off course. We should double our bpm
+ {
+ if (Bpm * 2 < MAX_BPM) // Lets do so only if the doubled bpm is < MAX_BPM
+ {
+ doubleBeat();
+ memset(halfDiscriminated, 0, TCHistSize*sizeof(int)); // Reset discrimination table
+ }
+ }
+ if (Bpm > 500 || Bpm < 0)
+ {
+ ResetAdapt();
+ }
+ if (Bpm < MIN_BPM)
+ {
+ if (++doubleCount > 4) // We're going too slow, lets double our bpm
+ doubleBeat();
+ }
+ else
+ doubleCount=0;
+ if (Bpm > MAX_BPM) // We're going too fast, lets slow our bpm by a factor of 2
+ {
+ if (++halfCount > 4)
+ halfBeat();
+ }
+ else
+ halfCount=0;
+ }
+}
+
+void SliderStep(int Ctl, int *slide)
+{
+*slide += Ctl == IDC_IN ? inInc : outInc;
+if (!*slide || *slide == 8) (Ctl == IDC_IN ? inInc : outInc) *= -1;
+}
+
+// render function
+// render should return 0 if it only used framebuffer, or 1 if the new output data is in fbout. this is
+// used when you want to do something that you'd otherwise need to make a copy of the framebuffer.
+// w and h are the width and height of the screen, in pixels.
+// isBeat is 1 if a beat has been detected.
+// visdata is in the format of [spectrum:0,wave:1][channel][band].
+
+int refineBeat(int isBeat)
+{
+ BOOL accepted=FALSE;
+ BOOL predicted=FALSE;
+ BOOL resyncin=FALSE;
+ BOOL resyncout=FALSE;
+
+ if (isBeat) // Show the beat received from AVS
+ SliderStep(IDC_IN, &inSlide);
+
+ DWORD TCNow = GetTickCount();
+
+ if (songChanged(TCNow))
+ {
+ bestConfidence=(int)((float)bestConfidence*0.5);
+ sticked=0;
+ stickyConfidenceCount=0;
+ if (cfg_smartbeatresetnewsong)
+ ResetAdapt();
+ }
+
+ // Try to predict if this frame should be a beat
+ if (Bpm && TCNow > predictionLastTC + (60000 / Bpm))
+ predicted = TRUE;
+
+
+ if (isBeat) // If it is a real beat, do discrimination/guessing and computations, then see if it is accepted
+ accepted = TCHistStep(TCHist, Avg, halfDiscriminated, &hdPos, &lastTC, TCNow, BEAT_REAL);
+
+ // Calculate current Bpm
+ CalcBPM();
+
+ // If prediction Bpm has not yet been set
+ // or if prediction bpm is too high or too low
+ // or if 3/4 of our history buffer contains beats within the range of typical drift
+ // the accept the calculated Bpm as the new prediction Bpm
+ // This allows keeping the beat going on when the music fades out, and readapt to the new beat as soon as
+ // the music fades in again
+ if ((accepted || predicted) && !sticked && (!predictionBpm || predictionBpm > MAX_BPM || predictionBpm < MIN_BPM))
+ {
+ if (Confidence >= bestConfidence)
+ {
+/* betterConfidenceCount++;
+ if (!predictionBpm || betterConfidenceCount == BETTER_CONF_ADOPT)
+ {*/
+ forceNewBeat=1;
+/* betterConfidenceCount=0;
+ }*/
+ }
+ if (Confidence >= 50)
+ {
+ topConfidenceCount++;
+ if (topConfidenceCount == TOP_CONF_ADOPT)
+ {
+ forceNewBeat=1;
+ topConfidenceCount=0;
+ }
+ }
+ if (forceNewBeat)
+ {
+ forceNewBeat=0;
+ bestConfidence = Confidence;
+ predictionBpm=Bpm;
+ }
+ }
+
+ if (!sticked) predictionBpm = Bpm;
+ Bpm=predictionBpm;
+
+
+/* resync = (predictionBpm &&
+ (predictionLastTC < TCNow - (30000/predictionBpm) - (60000/predictionBpm)*0.2) ||
+ (predictionLastTC < TCNow - (30000/predictionBpm) - (60000/predictionBpm)*0.2));*/
+ if (predictionBpm && accepted && !predicted)
+ {
+ int b=0;
+ if (TCNow > predictionLastTC + (60000 / predictionBpm)*0.7)
+ {
+ resyncin = TRUE;
+ b = (int)((float)predictionBpm * 1.01);
+ }
+ if (TCNow < predictionLastTC + (60000 / predictionBpm)*0.3)
+ {
+ resyncout = TRUE;
+ b = (int)((float)predictionBpm * 0.98);
+ }
+ if (!sticked && doResyncBpm && (resyncin || resyncout))
+ {
+ newBpm(b);
+ predictionBpm = GetBpm();
+ }
+ }
+
+ if (resyncin)
+ {
+ predictionLastTC = TCNow;
+ SliderStep(IDC_OUT, &outSlide);
+ doResyncBpm=TRUE;
+ return ((cfg_smartbeat && !cfg_smartbeatonlysticky) || (cfg_smartbeat && cfg_smartbeatonlysticky && sticked)) ? 1 : isBeat;
+ }
+ if (predicted)
+ {
+ predictionLastTC = TCNow;
+ if (Confidence > 25) TCHistStep(TCHist, Avg, halfDiscriminated, &hdPos, &lastTC, TCNow, BEAT_GUESSED);
+ SliderStep(IDC_OUT, &outSlide);
+ doResyncBpm=FALSE;
+ return ((cfg_smartbeat && !cfg_smartbeatonlysticky) || (cfg_smartbeat && cfg_smartbeatonlysticky && sticked)) ? 1 : isBeat;
+ }
+ if (resyncout)
+ {
+ predictionLastTC = TCNow;
+ doResyncBpm=TRUE;
+ return ((cfg_smartbeat && !cfg_smartbeatonlysticky) || (cfg_smartbeat && cfg_smartbeatonlysticky && sticked)) ? 0 : isBeat;
+ }
+
+ return ((cfg_smartbeat && !cfg_smartbeatonlysticky) || (cfg_smartbeat && cfg_smartbeatonlysticky && sticked)) ? (predictionBpm ? 0 : isBeat) : isBeat;
+}
+
+
+