Attach lyrics on midi export; stz loop midi
This commit is contained in:
parent
81515a40f1
commit
d5d8068c24
4 changed files with 246 additions and 24 deletions
|
|
@ -53,7 +53,7 @@ class MidiWriterKotlin constructor(private val fileRepository: FileRepository)
|
|||
tick = 0
|
||||
pitches.forEach {
|
||||
if (it.metaType > 0) {
|
||||
val metaDataString = String(it.metaBytes, Charsets.ISO_8859_1)
|
||||
val metaDataString = String(it.metaBytes, Charsets.UTF_8)
|
||||
addMetaMessage(it.metaType, it.tick, it.metaByteSize, metaDataString)
|
||||
} else if (it.pitch != "") {
|
||||
addNote(it.voiceNumber, it.pitch.toInt(), 100, it.tick.toLong())
|
||||
|
|
|
|||
|
|
@ -68,6 +68,106 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
|||
sequence.addControlChange(channel, ctrl, valByte, tickPosition)
|
||||
}
|
||||
|
||||
//val dcGridIndex = (dcMarker.gridIndex ?: 0)+1
|
||||
//val endRepeatGridIndex = (fineMarker?.gridIndex ?: dcGridIndex)+1
|
||||
/**
|
||||
* Déplie séquentiellement tous les sauts (D.C. et D.S. / DSFin) dans l'ordre chronologique.
|
||||
*/
|
||||
fun expandDaCapoAndSegno(
|
||||
initialPitches: List<MidiPitch>,
|
||||
initialMarkers: List<MidiMarkers>
|
||||
): Pair<List<MidiPitch>, List<MidiMarkers>> {
|
||||
val isDC = Regex("""D\.?C\.?""", RegexOption.IGNORE_CASE)
|
||||
val isDS = Regex("""D\.?S\.?""", RegexOption.IGNORE_CASE)
|
||||
val isFine = Regex("""\b(fine|fin|farany|end)\b""", RegexOption.IGNORE_CASE)
|
||||
|
||||
var currentPitches = initialPitches
|
||||
var currentMarkers = initialMarkers
|
||||
|
||||
var maxIterations = 20
|
||||
|
||||
while (maxIterations > 0) {
|
||||
maxIterations--
|
||||
|
||||
val sortedMarkers = currentMarkers.sortedBy { it.gridIndex ?: 0 }
|
||||
val candidateJumpMarker = sortedMarkers.firstOrNull { m ->
|
||||
m.marker.any { text ->
|
||||
val clean = text.trim()
|
||||
isDC.containsMatchIn(clean) || isDS.containsMatchIn(clean)
|
||||
}
|
||||
} ?: break
|
||||
|
||||
val candidateGridIdx = candidateJumpMarker.gridIndex ?: 0
|
||||
val jumpGridIndex = candidateGridIdx + 1
|
||||
val jumpRawTick = jumpGridIndex * 480L
|
||||
val isDsJump = candidateJumpMarker.marker.any { text -> isDS.containsMatchIn(text.trim()) }
|
||||
|
||||
currentMarkers = currentMarkers.map { m ->
|
||||
if (m === candidateJumpMarker || (m.gridIndex == candidateGridIdx && m.marker == candidateJumpMarker.marker)) {
|
||||
m.copy(marker = m.marker.map { text ->
|
||||
text.replace(Regex("""D\.?C\.?""", RegexOption.IGNORE_CASE), "")
|
||||
.replace(Regex("""D\.?S\.?""", RegexOption.IGNORE_CASE), "")
|
||||
})
|
||||
} else {
|
||||
m
|
||||
}
|
||||
}
|
||||
|
||||
val startRepeatGridIndex = if (isDsJump) {
|
||||
val segnoMarker = currentMarkers.lastOrNull { m ->
|
||||
(m.gridIndex ?: 0) <= jumpGridIndex && m.marker.any { it.contains("$") }
|
||||
}
|
||||
segnoMarker?.gridIndex ?: 0
|
||||
} else {
|
||||
0
|
||||
}
|
||||
val startRepeatRawTick = startRepeatGridIndex * 480L
|
||||
val fineMarker = currentMarkers.firstOrNull { m ->
|
||||
val gIdx = m.gridIndex ?: 0
|
||||
gIdx >= startRepeatGridIndex && gIdx <= jumpGridIndex && m.marker.any { text -> isFine.containsMatchIn(text.trim()) }
|
||||
}
|
||||
|
||||
val hasFine = fineMarker != null
|
||||
val endRepeatGridIndex = (fineMarker?.gridIndex ?: candidateGridIdx)
|
||||
val endRepeatRawTick = endRepeatGridIndex * 480L
|
||||
val shiftGridIndex = jumpGridIndex - startRepeatGridIndex
|
||||
val shiftInPitchTick: Int = shiftGridIndex * 60
|
||||
val pitchesBeforeJump = currentPitches.filter { (it.tick * 8L) <= jumpRawTick }
|
||||
|
||||
val pitchesToRepeat = currentPitches.filter {
|
||||
val raw = it.tick * 8L
|
||||
raw >= startRepeatRawTick && raw <= endRepeatRawTick
|
||||
}
|
||||
val duplicatedPitches = pitchesToRepeat.map { pitch ->
|
||||
pitch.copy(tick = pitch.tick + shiftInPitchTick)
|
||||
}
|
||||
|
||||
val repeatDurationGridIndex = endRepeatGridIndex - startRepeatGridIndex
|
||||
val durationShiftInPitchTick: Int = repeatDurationGridIndex * 60
|
||||
val shiftedPitchesAfterJump = currentPitches.filter { (it.tick * 8L) > jumpRawTick }.map { pitch ->
|
||||
pitch.copy(tick = pitch.tick + durationShiftInPitchTick)
|
||||
}
|
||||
|
||||
currentPitches = pitchesBeforeJump + duplicatedPitches + shiftedPitchesAfterJump
|
||||
|
||||
val markersBeforeJump = currentMarkers.filter { (it.gridIndex ?: 0) <= jumpGridIndex }
|
||||
val markersToRepeat = currentMarkers.filter {
|
||||
val gIdx = it.gridIndex ?: 0
|
||||
gIdx >= startRepeatGridIndex && gIdx <= endRepeatGridIndex
|
||||
}
|
||||
val duplicatedMarkers = markersToRepeat.map { m ->
|
||||
m.copy(gridIndex = (m.gridIndex ?: 0) + shiftGridIndex)
|
||||
}
|
||||
val shiftedMarkersAfterJump = currentMarkers.filter { (it.gridIndex ?: 0) > jumpGridIndex }.map { m ->
|
||||
m.copy(gridIndex = (m.gridIndex ?: 0) + repeatDurationGridIndex)
|
||||
}
|
||||
|
||||
currentMarkers = markersBeforeJump + duplicatedMarkers + shiftedMarkersAfterJump
|
||||
}
|
||||
|
||||
return Pair(currentPitches, currentMarkers)
|
||||
}
|
||||
|
||||
fun process(
|
||||
pitches: List<MidiPitch>,
|
||||
markers: List<MidiMarkers> = emptyList(),
|
||||
|
|
@ -75,6 +175,7 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
|||
voiceInstruments: Map<Int, Int> = mapOf(1 to 0, 2 to 0, 3 to 0, 4 to 0),
|
||||
initialKey: String = "C"
|
||||
) {
|
||||
val (effectivePitches, effectiveMarkers) = expandDaCapoAndSegno(pitches, markers)
|
||||
val lastTick = 0
|
||||
nextTick.clear()
|
||||
// addMetaMessage(0x59, 4, 2, 2,0)
|
||||
|
|
@ -96,7 +197,7 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
|||
val modulaRegex = Regex("""D(ô|o)?\s*dia\s*(C|Db|D|Eb|E|F|Gb|G|Ab|A|Bb|B)""", RegexOption.IGNORE_CASE)
|
||||
val tempoRegex = Regex("""^(?:♩|P)\s*[:=]\s*(\d+)""", RegexOption.IGNORE_CASE)
|
||||
|
||||
markers.forEach { midiMarker ->
|
||||
effectiveMarkers.forEach { midiMarker ->
|
||||
val gridIdx = (midiMarker.gridIndex ?: 0) * 480L
|
||||
midiMarker.marker.forEach { text ->
|
||||
val cleanText = text.trim()
|
||||
|
|
@ -168,7 +269,7 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
|||
val stepDelta = 20
|
||||
val processedHairpins = mutableSetOf<Long>()
|
||||
|
||||
val pitchesByTick = pitches.groupBy { (it.tick) * 8L }.toSortedMap()
|
||||
val pitchesByTick = effectivePitches.groupBy { (it.tick) * 8L }.toSortedMap()
|
||||
pitchesByTick.forEach { (rawTick, eventsAtTick) ->
|
||||
|
||||
/* Fermata CC*/
|
||||
|
|
@ -202,8 +303,7 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
|||
baseVelocity = nuanceMap[rawTick] ?: Dynamic.MF.velocity
|
||||
}
|
||||
|
||||
val adjustedTick = rawTick/* + timeOffset*/
|
||||
|
||||
val adjustedTick = rawTick + timeOffset
|
||||
|
||||
|
||||
// hairpin
|
||||
|
|
@ -243,40 +343,35 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
// Tempo Meta 0x51
|
||||
val pendingTempoTicks = tempoChangeMap.keys.filter { it <= rawTick }.sorted()
|
||||
pendingTempoTicks.forEach { tempoRawTick ->
|
||||
val newBpm = tempoChangeMap[tempoRawTick] ?: initialBpm
|
||||
val tempoAdjustedTick = tempoRawTick/* + timeOffset*/
|
||||
val tempoAdjustedTick = tempoRawTick + timeOffset
|
||||
|
||||
setTempoMeta(newBpm, tempoAdjustedTick.toInt())
|
||||
tempoChangeMap.remove(tempoRawTick)
|
||||
}
|
||||
|
||||
// Modulation Meta 0x59
|
||||
val pendingKeyTicks = keyChangeMap.keys.filter { it <= rawTick }.sorted()
|
||||
pendingKeyTicks.forEach { keyRawTick ->
|
||||
val targetKey = keyChangeMap[keyRawTick] ?: initialKey
|
||||
currentKey = targetKey
|
||||
|
||||
val keyAdjustedTick = keyRawTick/* + timeOffset*/
|
||||
val keyAdjustedTick = keyRawTick + timeOffset
|
||||
val keyIdx = Transpose.keyToNumber.indexOf(targetKey)
|
||||
|
||||
if (keyIdx != -1) {
|
||||
val sf = Transpose.keyToArmature[keyIdx]
|
||||
val keyBytes = byteArrayOf(sf.toByte(), 0.toByte()) // 0 = Majeur
|
||||
val keyBytes = byteArrayOf(sf.toByte(), 0.toByte())
|
||||
|
||||
addMetaMessage(0x59, keyAdjustedTick.toInt(), 2, keyBytes)
|
||||
}
|
||||
keyChangeMap.remove(keyRawTick)
|
||||
}
|
||||
|
||||
|
||||
/* Tetxe; marker */
|
||||
val pendingTextTicks = textMetaMap.keys.filter { it <= rawTick }.sorted()
|
||||
pendingTextTicks.forEach { textRawTick ->
|
||||
val textAdjustedTick = textRawTick/* + timeOffset*/
|
||||
val textAdjustedTick = textRawTick + timeOffset
|
||||
|
||||
textMetaMap[textRawTick]?.forEach { annotationText ->
|
||||
addTextMeta(text = annotationText, tickPosition = textAdjustedTick, metaType = 0x06)
|
||||
|
|
@ -285,7 +380,6 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
|||
}
|
||||
|
||||
eventsAtTick.forEach { noteEvent ->
|
||||
// Note + transpose
|
||||
val currentKeyIdx = Transpose.keyToNumber.indexOf(currentKey).coerceAtLeast(0)
|
||||
val transpositionSemitones = currentKeyIdx - baseKeyIdx
|
||||
|
||||
|
|
@ -303,7 +397,7 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
|||
}
|
||||
|
||||
tempoChangeMap.forEach { (tempoRawTick, bpm) ->
|
||||
val tempoAdjustedTick = tempoRawTick/* + timeOffset*/
|
||||
val tempoAdjustedTick = tempoRawTick + timeOffset
|
||||
setTempoMeta(bpm, tempoAdjustedTick.toInt())
|
||||
}
|
||||
tempoChangeMap.clear()
|
||||
|
|
@ -322,9 +416,6 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
|||
}
|
||||
return -1
|
||||
}
|
||||
/**
|
||||
* (Type 0x01 = Text, 0x06 = Marker, 0x05 = Lyric).
|
||||
*/
|
||||
fun addTextMeta(text: String, tickPosition: Long, metaType: Int = 0x01) {
|
||||
val bytes = text.toByteArray(Charsets.UTF_8)
|
||||
addMetaMessage(
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import mg.dot.feufaro.midi.MidiPitch
|
|||
import mg.dot.feufaro.midi.MidiWriterKotlin
|
||||
import mg.dot.feufaro.midi.MidiWriterforEXport
|
||||
import mg.dot.feufaro.provideSettings
|
||||
import mg.dot.feufaro.viewmodel.MidiMarkers
|
||||
import mg.dot.feufaro.viewmodel.PartitionMetadata
|
||||
import java.io.File
|
||||
import kotlin.math.min
|
||||
|
|
@ -261,8 +262,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
|||
unparsedNote.forEach {
|
||||
preloadN(it)
|
||||
}
|
||||
try {
|
||||
val localAccumulator = mutableListOf<TimeUnitObject>()
|
||||
try {
|
||||
withContext(Dispatchers.Main) {
|
||||
sharedScreenModel.updateFullTUOList(emptyList())
|
||||
}
|
||||
|
|
@ -270,6 +271,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
|||
} catch (e: Exception) {
|
||||
println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}")
|
||||
}
|
||||
attachLyricsToPitches(localAccumulator)
|
||||
val pitches = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber }))
|
||||
val midiWriter = MidiWriterKotlin(fileRepository)
|
||||
midiWriter.process(pitches)
|
||||
|
|
@ -289,8 +291,79 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
|||
fun generateMidiFile(bpm: Float, outputPath: String = "whawyd3.mid", transpI: Int = 0) {
|
||||
val parseScope = CoroutineScope(Dispatchers.Default)
|
||||
parseScope.launch {
|
||||
val pitchesSorted = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber }))
|
||||
val nbStanzas = sharedScreenModel.nbStanzas.value.takeIf { it > 0 } ?: 1
|
||||
val currentTuoList = sharedScreenModel.tuoList.value
|
||||
val basePitches = pitches.filter { it.metaType != 0x05 }.map { it.copy() }
|
||||
val baseMarkers = sharedScreenModel.getFullMarkers()
|
||||
|
||||
val loopedPitches = mutableListOf<MidiPitch>()
|
||||
val loopedMarkers = mutableListOf<MidiMarkers>()
|
||||
|
||||
val midiWriter = MidiWriterforEXport(fileRepository)
|
||||
val totalGridsInStanza = currentTuoList.size
|
||||
|
||||
var currentTickOffset = 0L
|
||||
var currentGridOffset = 0
|
||||
|
||||
for (stanza in 1..nbStanzas) {
|
||||
attachLyricsToPitches(tuoList = currentTuoList, stanzaNumber = stanza)
|
||||
val stanzaLyrics = pitches.filter { it.metaType == 0x05 }.map { it.copy() }
|
||||
|
||||
val stanzaPitches = basePitches.map { it.copy() } + stanzaLyrics
|
||||
|
||||
val (expandedPitches, expandedMarkers) = midiWriter.expandDaCapoAndSegno(stanzaPitches, baseMarkers)
|
||||
|
||||
expandedPitches.forEach { pitch ->
|
||||
loopedPitches.add(pitch.copy().apply {
|
||||
tick += currentTickOffset.toInt()
|
||||
metaBytes = metaBytes.copyOf()
|
||||
})
|
||||
}
|
||||
|
||||
expandedMarkers.forEach { marker ->
|
||||
loopedMarkers.add(
|
||||
marker.copy(
|
||||
timestamp = marker.timestamp + currentTickOffset,
|
||||
gridIndex = (marker.gridIndex ?: 0) + currentGridOffset
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val lastNoteGrid = expandedPitches
|
||||
.filter { it.metaType <= 0 && it.pitch.isNotEmpty() }
|
||||
.maxOfOrNull { it.tick / 60 }
|
||||
|
||||
val maxGridFromMarkers = expandedMarkers.maxOfOrNull { it.gridIndex ?: 0 }
|
||||
val maxGridFromPitches = expandedPitches.maxOfOrNull { it.tick / 60 }
|
||||
val maxStanzaGrid = maxOf(maxGridFromMarkers ?: 0, maxGridFromPitches ?: 0, totalGridsInStanza - 1)
|
||||
val stanzaGridCount = maxStanzaGrid + 1
|
||||
|
||||
val totalGridWithGap = if (stanza < nbStanzas && lastNoteGrid != null) {
|
||||
val measureInt = sharedScreenModel.measureInt.value.takeIf { it > 0 } ?: 4
|
||||
val gapNoires = when (measureInt) {
|
||||
2, 4, 8, 10 -> 4
|
||||
3, 6, 9, 12 -> 3
|
||||
else -> 4
|
||||
}
|
||||
lastNoteGrid + gapNoires
|
||||
} else {
|
||||
stanzaGridCount
|
||||
}
|
||||
|
||||
val stanzaTickDuration = totalGridWithGap * 60L
|
||||
|
||||
currentGridOffset += totalGridWithGap
|
||||
currentTickOffset += stanzaTickDuration
|
||||
}
|
||||
|
||||
pitches.clear()
|
||||
pitches.addAll(basePitches)
|
||||
attachLyricsToPitches(
|
||||
tuoList = currentTuoList,
|
||||
stanzaNumber = sharedScreenModel.stanza.value.takeIf { it > 0 } ?: 1
|
||||
)
|
||||
|
||||
val pitchesSorted = loopedPitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber }))
|
||||
|
||||
val instruments = mapOf(
|
||||
1 to prefs.getInt("voice_instrument", 0),
|
||||
|
|
@ -301,9 +374,10 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
|||
val originalKey = sharedScreenModel.songKey.value
|
||||
val transposedKey = Transpose.transposeKey(baseKey = originalKey, interval = transpI)
|
||||
val initialSongBpm = meta["p"]?.toFloatOrNull() ?: bpm
|
||||
|
||||
midiWriter.process(
|
||||
pitchesSorted,
|
||||
sharedScreenModel.getFullMarkers(),
|
||||
loopedMarkers,
|
||||
initialBpm = initialSongBpm,
|
||||
instruments,
|
||||
initialKey = transposedKey
|
||||
|
|
@ -311,6 +385,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
|||
midiWriter.save(outputPath)
|
||||
}
|
||||
}
|
||||
|
||||
fun justBuild(sourceFile: String, sourceContent: String) {
|
||||
currentFile = sourceFile
|
||||
val parseScope = CoroutineScope(Dispatchers.Default)
|
||||
|
|
@ -349,8 +424,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
|||
unparsedNote.forEach {
|
||||
preloadN(it)
|
||||
}
|
||||
try {
|
||||
val localAccumulator = mutableListOf<TimeUnitObject>()
|
||||
try {
|
||||
withContext(Dispatchers.Main) {
|
||||
sharedScreenModel.updateFullTUOList(emptyList())
|
||||
}
|
||||
|
|
@ -358,6 +433,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
|||
} catch (e: Exception) {
|
||||
println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}")
|
||||
}
|
||||
attachLyricsToPitches(localAccumulator)
|
||||
val pitches = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber }))
|
||||
val midiWriter = MidiWriterKotlin(fileRepository)
|
||||
midiWriter.process(pitches)
|
||||
|
|
@ -2112,6 +2188,48 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
|||
sharedScreenModel.setBpmFlow(bpm)
|
||||
}
|
||||
|
||||
private fun attachLyricsToPitches(
|
||||
tuoList: List<TimeUnitObject> = sharedScreenModel.tuoList.value,
|
||||
stanzaNumber: Int = sharedScreenModel.stanza.value.takeIf { it > 0 } ?: 1,
|
||||
stepTicks: Int = 60,
|
||||
) {
|
||||
pitches.removeAll { it.metaType == 0x05 }
|
||||
|
||||
val cleanRegex = Regex("(\\d+\\.)+")
|
||||
val validTuos = if (tuoList.size > 1 && tuoList.first().pTemplate.template.isEmpty()) {
|
||||
tuoList.drop(1)
|
||||
} else {
|
||||
tuoList
|
||||
}
|
||||
var lyricsAttachedCount = 0
|
||||
|
||||
validTuos.forEachIndexed { i, tuo ->
|
||||
val calculatedTick = i * stepTicks
|
||||
|
||||
val rawLines = tuo.getSingleSyllable(stanzaNumber)
|
||||
val cleanText = rawLines.firstOrNull()
|
||||
?.replace(cleanRegex, "")
|
||||
?.trim() ?: ""
|
||||
|
||||
// println("Voici ${tuo.lyricsAsMultiString(stanzaNumber)}")
|
||||
if (cleanText.isNotBlank()) {
|
||||
val bytes = cleanText.toByteArray(Charsets.UTF_8)
|
||||
val lyricMeta = MidiPitch(
|
||||
voiceNumber = 1,
|
||||
tick = calculatedTick,
|
||||
metaType = 0x05,
|
||||
metaBytes = bytes,
|
||||
metaByteSize = bytes.size
|
||||
)
|
||||
pitches.add(lyricMeta)
|
||||
lyricsAttachedCount++
|
||||
|
||||
// println(String.format("TUO #%-3d | Tick: %-5d | Syllable: [%s] -> ATTACHÉ",
|
||||
// i, calculatedTick, cleanText))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadN(voiceNumber: Int, line: String, templateComments: MutableList<String>) {
|
||||
var midiDuration = 0
|
||||
var nextDuration = 0
|
||||
|
|
|
|||
|
|
@ -532,7 +532,20 @@ actual class FMediaPlayer actual constructor(
|
|||
navigationJob = playerScope.launch(Dispatchers.Default) {
|
||||
sharedScreenModel.activeIndex.collect { currentIndex ->
|
||||
// println("bpm:$targetBpm _ ${sequencer?.tempoInBPM}| Index en mémoire : $availableIndices")
|
||||
// println("i $currentIndex ")
|
||||
//println("i $currentIndex ${sequencer!!.tickPosition}")
|
||||
if (currentIndex < 0) return@collect
|
||||
val tick = currentIndex * 60L
|
||||
val tuoList = sharedScreenModel.tuoList.value
|
||||
val validTuos = if (tuoList.size > 1 && tuoList.first().pTemplate.template.isEmpty()) {
|
||||
tuoList.drop(1)
|
||||
} else {
|
||||
tuoList
|
||||
}
|
||||
val currentTuo = validTuos.getOrNull(currentIndex)
|
||||
val currentStanza = sharedScreenModel.stanza.value.coerceAtLeast(1)
|
||||
val lyrics = currentTuo?.getSingleSyllable(currentStanza)?.joinToString(" ") ?: ""
|
||||
|
||||
println("Grille #%-3d | Tick: %-5d | Parole: [%s]".format(currentIndex, tick, lyrics))
|
||||
|
||||
if (sequencer?.isRunning == true) {
|
||||
if (Math.abs(sequencer!!.tempoInBPM - targetBpm) > 0.1) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue