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 u0Idx = lines.indexOfFirst { it.startsWith("U0:") }
val noteS = lines[lines.indexOfFirst { it.startsWith("N1") }]
val noteA = lines[lines.indexOfFirst { it.startsWith("N2") }]
val noteT = lines[lines.indexOfFirst { it.startsWith("N3") }]
val noteB = lines[lines.indexOfFirst { it.startsWith("N4") }]
val idxS = lines.indexOfFirst { it.startsWith("N1") }
val idxA = lines.indexOfFirst { it.startsWith("N2") }
val idxT = lines.indexOfFirst { it.startsWith("N3") }
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
@ -331,7 +336,6 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
}
val lyrics = lines[lyrIdx]
println("Parole sur $currentStanza: $lyrics")
val content = smartYLyrics(lyrics).substringAfter(":")
@ -370,33 +374,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
when {
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 -> {
@ -405,7 +383,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
// println("fullLine = '$fullLine'")
val afterU0 = fullLine.substringAfter("U0:")
val blankPrefixRegex = Regex("^(z[048CEKO]:)")
val blankPrefixRegex = Regex("^(.*?z[0-9A-Z]:)")
val match = blankPrefixRegex.find(afterU0)
val blankPrefix = match?.value ?: ""
val body = if (match != null) {
@ -420,7 +398,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
val symboles = parser.parsed()
val newLigneT = "U0:"+blankPrefix+symboles
println("Ligne de U0 ==> $newLigneT")
// println("Ligne de U0 ==> $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
*/
// println("taille tA: ${templatArray.size} et le trgt $targetIdx")
val updatedNotes = updateSourceLines(lines, templatArray, targetIdx, editState)
val templateUpd = reconstructIt(markerUpdated, measureString[0].digitToInt())
println("Re-formater:: $templateUpd")
// RESTAURATION DES TEMPLATES
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 fileName = filePath.substringAfterLast('/')
@ -481,27 +478,61 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
val result = StringBuilder()
val lastSlash = filePath.lastIndexOf('/')
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 ->
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 {
val parts = line.substring(3).split(":")
val fileName = parts[0]
val ignorePattern = if (parts.size > 1) parts[1] else ""
val fullTag = match.value
val parts = fullTag.split(":", limit = 3)
if (parts.size >= 2) {
val fileName = parts[1]
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 ->
if (regexIgnore == null || !regexIgnore.containsMatchIn(incLine)) {
result.append(incLine).append("\n")
val prefixesToIgnore = if (patternInside.isNotEmpty()) {
patternInside.split("|")
} 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) {
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 {
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)
// Découpage strict pour correspondre au template
println("Lyrics BODY $lyricsBody")
println("LyricsC $lyricsContent")
// println("Lyrics BODY $lyricsBody")
// println("LyricsC $lyricsContent")
val lyricsInLines = sharedScreenModel.synchronizedSyllables.value
lyricsInLines.mapIndexed { index, string ->
@ -575,7 +606,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
// 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) {
allTokens[targetIdx] = newSyllable
@ -682,7 +713,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")"
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)
i++
continue
@ -731,7 +762,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
resultTokens.add(processedToken)
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++
}
}
@ -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> {
val base = s.replace(Regex("[^drmfsltDRFSTzw]"), "")
@ -876,7 +1061,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
// "Array = ${templateArray.joinToString("|")}")
// println("APRèS T: ${templateArray.joinToString("|")}")
} else {
val body = templateLine.replaceFirst(Regex("^U0:z.:"), "")
val body = templateLine.replaceFirst(Regex("^U0:(?:z[0-9A-Z]:)?"), "")
//println("Mon contenu est: $body.")
val result = mutableListOf<String>()
@ -886,31 +1071,27 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
while (i < body.length) {
val char = body[i]
// 1. GESTION DES MARQUEURS ${...} -> ON IGNORE TOUT LE BLOC
if (char == '$' && i + 1 < body.length && body[i + 1] == '{') {
val closingBrace = body.indexOf('}', i)
if (closingBrace != -1) {
i = closingBrace + 1 // On saute jusqu'après le '}'
i = closingBrace + 1
continue
}
}
if (char == '$') {
i += 2 // On saute le '$' et la lettre qui suit
i += 2
continue
}
// 3. SÉPARATEURS -> ON COUPE LE BLOC
if (char in listOf(':', '|', '!', '/')) {
if (currentBlock.isNotEmpty()) {
result.add(currentBlock.toString())
currentBlock.clear()
}
}
// 4. IGNORER LES ESPACES
else if (char == ' ') {
// ne rien faire
}
// 5. TOUT LE RESTE (D, z, -, ., ,, (, ) )
else {
currentBlock.append(char)
}
@ -918,7 +1099,6 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
i++
}
// Ajouter le dernier morceau s'il n'y a pas de séparateur à la fin
if (currentBlock.isNotEmpty()) result.add(currentBlock.toString())
templateArray = result
@ -929,6 +1109,297 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
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 {
val regex = Regex("([drmfsltDRFSTzw])([,']*)(\\d+)")
return regex.replace(text) { matchResult ->

View file

@ -25,8 +25,7 @@ class SolfaVisualTransformation : VisualTransformation {
when {
line.startsWith("M0:") -> parseMetadataLine(line)
line.startsWith("U0:") && hasU0 -> parseControlLine(line)
line.startsWith("T0:") && !hasU0 && hasT0 -> parseControlLine(line)
line.startsWith("U0:") || line.startsWith("T0:") -> parseControlLine(line)
line.startsWith("N") && line.getOrNull(2) == ':' -> parseNoteLine(line)
(line.startsWith("Y") || line.startsWith("E")) && (line.getOrNull(2) == ':' || line.getOrNull(3) == ':') -> parseLyricLine(line)
else -> append(line)
@ -129,16 +128,21 @@ private fun AnnotatedString.Builder.appendWithMarkers(text: 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)
match?.let {
pushStyle(SpanStyle(color = ColorPrefixA))
append(it.groupValues[1])
if (match != null) {
pushStyle(SpanStyle(color = ColorPrefixA, fontWeight = FontWeight.Bold))
append(match.groupValues[1])
pop()
if (match.groupValues[2].isNotEmpty()) {
pushStyle(SpanStyle(color = ColorPrefixB))
append(it.groupValues[2])
append(match.groupValues[2])
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.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.*
import androidx.compose.ui.window.Popup
import androidx.compose.ui.window.PopupProperties
@ -57,7 +59,6 @@ fun TUODetailDialog(
var templateFragment by remember { mutableStateOf(editState.templateFragment) }
var marker by remember { mutableStateOf(editState.marker) }
var hairPin by remember { mutableStateOf(editState.hairPin) }
val lyricsLines = remember {
mutableStateListOf<String>().apply {
@ -66,6 +67,7 @@ fun TUODetailDialog(
}
}
var canAddMark = mutableStateOf(false)
Popup(
offset = menuPosition,
onDismissRequest = onDismiss,
@ -96,7 +98,6 @@ fun TUODetailDialog(
modifier = Modifier
.verticalScroll(rememberScrollState())
) {
// --- SECTION TEMPLATE ---
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Column(
@ -114,7 +115,7 @@ fun TUODetailDialog(
)
}
if(!editState.marker.isNullOrEmpty()) {
if(!editState.marker.isNullOrEmpty() || canAddMark.value) {
Column(
modifier = Modifier.weight(1f)
) {
@ -130,20 +131,16 @@ fun TUODetailDialog(
onValueChng = { marker = it }
)
}
}
if(!editState.hairPin.isNullOrEmpty()) {
Column(
modifier = Modifier.weight(1f)
) {
MyTextEditField(
value = hairPin,
customFontSize = 14.sp,
color = Color.Yellow,
customPadding = 8.dp,
customBrush = SolidColor(Color.White),
isEditable = isEditable,
isAddable = canAdd,
onValueChng = { hairPin = it }
} else {
IconButton(
modifier = Modifier.size(30.dp),
onClick = {
canAddMark.value = true
}) {
Icon(
imageVector = Icons.Default.Add,
tint = Color.Green,
contentDescription = null
)
}
}
@ -263,7 +260,8 @@ fun TUODetailDialog(
originalNotes = originalNotes.toMap(),
originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
lyricsByStanza = mutableMapOf(currentStanza to lyricsLines.joinToString(" ")),
templateFragment = templateFragment
templateFragment = templateFragment,
marker = marker
)
onSave(state)
},

View file

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

View file

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

View file

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