Skip to content

Commit

Permalink
Merge pull request #1503 from Wazzledi/master
Browse files Browse the repository at this point in the history
QLC+ 5: Add linear regression to BPM tapping
  • Loading branch information
mcallegari committed Jan 16, 2024
2 parents 243ecc2 + 99e7437 commit ef39a1a
Show file tree
Hide file tree
Showing 3 changed files with 98 additions and 21 deletions.
39 changes: 39 additions & 0 deletions qmlui/js/TimeUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,3 +320,42 @@ function timeToBeatSize(time, bpmNumber, beatsDivision, tickSize)
// tickSize : barDuration = x : time
return (tickSize * time) / barDuration;
}


/**
* Return the average time between two taps given by a list of tap times.
* It caculates the linear regression of the recorded tap times. The slope of the resulting
* linear function represents the average time between two taps.
*/
function calculateBPMByTapIntervals(tapHistory)
{
var tapHistorySorted = []

//reduce size to only 16 taps
while (tapHistory.length > 16) tapHistory.splice(0,1)

//copy tap history to sort it
tapHistorySorted = tapHistory.slice()
tapHistorySorted.sort()

// Find the median time between taps, assume that the tempo is +-40% of this
var tapHistoryMedian = tapHistorySorted[Math.floor(tapHistorySorted.length/2)]

//init needed variables
var n = 1, tapx = 0, tapy = 0, sum_x = 0, sum_y = 0, sum_xx = 0, sum_xy = 0

for (var i = 0; i < tapHistory.length; i++)
{
var intervalMs = tapHistory[i]
n++
// Divide by tapHistoryMedian to determine if a tap was skipped during input
tapx += Math.floor((tapHistoryMedian/2 + intervalMs) / tapHistoryMedian)
tapy += intervalMs
sum_x += tapx
sum_y += tapy
sum_xx += tapx * tapx
sum_xy += tapx * tapy
}

return (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x)
}
33 changes: 27 additions & 6 deletions qmlui/qml/KeyPad.qml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
import QtQuick 2.0
import QtQuick.Layouts 1.1

import "TimeUtils.js" as TimeUtils

import "."

Rectangle
Expand All @@ -32,10 +34,16 @@ Rectangle

property bool showDMXcontrol: true
property bool showTapButton: false
property double tapTimeValue: 0

property alias commandString: commandBox.text
property real itemHeight: Math.max(UISettings.iconSizeDefault, keyPadRoot.height / keyPadGrid.rows) - 3

//needed for bpm tapping
property double tapTimeValue: 0
property int tapCount: 0
property double lastTap: 0
property var tapHistory: []

onVisibleChanged: if (visible) commandBox.selectAndFocus()

signal executeCommand(string cmd)
Expand Down Expand Up @@ -95,18 +103,31 @@ Rectangle
{
tapTimer.stop()
tapButton.border.color = UISettings.bgMedium
tapTimeValue = 0
lastTap = 0
tapHistory = []
}
else
{
var currTime = new Date().getTime()
if (tapTimeValue != 0)

if (lastTap != 0 && currTime - lastTap < 1500)
{
keyPadRoot.tapTimeChanged(currTime - tapTimeValue)
tapTimer.interval = currTime - tapTimeValue
var newTime = currTime - lastTap

tapHistory.push(newTime)

tapTimeValue = TimeUtils.calculateBPMByTapIntervals(tapHistory)

keyPadRoot.tapTimeChanged(tapTimeValue)
tapTimer.interval = tapTimeValue
tapTimer.restart()
}
tapTimeValue = currTime
else
{
lastTap = 0
tapHistory = []
}
lastTap = currTime
}
}
}
Expand Down
47 changes: 32 additions & 15 deletions qmlui/qml/TimeEditTool.qml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ GridLayout

/* The TAP time counter */
property double tapTimeValue: 0
//needed for bpm tapping
property int tapCount: 0
property double lastTap: 0
property var tapHistory: []

/* If needed, this property can be used to recognize which type
of speed value is being edited */
Expand Down Expand Up @@ -177,23 +181,36 @@ GridLayout
onClicked:
{
/* right click resets the current TAP time */
if (mouseButton === Qt.RightButton)
{
tapTimer.stop()
tapButton.border.color = UISettings.bgMedium
tapTimeValue = 0
}
else
{
var currTime = new Date().getTime()
if (tapTimeValue != 0)
if (mouseButton === Qt.RightButton)
{
updateTime(currTime - tapTimeValue, "")
tapTimer.interval = timeValue
tapTimer.restart()
tapTimer.stop()
tapButton.border.color = UISettings.bgMedium
lastTap = 0
tapHistory = []
}
else
{
var currTime = new Date().getTime()

if (lastTap != 0 && currTime - lastTap < 1500)
{
var newTime = currTime - lastTap

tapHistory.push(newTime)

tapTimeValue = TimeUtils.calculateBPMByTapIntervals(tapHistory)

updateTime(tapTimeValue, "")
tapTimer.interval = timeValue
tapTimer.restart()
}
else
{
lastTap = 0
tapHistory = []
}
lastTap = currTime
}
tapTimeValue = currTime
}
}
}

Expand Down

0 comments on commit ef39a1a

Please sign in to comment.