Can Add Marker on editing partition TUODetailDialog '+' botton & reconstruct Template

This commit is contained in:
Hasinjato 2026-05-20 12:00:59 +03:00
parent bf797762ea
commit 80ef86527b
7 changed files with 579 additions and 113 deletions

View file

@ -319,10 +319,15 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
val t0Idx = lines.indexOfFirst { it.startsWith("T0:") } val t0Idx = lines.indexOfFirst { it.startsWith("T0:") }
val u0Idx = lines.indexOfFirst { it.startsWith("U0:") } val u0Idx = lines.indexOfFirst { it.startsWith("U0:") }
val noteS = lines[lines.indexOfFirst { it.startsWith("N1") }] val idxS = lines.indexOfFirst { it.startsWith("N1") }
val noteA = lines[lines.indexOfFirst { it.startsWith("N2") }] val idxA = lines.indexOfFirst { it.startsWith("N2") }
val noteT = lines[lines.indexOfFirst { it.startsWith("N3") }] val idxT = lines.indexOfFirst { it.startsWith("N3") }
val noteB = lines[lines.indexOfFirst { it.startsWith("N4") }] val idxB = lines.indexOfFirst { it.startsWith("N4") }
val noteS = if (idxS != -1) lines[idxS] else ""
val noteA = if (idxA != -1) lines[idxA] else ""
val noteT = if (idxT != -1) lines[idxT] else ""
val noteB = if (idxB != -1) lines[idxB] else ""
val currentStanza = sharedScreenModel.stanza.value val currentStanza = sharedScreenModel.stanza.value
@ -331,7 +336,6 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
} }
val lyrics = lines[lyrIdx] val lyrics = lines[lyrIdx]
println("Parole sur $currentStanza: $lyrics")
val content = smartYLyrics(lyrics).substringAfter(":") val content = smartYLyrics(lyrics).substringAfter(":")
@ -370,33 +374,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
when { when {
t0Idx != -1 -> { t0Idx != -1 -> {
val line = lines[t0Idx]
val prefix = line.substringBefore('{') + "{"
val suffix = "}"
val body = line.substringAfter('{').substringBeforeLast('}')
val regex = Regex("([^:|/!]*[:|/!])|([^:|/!]+)")
val fragments = regex.findAll(body).map { it.value }.toMutableList()
// println("Nombre de blocs détectés : ${fragments.size}")
val markerRegex = Regex("""\$\{.*?\}|\$.""")
if (targetIdx in fragments.indices) {
val oldFrag = fragments[targetIdx]
val delimiter = if (oldFrag.isNotEmpty() && oldFrag.last() in ":|/!") {
oldFrag.last().toString()
} else ""
val cleanBody = fragments.joinToString("") { it.replace(markerRegex, "") }
lines[t0Idx] = prefix + cleanBody + suffix
println("AVANT: $line")
println("APRÈS: ${lines[t0Idx]}")
} else {
println("Index $targetIdx hors limites pour T0 (taille=${fragments.size})")
return
}
} }
u0Idx != -1 -> { u0Idx != -1 -> {
@ -405,7 +383,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
// println("fullLine = '$fullLine'") // println("fullLine = '$fullLine'")
val afterU0 = fullLine.substringAfter("U0:") val afterU0 = fullLine.substringAfter("U0:")
val blankPrefixRegex = Regex("^(z[048CEKO]:)") val blankPrefixRegex = Regex("^(.*?z[0-9A-Z]:)")
val match = blankPrefixRegex.find(afterU0) val match = blankPrefixRegex.find(afterU0)
val blankPrefix = match?.value ?: "" val blankPrefix = match?.value ?: ""
val body = if (match != null) { val body = if (match != null) {
@ -420,7 +398,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
val symboles = parser.parsed() val symboles = parser.parsed()
val newLigneT = "U0:"+blankPrefix+symboles val newLigneT = "U0:"+blankPrefix+symboles
println("Ligne de U0 ==> $newLigneT") // println("Ligne de U0 ==> $newLigneT")
lines[u0Idx] = newLigneT lines[u0Idx] = newLigneT
} }
@ -438,20 +416,39 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
}*/ }*/
val markerClean = editState.marker
val modulaRegex = Regex("""^\s*D(ô|o)?\s*dia\s*(C|Db|D|Eb|E|F|Gb|G|Ab|A|Bb|B)\s*$""", RegexOption.IGNORE_CASE)
val match = modulaRegex.find(markerClean)
val newMarker = if (match != null) {
val lettre = match.groupValues[2]
"\${c:$lettre}"
} else {
markerClean.split(Regex("\\s+")).filter { it.isNotEmpty() }.joinToString(" ") { part ->
when (part) {
"𝄐" -> "\$Q"
else -> "\${$part}"
}
}
}
/* MODIF TEMPLATE */
val markerUpdated = updateMarkerSource(if(u0Idx != -1) lines[u0Idx] else lines[t0Idx], targetIdx, newMarker)
/* MODIF SOURCE Note&Lyrics */
val updatedSource = updateSourceLines(lines, templatArray, targetIdx, editState)
/*
MODIF NOTES N1 N2 N3 N4 val templateUpd = reconstructIt(markerUpdated, measureString[0].digitToInt())
*/
// println("taille tA: ${templatArray.size} et le trgt $targetIdx") println("Re-formater:: $templateUpd")
val updatedNotes = updateSourceLines(lines, templatArray, targetIdx, editState)
// RESTAURATION DES TEMPLATES // RESTAURATION DES TEMPLATES
if (templateString != "") { if (templateString != "") {
updatedNotes[templateIndices] = templateString updatedSource[templateIndices] = templateUpd
} }
val finalString = updatedNotes.joinToString("\n") val finalString = updatedSource.joinToString("\n")
val tempDir = System.getProperty("java.io.tmpdir") val tempDir = System.getProperty("java.io.tmpdir")
val fileName = filePath.substringAfterLast('/') val fileName = filePath.substringAfterLast('/')
@ -481,27 +478,61 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
val result = StringBuilder() val result = StringBuilder()
val lastSlash = filePath.lastIndexOf('/') val lastSlash = filePath.lastIndexOf('/')
val directory = if (lastSlash != -1) filePath.substring(0, lastSlash + 1) else "" val directory = if (lastSlash != -1) filePath.substring(0, lastSlash + 1) else ""
val myLines = fileRepository.readFileLines(filePath) // Si inclusion multiples
val myLines = try {
fileRepository.readFileLines(filePath)
} catch (e: Exception) {
return "// Erreur lecture: $filePath"
}
val inclusionRegex = Regex("""I\d+:[^ \n\r]+""")
myLines.forEach { line -> myLines.forEach { line ->
if (line.startsWith("I0:")) { var lastIndex = 0
val matches = inclusionRegex.findAll(line).toList()
if (matches.isNotEmpty()) {
val lineBuilder = StringBuilder()
for (match in matches) {
lineBuilder.append(line.substring(lastIndex, match.range.first))
try { try {
val parts = line.substring(3).split(":") val fullTag = match.value
val fileName = parts[0] val parts = fullTag.split(":", limit = 3)
val ignorePattern = if (parts.size > 1) parts[1] else ""
if (parts.size >= 2) {
val fileName = parts[1]
val fullPath = directory + fileName val fullPath = directory + fileName
val includedLines = fileRepository.readFileLines(fullPath) val rawIncludedContent = fileRepository.readFileLines(fullPath)
val regexIgnore = if (ignorePattern.isNotEmpty()) Regex(ignorePattern) else null if (parts.size > 2 && parts[2].startsWith("^")) {
val patternInside = parts[2]
.substringAfter("(", "")
.substringBefore(")", "")
includedLines.forEach { incLine -> val prefixesToIgnore = if (patternInside.isNotEmpty()) {
if (regexIgnore == null || !regexIgnore.containsMatchIn(incLine)) { patternInside.split("|")
result.append(incLine).append("\n") } else {
listOf(parts[2].substring(1))
}
val filteredLines = rawIncludedContent.filter { incLine ->
prefixesToIgnore.none { prefix ->
incLine.trim().startsWith(prefix)
}
}
lineBuilder.append(filteredLines.joinToString("\n"))
} else {
lineBuilder.append(rawIncludedContent.joinToString("\n"))
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
result.append("// Erreur inclusion: ${e.message}\n") lineBuilder.append("// Erreur: ${e.message}")
} }
lastIndex = match.range.last + 1
}
lineBuilder.append(line.substring(lastIndex))
result.append(lineBuilder.toString()).append("\n")
} else { } else {
result.append(line).append("\n") result.append(line).append("\n")
} }
@ -544,8 +575,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
val lyricsContent = if(lineYIdx != -1) smartYLyrics(lyricsBody) else smartELyrics(lyricsBody) val lyricsContent = if(lineYIdx != -1) smartYLyrics(lyricsBody) else smartELyrics(lyricsBody)
// Découpage strict pour correspondre au template // Découpage strict pour correspondre au template
println("Lyrics BODY $lyricsBody") // println("Lyrics BODY $lyricsBody")
println("LyricsC $lyricsContent") // println("LyricsC $lyricsContent")
val lyricsInLines = sharedScreenModel.synchronizedSyllables.value val lyricsInLines = sharedScreenModel.synchronizedSyllables.value
lyricsInLines.mapIndexed { index, string -> lyricsInLines.mapIndexed { index, string ->
@ -575,7 +606,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
// marker)) // marker))
} }
println("\nAction finale : Remplacer l'index $targetIdx (valeur: ${allTokens.getOrNull(targetIdx)})") // println("\nAction finale : Remplacer l'index $targetIdx (valeur: ${allTokens.getOrNull(targetIdx)})")
if (targetIdx < allTokens.size) { if (targetIdx < allTokens.size) {
allTokens[targetIdx] = newSyllable allTokens[targetIdx] = newSyllable
@ -682,7 +713,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")" val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")"
if (isStructural) { if (isStructural) {
println(String.format("%-5d | %-10s | %-8s | %-12s | %-10s", i, token, "STRUCT", "-", "Keep")) // println(String.format("%-5d | %-10s | %-8s | %-12s | %-10s", i, token, "STRUCT", "-", "Keep"))
resultTokens.add(token) resultTokens.add(token)
i++ i++
continue continue
@ -731,7 +762,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
resultTokens.add(processedToken) resultTokens.add(processedToken)
if (processedToken.matches(Regex("[drmfsltDRFSTzw].*|[-―]"))) { if (processedToken.matches(Regex("[drmfsltDRFSTzw].*|[-―]"))) {
println(String.format("NEW | %-10s | INSERT | %-12d | REPLACE", processedToken, currentLogicalIdx)) // println(String.format("NEW | %-10s | INSERT | %-12d | REPLACE", processedToken, currentLogicalIdx))
currentLogicalIdx++ currentLogicalIdx++
} }
} }
@ -772,6 +803,160 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
} }
} }
private fun updateMarkerSource(
templateLine: String,
targetPointer: Int,
newMarkerValue: String
): String {
val isT0 = templateLine.startsWith("T0")
val prefix: String
val body: String
val suffix = if (isT0) "}" else ""
if (isT0) {
prefix = "T0:{"
body = templateLine.substringAfter("{").substringBeforeLast("}")
} else {
val match = Regex("^(.*?z\\d|.*?z.):").find(templateLine)
if (match != null) {
prefix = match.value
body = templateLine.substring(match.value.length)
} else {
prefix = templateLine.substringBefore(":") + ":"
body = templateLine.substringAfter(":")
}
}
// println("\n" + "=".repeat(100))
// println("DEBUG COMPACTAGE & MARKERS - préfixe $prefix")
// println("Cible Idx: $targetPointer | Nouveau Marqueur: \"$newMarkerValue\"")
// println("-".repeat(100))
// println(String.format("%-4s | %-25s | %-15s | %-25s", "Idx", "Bloc Étendu (Input)", "Action", "Bloc Compacté (Output)"))
// println("-".repeat(100))
val resultBody = StringBuilder()
var currentLogicalIdx = 0
var i = 0
var currentBlock = StringBuilder()
val separators = listOf(':', '|', '!', '/')
while (i <= body.length) {
if (i < body.length && body[i] == '$' && i + 1 < body.length && body[i + 1] == '{') {
val endPos = body.indexOf('}', i)
if (endPos != -1) {
currentBlock.append(body.substring(i, endPos + 1))
i = endPos + 1
continue
}
}
if (i == body.length || body[i] in separators) {
if (isT0 && currentBlock.toString().trim().isEmpty()) {
if (i < body.length) resultBody.append(body[i])
currentBlock.clear()
i++
continue
}
if (i == body.length && currentBlock.toString().trim().isEmpty()) {
break
}
var rawBlockText = currentBlock.toString()
var actionTaken = "Keep"
if (rawBlockText.trim().isEmpty()) {
rawBlockText = if(isT0) "" else "z"
actionTaken = if(isT0) "EMPTY->\"\"" else "EMPTY->z"
}
var blockToProcess = if (currentLogicalIdx == targetPointer) {
actionTaken = if (newMarkerValue.isEmpty()) "DELETE" else "UPDATE"
val note = rawBlockText.replace(Regex("""\$\{.*?\}|\$."""), "").trim()
newMarkerValue + note
} else {
rawBlockText
}
val compactedBlock = StringBuilder()
if (blockToProcess.isNotEmpty()) {
var k = 0
while (k < blockToProcess.length) {
val char = blockToProcess[k]
if (char == 'D' || char == 'z' || char == '-') {
var count = 1
val noteStart = char
val markerFound = StringBuilder()
var mIdx = k + 1
while (mIdx < blockToProcess.length) {
val nextChar = blockToProcess[mIdx]
if (nextChar == '$') {
if (mIdx + 1 < blockToProcess.length && blockToProcess[mIdx + 1] == '{') {
val end = blockToProcess.indexOf('}', mIdx)
if (end != -1) {
markerFound.append(blockToProcess.substring(mIdx, end + 1))
mIdx = end + 1
continue
}
} else if (mIdx + 1 < blockToProcess.length) {
markerFound.append(blockToProcess.substring(mIdx, mIdx + 2))
mIdx += 2
continue
}
}
break
}
var scanIdx = mIdx
while (scanIdx < blockToProcess.length && blockToProcess[scanIdx] == '-' && count < 4) {
if (scanIdx + 1 < blockToProcess.length && blockToProcess[scanIdx + 1] == '$') break
count++
scanIdx++
}
val compactCode = when (count) {
1 -> noteStart
2 -> if (noteStart == 'D') '8' else '2'
3 -> if (noteStart == 'D') 'C' else '3'
4 -> if (noteStart == 'D') 'G' else '4'
else -> noteStart
}
compactedBlock.append(compactCode).append(markerFound)
k = scanIdx
} else {
compactedBlock.append(char)
k++
}
}
} else {
compactedBlock.append("")
}
// println(String.format("%-4d | %-25s | %-15s | %-25s",
// currentLogicalIdx, rawBlockText, actionTaken, compactedBlock.toString()))
resultBody.append(compactedBlock)
if (i < body.length) resultBody.append(body[i])
currentBlock.clear()
currentLogicalIdx++
} else {
currentBlock.append(body[i])
}
i++
}
val finalResult = "$prefix$resultBody$suffix"
// println("-".repeat(100))
// println("RÉSULTAT FINAL COMPACTÉ : $finalResult")
// println("=".repeat(100))
return finalResult
}
private fun parseNoteAndOctave(s: String): Pair<String, Int> { private fun parseNoteAndOctave(s: String): Pair<String, Int> {
val base = s.replace(Regex("[^drmfsltDRFSTzw]"), "") val base = s.replace(Regex("[^drmfsltDRFSTzw]"), "")
@ -876,7 +1061,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
// "Array = ${templateArray.joinToString("|")}") // "Array = ${templateArray.joinToString("|")}")
// println("APRèS T: ${templateArray.joinToString("|")}") // println("APRèS T: ${templateArray.joinToString("|")}")
} else { } else {
val body = templateLine.replaceFirst(Regex("^U0:z.:"), "") val body = templateLine.replaceFirst(Regex("^U0:(?:z[0-9A-Z]:)?"), "")
//println("Mon contenu est: $body.") //println("Mon contenu est: $body.")
val result = mutableListOf<String>() val result = mutableListOf<String>()
@ -886,31 +1071,27 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
while (i < body.length) { while (i < body.length) {
val char = body[i] val char = body[i]
// 1. GESTION DES MARQUEURS ${...} -> ON IGNORE TOUT LE BLOC
if (char == '$' && i + 1 < body.length && body[i + 1] == '{') { if (char == '$' && i + 1 < body.length && body[i + 1] == '{') {
val closingBrace = body.indexOf('}', i) val closingBrace = body.indexOf('}', i)
if (closingBrace != -1) { if (closingBrace != -1) {
i = closingBrace + 1 // On saute jusqu'après le '}' i = closingBrace + 1
continue continue
} }
} }
if (char == '$') { if (char == '$') {
i += 2 // On saute le '$' et la lettre qui suit i += 2
continue continue
} }
// 3. SÉPARATEURS -> ON COUPE LE BLOC
if (char in listOf(':', '|', '!', '/')) { if (char in listOf(':', '|', '!', '/')) {
if (currentBlock.isNotEmpty()) { if (currentBlock.isNotEmpty()) {
result.add(currentBlock.toString()) result.add(currentBlock.toString())
currentBlock.clear() currentBlock.clear()
} }
} }
// 4. IGNORER LES ESPACES
else if (char == ' ') { else if (char == ' ') {
// ne rien faire
} }
// 5. TOUT LE RESTE (D, z, -, ., ,, (, ) )
else { else {
currentBlock.append(char) currentBlock.append(char)
} }
@ -918,7 +1099,6 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
i++ i++
} }
// Ajouter le dernier morceau s'il n'y a pas de séparateur à la fin
if (currentBlock.isNotEmpty()) result.add(currentBlock.toString()) if (currentBlock.isNotEmpty()) result.add(currentBlock.toString())
templateArray = result templateArray = result
@ -929,6 +1109,297 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
return templateArray return templateArray
} }
private fun reconstructIt(finalResult: String, measure: Int): String {
if(finalResult.startsWith("T0:")) {
/* T0 */
return finalResult
} else {
/* U0 */
val prefixMatch = Regex("^(.*?z[048CEKO]:|.*?z.:)").find(finalResult)
val prefix = prefixMatch?.value ?: "U0:z0:"
val body = finalResult.substring(prefixMatch?.value?.length ?: (finalResult.indexOf(":") + 1))
val buff = mutableListOf<String>()
var currentBlockBuilder = StringBuilder()
var idx = 0
while (idx < body.length) {
if (body[idx] == '$' && idx + 1 < body.length && body[idx + 1] == '{') {
val endPos = body.indexOf('}', idx)
if (endPos != -1) {
currentBlockBuilder.append(body.substring(idx, endPos + 1))
idx = endPos + 1
continue
}
}
val char = body[idx]
if (char == ':' || char == '|' || char == '!') {
if (currentBlockBuilder.isNotEmpty()) {
buff.add(currentBlockBuilder.toString())
currentBlockBuilder.setLength(0)
}
} else if (char == '/') {
currentBlockBuilder.append(char)
buff.add(currentBlockBuilder.toString())
currentBlockBuilder.setLength(0)
} else {
currentBlockBuilder.append(char)
}
idx++
}
if (currentBlockBuilder.isNotEmpty()) {
buff.add(currentBlockBuilder.toString())
}
val buff2 = mutableListOf<String>()
val consumedGlobally = mutableSetOf<Pair<Int, Int>>()
val consumedBlocks = mutableSetOf<Int>()
fun getDurationAt(targetStr: String, targetCharIdx: Int, blockIdx: Int): Int {
val cleanStr = targetStr.replace(Regex("\\$\\{.*?\\}|\\$."), "")
val charAtIdx = targetStr[targetCharIdx]
if (blockIdx > 0) {
val prevBlock = buff[blockIdx - 1].replace(Regex("\\$\\{.*?\\}|\\$."), "")
val isFirstActiveChar = targetCharIdx == 0 || targetStr.substring(0, targetCharIdx).all {
"., !:|(){}$".contains(it) || consumedGlobally.contains(blockIdx to targetStr.indexOf(it))
}
if (prevBlock.endsWith("-.") || prevBlock == ".") {
if (isFirstActiveChar) {
if (charAtIdx == 'z' || charAtIdx == 'D') {
return 4
}
}
}
if (!cleanStr.contains(".") && !cleanStr.contains(",")) {
if (prevBlock.endsWith("./") || prevBlock.endsWith(".")) return 2
if (prevBlock.endsWith(",/") || prevBlock.endsWith(",")) return 1
}
}
if (charAtIdx == 'D' || charAtIdx == 'z') {
val textAfter = targetStr.substring(targetCharIdx + 1)
val cleanAfter = textAfter.replace(Regex("\\$\\{.*?\\}|\\$."), "")
if (cleanAfter.startsWith(".") && !cleanAfter.substring(1).any { it == 'D' || it == 'z' || it == '-' }) {
return 4
}
}
if (!cleanStr.contains(".") && !cleanStr.contains(",")) return 4
val pByPoint = cleanStr.split(".")
val prefixBefore = targetStr.substring(0, targetCharIdx).replace(Regex("\\$\\{.*?\\}|\\$."), "")
val cleanIdx = prefixBefore.length
var curPos = 0
var zoneIdx = 0
for (i in pByPoint.indices) {
if (cleanIdx >= curPos && cleanIdx < curPos + pByPoint[i].length + 1) {
zoneIdx = i
break
}
curPos += pByPoint[i].length + 1
}
return if (cleanStr.contains(",")) {
if (pByPoint[zoneIdx].contains(",")) 1 else 2
} else {
2
}
}
fun encodeDuration(duration: Int): String {
return when (duration) {
1 -> "1"; 2 -> "2"; 3 -> "3"; 4 -> "4"
6 -> "6"; 8 -> "8"; 10 -> "A"; 11 -> "B";
12 -> "C"; 14 -> "E"; 16 -> "G"; 20 -> "K";
24 -> "O"; 28 -> "S"; 32 -> "W"
else -> duration.toString()
}
}
var k = 0
while (k < buff.size) {
if (consumedBlocks.contains(k)) {
// println("Pour $k = ${buff[k]} -> Saut du bloc entier (déjà absorbé dans un silence cumulé)")
k++
continue
}
val currentStr = buff[k]
val sb = StringBuilder()
val prefixBuffer = StringBuilder()
// println("Pour $k = $currentStr")
if (k > 0) {
val prevBlock = buff[k - 1].replace(Regex("\\$\\{.*?\\}|\\$."), "")
val cleanCurrent = currentStr.replace(Regex("\\$\\{.*?\\}|\\$."), "")
if (prevBlock.endsWith("-./") || prevBlock.endsWith("-.)/")) {
if (cleanCurrent.contains(".") && cleanCurrent.startsWith("D") && cleanCurrent.count { it == 'D' } >= 2) {
val silenceDuration = 2
sb.append("z$silenceDuration ")
// println(" -> [RÈGLE SPÉCIALE] Silence résiduel injecté ! Insertion de 'z$silenceDuration '")
}
}
}
var charIdx = 0
while (charIdx < currentStr.length) {
if (consumedGlobally.contains(k to charIdx)) {
// println(" -> Saut du caractère '${currentStr[charIdx]}' (déjà absorbé)")
charIdx++
continue
}
val char = currentStr[charIdx]
when {
char == '$' && charIdx + 1 < currentStr.length && currentStr[charIdx + 1] == '{' -> {
val endIdx = currentStr.indexOf('}', charIdx)
if (endIdx != -1) {
prefixBuffer.append(currentStr.substring(charIdx, endIdx + 1))
charIdx = endIdx
}
}
char == '$' && charIdx + 1 < currentStr.length -> {
prefixBuffer.append(char).append(currentStr[charIdx + 1])
charIdx++
}
char == '(' -> prefixBuffer.append(char)
char == ')' || char == '/' -> sb.append(char)
char == 'D' || char == 'z' -> {
var totalDuration = getDurationAt(currentStr, charIdx, k)
// println(" -> Note '$char' détectée. Zone initiale, Durée: $totalDuration")
var lookK = k
var lookC = charIdx + 1
var searching = true
while (searching) {
if (lookC >= buff[lookK].length) {
lookK++; lookC = 0
if (lookK >= buff.size) break
}
val next = buff[lookK][lookC]
when {
next == '$' -> {
if (lookC + 1 < buff[lookK].length && buff[lookK][lookC + 1] == '{') {
val end = buff[lookK].indexOf('}', lookC)
lookC = if (end != -1) end + 1 else lookC + 2
} else {
lookC += 2
}
}
next == '-' -> {
val resDur = getDurationAt(buff[lookK], lookC, lookK)
totalDuration += resDur
consumedGlobally.add(lookK to lookC)
// println(" + Absorption tiret à [$lookK,$lookC]. Durée zone: $resDur. Total: $totalDuration")
lookC++
}
next == 'z' && lookK == k && char == 'D' -> {
var hasFutureResonance = false
if (k + 1 < buff.size) {
if (buff[k + 1].replace(Regex("\\$\\{.*?\\}|\\$."), "")
.startsWith("-")
) hasFutureResonance = true
}
if (!hasFutureResonance && currentStr.contains(".") && currentStr.contains(",")) {
val resDur = getDurationAt(currentStr, lookC, k)
totalDuration += resDur
consumedGlobally.add(lookK to lookC)
lookC++
} else {
searching = false
}
}
"., !:|(){}".contains(next) || next == '$' -> lookC++
else -> searching = false
}
}
val isPureSilenceBlock =
char == 'z' && (lookK > k || (lookK == k && lookC >= buff[k].length))
if (isPureSilenceBlock) {
var nextBlockIdx = k + 1
var lookingForSilence = true
while (lookingForSilence && nextBlockIdx < buff.size) {
val nextBlockRaw = buff[nextBlockIdx]
val nextBlockClean = nextBlockRaw.replace(Regex("\\$\\{.*?\\}|\\$."), "").trim()
if (nextBlockClean.isNotEmpty() && nextBlockClean.all {
it == 'z' || "., !:|()/{}".contains(
it
)
}) {
val zIdxInNext = nextBlockRaw.indexOf('z')
if (zIdxInNext != -1) {
val nextSilenceDur = getDurationAt(nextBlockRaw, zIdxInNext, nextBlockIdx)
totalDuration += nextSilenceDur
// println(" [CUMUL SILENCE] -> Bloc $nextBlockIdx ('$nextBlockClean') absorbé. Durée ajoutée: $nextSilenceDur. Total temporaire: $totalDuration")
consumedBlocks.add(nextBlockIdx)
nextBlockIdx++
} else {
lookingForSilence = false
}
} else {
lookingForSilence = false
}
}
}
val code = if (char == 'z') {
"z${encodeDuration(totalDuration)}"
} else {
encodeDuration(totalDuration)
}
sb.append(prefixBuffer.toString())
prefixBuffer.setLength(0)
sb.append(code)
// println(" -> Code généré: $code")
}
}
charIdx++
}
if (prefixBuffer.isNotEmpty()) sb.insert(0, prefixBuffer.toString())
var resultBlock = sb.toString()
if (resultBlock.contains("31")) {
resultBlock = resultBlock.replace("31", "y")
// println(" -> Motif 'y' détecté")
}
// println("Pour ça ON a $resultBlock \n")
if (resultBlock.isNotEmpty() || buff[k].contains("/")) buff2.add(resultBlock)
k++
}
val cleanedBlocks = buff2.map { it.replace(" ", "") }
val finalLines = (prefix + cleanedBlocks.joinToString(""))
.replace("2z11", "y")
.replace("/", "/ ")
.replace(Regex("\\s+"), " ")
.trim()
// println(finalLines)
return finalLines
}
}
private fun expandNotes(text: String): String { private fun expandNotes(text: String): String {
val regex = Regex("([drmfsltDRFSTzw])([,']*)(\\d+)") val regex = Regex("([drmfsltDRFSTzw])([,']*)(\\d+)")
return regex.replace(text) { matchResult -> return regex.replace(text) { matchResult ->

View file

@ -25,8 +25,7 @@ class SolfaVisualTransformation : VisualTransformation {
when { when {
line.startsWith("M0:") -> parseMetadataLine(line) line.startsWith("M0:") -> parseMetadataLine(line)
line.startsWith("U0:") && hasU0 -> parseControlLine(line) line.startsWith("U0:") || line.startsWith("T0:") -> parseControlLine(line)
line.startsWith("T0:") && !hasU0 && hasT0 -> parseControlLine(line)
line.startsWith("N") && line.getOrNull(2) == ':' -> parseNoteLine(line) line.startsWith("N") && line.getOrNull(2) == ':' -> parseNoteLine(line)
(line.startsWith("Y") || line.startsWith("E")) && (line.getOrNull(2) == ':' || line.getOrNull(3) == ':') -> parseLyricLine(line) (line.startsWith("Y") || line.startsWith("E")) && (line.getOrNull(2) == ':' || line.getOrNull(3) == ':') -> parseLyricLine(line)
else -> append(line) else -> append(line)
@ -129,16 +128,21 @@ private fun AnnotatedString.Builder.appendWithMarkers(text: String) {
} }
private fun AnnotatedString.Builder.parseControlLine(line: String) { private fun AnnotatedString.Builder.parseControlLine(line: String) {
val regex = Regex("^(U0:)(z[0-9A-Z]:)?(.*)$") val regex = Regex("^([UT]0:)(z[0-9A-Z]:)?(.*)$")
val match = regex.find(line) val match = regex.find(line)
match?.let { if (match != null) {
pushStyle(SpanStyle(color = ColorPrefixA)) pushStyle(SpanStyle(color = ColorPrefixA, fontWeight = FontWeight.Bold))
append(it.groupValues[1]) append(match.groupValues[1])
pop() pop()
if (match.groupValues[2].isNotEmpty()) {
pushStyle(SpanStyle(color = ColorPrefixB)) pushStyle(SpanStyle(color = ColorPrefixB))
append(it.groupValues[2]) append(match.groupValues[2])
pop() pop()
parseGenericContent(it.groupValues[3]) }
parseGenericContent(match.groupValues[3])
} else {
append(line)
} }
} }

View file

@ -19,10 +19,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.* import androidx.compose.ui.unit.*
import androidx.compose.ui.window.Popup import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties import androidx.compose.ui.window.PopupProperties
@ -57,7 +59,6 @@ fun TUODetailDialog(
var templateFragment by remember { mutableStateOf(editState.templateFragment) } var templateFragment by remember { mutableStateOf(editState.templateFragment) }
var marker by remember { mutableStateOf(editState.marker) } var marker by remember { mutableStateOf(editState.marker) }
var hairPin by remember { mutableStateOf(editState.hairPin) }
val lyricsLines = remember { val lyricsLines = remember {
mutableStateListOf<String>().apply { mutableStateListOf<String>().apply {
@ -66,6 +67,7 @@ fun TUODetailDialog(
} }
} }
var canAddMark = mutableStateOf(false)
Popup( Popup(
offset = menuPosition, offset = menuPosition,
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
@ -96,7 +98,6 @@ fun TUODetailDialog(
modifier = Modifier modifier = Modifier
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
// --- SECTION TEMPLATE --- // --- SECTION TEMPLATE ---
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) { Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Column( Column(
@ -114,7 +115,7 @@ fun TUODetailDialog(
) )
} }
if(!editState.marker.isNullOrEmpty()) { if(!editState.marker.isNullOrEmpty() || canAddMark.value) {
Column( Column(
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) { ) {
@ -130,20 +131,16 @@ fun TUODetailDialog(
onValueChng = { marker = it } onValueChng = { marker = it }
) )
} }
} } else {
if(!editState.hairPin.isNullOrEmpty()) { IconButton(
Column( modifier = Modifier.size(30.dp),
modifier = Modifier.weight(1f) onClick = {
) { canAddMark.value = true
MyTextEditField( }) {
value = hairPin, Icon(
customFontSize = 14.sp, imageVector = Icons.Default.Add,
color = Color.Yellow, tint = Color.Green,
customPadding = 8.dp, contentDescription = null
customBrush = SolidColor(Color.White),
isEditable = isEditable,
isAddable = canAdd,
onValueChng = { hairPin = it }
) )
} }
} }
@ -263,7 +260,8 @@ fun TUODetailDialog(
originalNotes = originalNotes.toMap(), originalNotes = originalNotes.toMap(),
originalLyricsByStanza = originalLyricsByStz.toMutableMap(), originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
lyricsByStanza = mutableMapOf(currentStanza to lyricsLines.joinToString(" ")), lyricsByStanza = mutableMapOf(currentStanza to lyricsLines.joinToString(" ")),
templateFragment = templateFragment templateFragment = templateFragment,
marker = marker
) )
onSave(state) onSave(state)
}, },

View file

@ -7,6 +7,5 @@ data class TUOEditState(
val lyricsByStanza: MutableMap<Int, String> = mutableMapOf(), val lyricsByStanza: MutableMap<Int, String> = mutableMapOf(),
val originalLyricsByStanza: MutableMap<Int, String> = mutableMapOf(), val originalLyricsByStanza: MutableMap<Int, String> = mutableMapOf(),
val templateFragment: String = "", val templateFragment: String = "",
val marker: String = "", val marker: String = ""
val hairPin: String = ""
) )

View file

@ -843,9 +843,6 @@ fun LazyVerticalGridTUO(
val editState = TUOEditState( val editState = TUOEditState(
tuoIndex = oneTUO.firstTuoIndex, tuoIndex = oneTUO.firstTuoIndex,
/*notesByVoice = (0..3).associate { i ->
i to (oneTUO.tuNotes.getOrNull(i + 1)?.toString() ?: "")
},*/
notesByVoice = (0..3).associate { i -> notesByVoice = (0..3).associate { i ->
val rawNote = oneTUO.tuNotes.getOrNull(i + 1)?.toString() ?: "" val rawNote = oneTUO.tuNotes.getOrNull(i + 1)?.toString() ?: ""
val fixedNote = autoFixNote(rawNote, template) val fixedNote = autoFixNote(rawNote, template)
@ -855,14 +852,11 @@ fun LazyVerticalGridTUO(
s to oneTUO.getSingleSyllable(s).firstOrNull().orEmpty() s to oneTUO.getSingleSyllable(s).firstOrNull().orEmpty()
}.toMutableMap(), }.toMutableMap(),
templateFragment = oneTUO.pTemplate.template, templateFragment = oneTUO.pTemplate.template,
marker = oneTUO.pTemplate.markerToString(), marker = listOfNotNull(
hairPin = oneTUO.hasHairPin()?.toString() ?: "" oneTUO.pTemplate.markerToString().takeIf { it.isNotBlank() },
oneTUO.hasHairPin()?.toString()?.takeIf { it.isNotBlank() }
).joinToString(" ")
) )
// oneTUO.tuNotes.mapIndexed { index, note ->
//
// println("i$index => ${note.toString()}")
// }
TUODetailDialog( TUODetailDialog(
editState = editState, editState = editState,
tuo = currentSelected, tuo = currentSelected,

View file

@ -523,7 +523,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
fun openTUOEditor(editState: TUOEditState) { fun openTUOEditor(editState: TUOEditState) {
_tuoEditState.value = editState _tuoEditState.value = editState
println("nMrk = [${editState.marker}] template [${editState.templateFragment}] hp [${editState.hairPin}]") println("nMrk = [${editState.marker}] template [${editState.templateFragment}]")
} }
fun closeTUOEditor() { fun closeTUOEditor() {