Coverage Summary for Class: BpmMeter (com.vsevolodganin.clicktrack.metronome)
Class |
Class, %
|
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
BpmMeter |
0%
(0/1)
|
0%
(0/4)
|
0%
(0/14)
|
0%
(0/19)
|
0%
(0/117)
|
package com.vsevolodganin.clicktrack.metronome
import com.vsevolodganin.clicktrack.model.BeatsPerMinute
import me.tatarka.inject.annotations.Inject
import kotlin.time.Clock
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.ExperimentalTime
@Inject
class BpmMeter {
private val samples: Array<Long?> = Array(HISTORY_SIZE) { null }
private var index: Int = 0
fun addTap() {
val now = now()
samples[index] = now
index = (index + 1) % HISTORY_SIZE
}
fun calculateBpm(): BeatsPerMinute? {
val now = now()
val applicableSamples = samples
.asSequence()
.filterNotNull()
.filter { now - it < TIME_WINDOW_SIZE_MILLIS }
.toList()
.takeIf { it.size >= MIN_COUNT_TO_DERIVE_BPM }
?: return null
val minTime = applicableSamples.minOrNull() ?: return null
val maxTime = applicableSamples.maxOrNull() ?: return null
return BeatsPerMinute(
beatCount = applicableSamples.size - 1,
timelapse = (maxTime - minTime).milliseconds,
)
}
@OptIn(ExperimentalTime::class)
private fun now() = Clock.System.now().toEpochMilliseconds()
}
private const val TIME_WINDOW_SIZE_MILLIS = 3000L
private const val HISTORY_SIZE = 10
private const val MIN_COUNT_TO_DERIVE_BPM = 3