diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt index 4a274e4..e1cb067 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt @@ -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:")) { - try { - val parts = line.substring(3).split(":") - val fileName = parts[0] - val ignorePattern = if (parts.size > 1) parts[1] else "" + var lastIndex = 0 + val matches = inclusionRegex.findAll(line).toList() - val fullPath = directory + fileName - val includedLines = fileRepository.readFileLines(fullPath) + if (matches.isNotEmpty()) { + val lineBuilder = StringBuilder() + for (match in matches) { + lineBuilder.append(line.substring(lastIndex, match.range.first)) - val regexIgnore = if (ignorePattern.isNotEmpty()) Regex(ignorePattern) else null + try { + val fullTag = match.value + val parts = fullTag.split(":", limit = 3) - includedLines.forEach { incLine -> - if (regexIgnore == null || !regexIgnore.containsMatchIn(incLine)) { - result.append(incLine).append("\n") + if (parts.size >= 2) { + val fileName = parts[1] + val fullPath = directory + fileName + val rawIncludedContent = fileRepository.readFileLines(fullPath) + + if (parts.size > 2 && parts[2].startsWith("^")) { + val patternInside = parts[2] + .substringAfter("(", "") + .substringBefore(")", "") + + 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) { + lineBuilder.append("// Erreur: ${e.message}") } - } catch (e: Exception) { - result.append("// Erreur inclusion: ${e.message}\n") + 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 { 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() @@ -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] == '{') { + 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() + 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() + val consumedGlobally = mutableSetOf>() + val consumedBlocks = mutableSetOf() + + 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 -> diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/SolfaVisualTransformation.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/SolfaVisualTransformation.kt index 367723d..59ed338 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/SolfaVisualTransformation.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/SolfaVisualTransformation.kt @@ -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() - pushStyle(SpanStyle(color = ColorPrefixB)) - append(it.groupValues[2]) - pop() - parseGenericContent(it.groupValues[3]) + + if (match.groupValues[2].isNotEmpty()) { + pushStyle(SpanStyle(color = ColorPrefixB)) + append(match.groupValues[2]) + pop() + } + parseGenericContent(match.groupValues[3]) + } else { + append(line) } } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUODetailDialog.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUODetailDialog.kt index 1c60b32..144a5dd 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUODetailDialog.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUODetailDialog.kt @@ -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().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) }, diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUOEditState.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUOEditState.kt index d0c9c2e..79540d9 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUOEditState.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUOEditState.kt @@ -7,6 +7,5 @@ data class TUOEditState( val lyricsByStanza: MutableMap = mutableMapOf(), val originalLyricsByStanza: MutableMap = mutableMapOf(), val templateFragment: String = "", - val marker: String = "", - val hairPin: String = "" + val marker: String = "" ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt index 44cfeb6..82adbbe 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt @@ -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, diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/SimpleDrawerContent.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/SimpleDrawerContent.kt index d3f1f0f..776ea4d 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/SimpleDrawerContent.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/SimpleDrawerContent.kt @@ -82,7 +82,7 @@ fun SimpleDrawerContent( checked = editMode, onCheckedChange = { newState -> sharedScreenModel.toggleEditorMode(newState) - }, + }, label = "Mode Edit", color = MaterialTheme.colorScheme.primary ) @@ -119,7 +119,7 @@ fun SimpleDrawerContent( stickyHeader { DrawerHeaderSticky( title = "Solfa disponibles", - icon = Icons.AutoMirrored.Filled.Note, + icon = Icons.AutoMirrored.Filled.Note, color = MaterialTheme.colorScheme.primary, isExpanded = internalExpanded, onToggle = { internalExpanded = !internalExpanded }, diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt index a1c91cf..f447c9c 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt @@ -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() { @@ -601,7 +601,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode _currentPos.value = pos _mediaPlayer?.let { player -> player.seekTo(pos.toLong()) - } + } } fun setDragging(dragState: Boolean) {