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
|
tick = 0
|
||||||
pitches.forEach {
|
pitches.forEach {
|
||||||
if (it.metaType > 0) {
|
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)
|
addMetaMessage(it.metaType, it.tick, it.metaByteSize, metaDataString)
|
||||||
} else if (it.pitch != "") {
|
} else if (it.pitch != "") {
|
||||||
addNote(it.voiceNumber, it.pitch.toInt(), 100, it.tick.toLong())
|
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)
|
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(
|
fun process(
|
||||||
pitches: List<MidiPitch>,
|
pitches: List<MidiPitch>,
|
||||||
markers: List<MidiMarkers> = emptyList(),
|
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),
|
voiceInstruments: Map<Int, Int> = mapOf(1 to 0, 2 to 0, 3 to 0, 4 to 0),
|
||||||
initialKey: String = "C"
|
initialKey: String = "C"
|
||||||
) {
|
) {
|
||||||
|
val (effectivePitches, effectiveMarkers) = expandDaCapoAndSegno(pitches, markers)
|
||||||
val lastTick = 0
|
val lastTick = 0
|
||||||
nextTick.clear()
|
nextTick.clear()
|
||||||
// addMetaMessage(0x59, 4, 2, 2,0)
|
// 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 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)
|
val tempoRegex = Regex("""^(?:♩|P)\s*[:=]\s*(\d+)""", RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
markers.forEach { midiMarker ->
|
effectiveMarkers.forEach { midiMarker ->
|
||||||
val gridIdx = (midiMarker.gridIndex ?: 0) * 480L
|
val gridIdx = (midiMarker.gridIndex ?: 0) * 480L
|
||||||
midiMarker.marker.forEach { text ->
|
midiMarker.marker.forEach { text ->
|
||||||
val cleanText = text.trim()
|
val cleanText = text.trim()
|
||||||
|
|
@ -168,7 +269,7 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
||||||
val stepDelta = 20
|
val stepDelta = 20
|
||||||
val processedHairpins = mutableSetOf<Long>()
|
val processedHairpins = mutableSetOf<Long>()
|
||||||
|
|
||||||
val pitchesByTick = pitches.groupBy { (it.tick) * 8L }.toSortedMap()
|
val pitchesByTick = effectivePitches.groupBy { (it.tick) * 8L }.toSortedMap()
|
||||||
pitchesByTick.forEach { (rawTick, eventsAtTick) ->
|
pitchesByTick.forEach { (rawTick, eventsAtTick) ->
|
||||||
|
|
||||||
/* Fermata CC*/
|
/* Fermata CC*/
|
||||||
|
|
@ -202,8 +303,7 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
||||||
baseVelocity = nuanceMap[rawTick] ?: Dynamic.MF.velocity
|
baseVelocity = nuanceMap[rawTick] ?: Dynamic.MF.velocity
|
||||||
}
|
}
|
||||||
|
|
||||||
val adjustedTick = rawTick/* + timeOffset*/
|
val adjustedTick = rawTick + timeOffset
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// hairpin
|
// hairpin
|
||||||
|
|
@ -243,40 +343,35 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Tempo Meta 0x51
|
|
||||||
val pendingTempoTicks = tempoChangeMap.keys.filter { it <= rawTick }.sorted()
|
val pendingTempoTicks = tempoChangeMap.keys.filter { it <= rawTick }.sorted()
|
||||||
pendingTempoTicks.forEach { tempoRawTick ->
|
pendingTempoTicks.forEach { tempoRawTick ->
|
||||||
val newBpm = tempoChangeMap[tempoRawTick] ?: initialBpm
|
val newBpm = tempoChangeMap[tempoRawTick] ?: initialBpm
|
||||||
val tempoAdjustedTick = tempoRawTick/* + timeOffset*/
|
val tempoAdjustedTick = tempoRawTick + timeOffset
|
||||||
|
|
||||||
setTempoMeta(newBpm, tempoAdjustedTick.toInt())
|
setTempoMeta(newBpm, tempoAdjustedTick.toInt())
|
||||||
tempoChangeMap.remove(tempoRawTick)
|
tempoChangeMap.remove(tempoRawTick)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Modulation Meta 0x59
|
|
||||||
val pendingKeyTicks = keyChangeMap.keys.filter { it <= rawTick }.sorted()
|
val pendingKeyTicks = keyChangeMap.keys.filter { it <= rawTick }.sorted()
|
||||||
pendingKeyTicks.forEach { keyRawTick ->
|
pendingKeyTicks.forEach { keyRawTick ->
|
||||||
val targetKey = keyChangeMap[keyRawTick] ?: initialKey
|
val targetKey = keyChangeMap[keyRawTick] ?: initialKey
|
||||||
currentKey = targetKey
|
currentKey = targetKey
|
||||||
|
|
||||||
val keyAdjustedTick = keyRawTick/* + timeOffset*/
|
val keyAdjustedTick = keyRawTick + timeOffset
|
||||||
val keyIdx = Transpose.keyToNumber.indexOf(targetKey)
|
val keyIdx = Transpose.keyToNumber.indexOf(targetKey)
|
||||||
|
|
||||||
if (keyIdx != -1) {
|
if (keyIdx != -1) {
|
||||||
val sf = Transpose.keyToArmature[keyIdx]
|
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)
|
addMetaMessage(0x59, keyAdjustedTick.toInt(), 2, keyBytes)
|
||||||
}
|
}
|
||||||
keyChangeMap.remove(keyRawTick)
|
keyChangeMap.remove(keyRawTick)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Tetxe; marker */
|
|
||||||
val pendingTextTicks = textMetaMap.keys.filter { it <= rawTick }.sorted()
|
val pendingTextTicks = textMetaMap.keys.filter { it <= rawTick }.sorted()
|
||||||
pendingTextTicks.forEach { textRawTick ->
|
pendingTextTicks.forEach { textRawTick ->
|
||||||
val textAdjustedTick = textRawTick/* + timeOffset*/
|
val textAdjustedTick = textRawTick + timeOffset
|
||||||
|
|
||||||
textMetaMap[textRawTick]?.forEach { annotationText ->
|
textMetaMap[textRawTick]?.forEach { annotationText ->
|
||||||
addTextMeta(text = annotationText, tickPosition = textAdjustedTick, metaType = 0x06)
|
addTextMeta(text = annotationText, tickPosition = textAdjustedTick, metaType = 0x06)
|
||||||
|
|
@ -285,7 +380,6 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
||||||
}
|
}
|
||||||
|
|
||||||
eventsAtTick.forEach { noteEvent ->
|
eventsAtTick.forEach { noteEvent ->
|
||||||
// Note + transpose
|
|
||||||
val currentKeyIdx = Transpose.keyToNumber.indexOf(currentKey).coerceAtLeast(0)
|
val currentKeyIdx = Transpose.keyToNumber.indexOf(currentKey).coerceAtLeast(0)
|
||||||
val transpositionSemitones = currentKeyIdx - baseKeyIdx
|
val transpositionSemitones = currentKeyIdx - baseKeyIdx
|
||||||
|
|
||||||
|
|
@ -303,7 +397,7 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
||||||
}
|
}
|
||||||
|
|
||||||
tempoChangeMap.forEach { (tempoRawTick, bpm) ->
|
tempoChangeMap.forEach { (tempoRawTick, bpm) ->
|
||||||
val tempoAdjustedTick = tempoRawTick/* + timeOffset*/
|
val tempoAdjustedTick = tempoRawTick + timeOffset
|
||||||
setTempoMeta(bpm, tempoAdjustedTick.toInt())
|
setTempoMeta(bpm, tempoAdjustedTick.toInt())
|
||||||
}
|
}
|
||||||
tempoChangeMap.clear()
|
tempoChangeMap.clear()
|
||||||
|
|
@ -322,9 +416,6 @@ class MidiWriterforEXport constructor(private val fileRepository: FileRepositor
|
||||||
}
|
}
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* (Type 0x01 = Text, 0x06 = Marker, 0x05 = Lyric).
|
|
||||||
*/
|
|
||||||
fun addTextMeta(text: String, tickPosition: Long, metaType: Int = 0x01) {
|
fun addTextMeta(text: String, tickPosition: Long, metaType: Int = 0x01) {
|
||||||
val bytes = text.toByteArray(Charsets.UTF_8)
|
val bytes = text.toByteArray(Charsets.UTF_8)
|
||||||
addMetaMessage(
|
addMetaMessage(
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ import mg.dot.feufaro.midi.MidiPitch
|
||||||
import mg.dot.feufaro.midi.MidiWriterKotlin
|
import mg.dot.feufaro.midi.MidiWriterKotlin
|
||||||
import mg.dot.feufaro.midi.MidiWriterforEXport
|
import mg.dot.feufaro.midi.MidiWriterforEXport
|
||||||
import mg.dot.feufaro.provideSettings
|
import mg.dot.feufaro.provideSettings
|
||||||
|
import mg.dot.feufaro.viewmodel.MidiMarkers
|
||||||
import mg.dot.feufaro.viewmodel.PartitionMetadata
|
import mg.dot.feufaro.viewmodel.PartitionMetadata
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
@ -261,8 +262,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
unparsedNote.forEach {
|
unparsedNote.forEach {
|
||||||
preloadN(it)
|
preloadN(it)
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
val localAccumulator = mutableListOf<TimeUnitObject>()
|
val localAccumulator = mutableListOf<TimeUnitObject>()
|
||||||
|
try {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
sharedScreenModel.updateFullTUOList(emptyList())
|
sharedScreenModel.updateFullTUOList(emptyList())
|
||||||
}
|
}
|
||||||
|
|
@ -270,6 +271,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}")
|
println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}")
|
||||||
}
|
}
|
||||||
|
attachLyricsToPitches(localAccumulator)
|
||||||
val pitches = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber }))
|
val pitches = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber }))
|
||||||
val midiWriter = MidiWriterKotlin(fileRepository)
|
val midiWriter = MidiWriterKotlin(fileRepository)
|
||||||
midiWriter.process(pitches)
|
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) {
|
fun generateMidiFile(bpm: Float, outputPath: String = "whawyd3.mid", transpI: Int = 0) {
|
||||||
val parseScope = CoroutineScope(Dispatchers.Default)
|
val parseScope = CoroutineScope(Dispatchers.Default)
|
||||||
parseScope.launch {
|
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 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(
|
val instruments = mapOf(
|
||||||
1 to prefs.getInt("voice_instrument", 0),
|
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 originalKey = sharedScreenModel.songKey.value
|
||||||
val transposedKey = Transpose.transposeKey(baseKey = originalKey, interval = transpI)
|
val transposedKey = Transpose.transposeKey(baseKey = originalKey, interval = transpI)
|
||||||
val initialSongBpm = meta["p"]?.toFloatOrNull() ?: bpm
|
val initialSongBpm = meta["p"]?.toFloatOrNull() ?: bpm
|
||||||
|
|
||||||
midiWriter.process(
|
midiWriter.process(
|
||||||
pitchesSorted,
|
pitchesSorted,
|
||||||
sharedScreenModel.getFullMarkers(),
|
loopedMarkers,
|
||||||
initialBpm = initialSongBpm,
|
initialBpm = initialSongBpm,
|
||||||
instruments,
|
instruments,
|
||||||
initialKey = transposedKey
|
initialKey = transposedKey
|
||||||
|
|
@ -311,6 +385,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
midiWriter.save(outputPath)
|
midiWriter.save(outputPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun justBuild(sourceFile: String, sourceContent: String) {
|
fun justBuild(sourceFile: String, sourceContent: String) {
|
||||||
currentFile = sourceFile
|
currentFile = sourceFile
|
||||||
val parseScope = CoroutineScope(Dispatchers.Default)
|
val parseScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
@ -349,8 +424,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
unparsedNote.forEach {
|
unparsedNote.forEach {
|
||||||
preloadN(it)
|
preloadN(it)
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
val localAccumulator = mutableListOf<TimeUnitObject>()
|
val localAccumulator = mutableListOf<TimeUnitObject>()
|
||||||
|
try {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
sharedScreenModel.updateFullTUOList(emptyList())
|
sharedScreenModel.updateFullTUOList(emptyList())
|
||||||
}
|
}
|
||||||
|
|
@ -358,6 +433,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}")
|
println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}")
|
||||||
}
|
}
|
||||||
|
attachLyricsToPitches(localAccumulator)
|
||||||
val pitches = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber }))
|
val pitches = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber }))
|
||||||
val midiWriter = MidiWriterKotlin(fileRepository)
|
val midiWriter = MidiWriterKotlin(fileRepository)
|
||||||
midiWriter.process(pitches)
|
midiWriter.process(pitches)
|
||||||
|
|
@ -2112,6 +2188,48 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
sharedScreenModel.setBpmFlow(bpm)
|
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>) {
|
private fun loadN(voiceNumber: Int, line: String, templateComments: MutableList<String>) {
|
||||||
var midiDuration = 0
|
var midiDuration = 0
|
||||||
var nextDuration = 0
|
var nextDuration = 0
|
||||||
|
|
|
||||||
|
|
@ -532,7 +532,20 @@ actual class FMediaPlayer actual constructor(
|
||||||
navigationJob = playerScope.launch(Dispatchers.Default) {
|
navigationJob = playerScope.launch(Dispatchers.Default) {
|
||||||
sharedScreenModel.activeIndex.collect { currentIndex ->
|
sharedScreenModel.activeIndex.collect { currentIndex ->
|
||||||
// println("bpm:$targetBpm _ ${sequencer?.tempoInBPM}| Index en mémoire : $availableIndices")
|
// 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 (sequencer?.isRunning == true) {
|
||||||
if (Math.abs(sequencer!!.tempoInBPM - targetBpm) > 0.1) {
|
if (Math.abs(sequencer!!.tempoInBPM - targetBpm) > 0.1) {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue