Coverage Summary for Class: AbstractPolyrhythm (com.vsevolodganin.clicktrack.model)
Class |
Method, %
|
Branch, %
|
Line, %
|
Instruction, %
|
AbstractPolyrhythm |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/12)
|
AbstractPolyrhythm$Column |
0%
(0/1)
|
|
0%
(0/1)
|
0%
(0/12)
|
Total |
0%
(0/2)
|
|
0%
(0/2)
|
0%
(0/24)
|
package com.vsevolodganin.clicktrack.model
import com.vsevolodganin.clicktrack.utils.collection.toRoundRobin
import com.vsevolodganin.clicktrack.utils.math.Rational
import com.vsevolodganin.clicktrack.utils.math.ZERO
import com.vsevolodganin.clicktrack.utils.math.compareTo
import com.vsevolodganin.clicktrack.utils.math.lcm
import com.vsevolodganin.clicktrack.utils.math.min
import com.vsevolodganin.clicktrack.utils.math.minus
import com.vsevolodganin.clicktrack.utils.math.plus
/**
* Polyrhythm is represented as a grid with the length equal to least common multiple of all input patterns.
*/
data class AbstractPolyrhythm(val columns: List<Column>, val untilFirst: Rational) {
data class Column(val indices: List<Int>, val untilNext: Rational)
}
fun AbstractPolyrhythm(pattern1: List<NoteEvent>, pattern2: List<NoteEvent>): AbstractPolyrhythm {
val resultingPattern = mutableListOf<AbstractPolyrhythm.Column>()
val commonLength = lcm(pattern1.length, pattern2.length)
val pattern1Commonized = pattern1.resize(commonLength)
val pattern2Commonized = pattern2.resize(commonLength)
val pattern1InitialRestLength = pattern1.initialRestLength()
val pattern2InitialRestLength = pattern2.initialRestLength()
var pattern1RunningLength = pattern1InitialRestLength
var pattern2RunningLength = pattern2InitialRestLength
val pattern1NonRestLengths = pattern1Commonized.continuousNonRestLengths()
val pattern2NonRestLengths = pattern2Commonized.continuousNonRestLengths()
val pattern1NonRestLengthsIterator = pattern1NonRestLengths.iterator()
val pattern2NonRestLengthsIterator = pattern2NonRestLengths.iterator()
while (pattern1RunningLength < commonLength || pattern2RunningLength < commonLength) {
when {
pattern1RunningLength < pattern2RunningLength -> {
val pattern1EventLength = pattern1NonRestLengthsIterator.next()
resultingPattern += AbstractPolyrhythm.Column(
indices = listOf(0),
untilNext = min(pattern1EventLength, pattern2RunningLength - pattern1RunningLength),
)
pattern1RunningLength += pattern1EventLength
}
pattern2RunningLength < pattern1RunningLength -> {
val pattern2EventLength = pattern2NonRestLengthsIterator.next()
resultingPattern += AbstractPolyrhythm.Column(
indices = listOf(1),
untilNext = min(pattern2EventLength, pattern1RunningLength - pattern2RunningLength),
)
pattern2RunningLength += pattern2EventLength
}
else -> {
val pattern1EventLength = pattern1NonRestLengthsIterator.next()
val pattern2EventLength = pattern2NonRestLengthsIterator.next()
resultingPattern += AbstractPolyrhythm.Column(
indices = listOf(0, 1),
untilNext = min(pattern1EventLength, pattern2EventLength),
)
pattern1RunningLength += pattern1EventLength
pattern2RunningLength += pattern2EventLength
}
}
}
return AbstractPolyrhythm(
columns = resultingPattern,
untilFirst = min(pattern1InitialRestLength, pattern2InitialRestLength),
)
}
private fun List<NoteEvent>.resize(length: Rational): List<NoteEvent> {
if (isEmpty()) return emptyList()
val roundSequence = toRoundRobin().iterator()
val result = mutableListOf<NoteEvent>()
var runningLength = Rational.ZERO
while (runningLength < length) {
val next = roundSequence.next()
result += next
runningLength += next.length
}
return result
}
private fun List<NoteEvent>.continuousNonRestLengths(): List<Rational> {
val result = mutableListOf<Rational>()
var nonRestEvent: NoteEvent? = null
var runningLength = Rational.ZERO
for (event in this) {
when (event.type) {
NoteEvent.Type.NOTE -> {
nonRestEvent?.let { result += runningLength }
nonRestEvent = event
runningLength = event.length
}
NoteEvent.Type.REST -> {
runningLength += event.length
}
}
}
nonRestEvent?.let { result += runningLength }
return result
}
private fun List<NoteEvent>.initialRestLength(): Rational {
return indexOfFirst { it.type != NoteEvent.Type.REST }.takeIf { it >= 0 }
?.let { indexOfFirstNonRest -> subList(0, indexOfFirstNonRest).length }
?: Rational.ZERO
}
private val List<NoteEvent>.length: Rational get() = fold(Rational.ZERO) { acc, event -> acc + event.length }