From 2f536ba45bf1b7949e7d4b1c31a22d47a6b454ea Mon Sep 17 00:00:00 2001 From: Hasinjato Date: Fri, 22 May 2026 12:32:11 +0300 Subject: [PATCH] Auto Update Template struct when Editing notes - template with note format --- .../kotlin/mg/dot/feufaro/solfa/Solfa.kt | 162 ++++++++---------- .../mg/dot/feufaro/solfa/TUODetailDialog.kt | 99 ++++++++++- .../mg/dot/feufaro/solfa/TimeUnitObject.kt | 12 +- 3 files changed, 176 insertions(+), 97 deletions(-) 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 e1cb067..64b416a 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt @@ -4,6 +4,7 @@ import SharedScreenModel import kotlinx.coroutines.* import mg.dot.feufaro.FileRepository import mg.dot.feufaro.SaveSettings +import mg.dot.feufaro.getGlobalTemplate import mg.dot.feufaro.launchFilePicker import mg.dot.feufaro.midi.MidiPitch import mg.dot.feufaro.midi.MidiWriterKotlin @@ -431,12 +432,12 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository } } } - /* 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 TEMPLATE */ + val markerUpdated = updateMarkerSource(if(u0Idx != -1) lines[u0Idx] else lines[t0Idx], targetIdx, newMarker, editState) + val templateUpd = reconstructIt(markerUpdated, measureString[0].digitToInt()) @@ -557,94 +558,49 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository lines: MutableList, fragments: List, targetIdx: Int, - deleteCount: Int, editState: TUOEditState ) { editState.lyricsByStanza.forEach { (stanzaNum, newSyllable) -> - val lineYIdx = lines.indexOfFirst { it.matches(Regex("^[Y]$stanzaNum:.*")) } - val lineEIdx = lines.indexOfFirst { it.matches(Regex("^[E]$stanzaNum:.*")) } + val lineIdx = lines.indexOfFirst { it.matches(Regex("^[YE]$stanzaNum:.*")) } + if (lineIdx == -1) return@forEach - val lineIdx = if(lineYIdx != -1) lineYIdx else lineEIdx + val currentLine = lines[lineIdx] + val prefix = currentLine.substringBefore(":") + ":" + val body = currentLine.substringAfter(":") - if (lineIdx != -1) { - val currentLine = lines[lineIdx] - val prefix = currentLine.substringBefore(":") + ":" - val lyricsBody = currentLine.substringAfter(":") + val tokens = tokenizeLyricsPreserving(body) - // On traite les paroles pour avoir la liste brute - val lyricsContent = if(lineYIdx != -1) smartYLyrics(lyricsBody) else smartELyrics(lyricsBody) - - // Découpage strict pour correspondre au template -// println("Lyrics BODY $lyricsBody") -// println("LyricsC $lyricsContent") - - val lyricsInLines = sharedScreenModel.synchronizedSyllables.value - lyricsInLines.mapIndexed { index, string -> - //println("SYNRO $index => {$string}") - } - - val allTokens = lyricsInLines.toMutableList() - -// println("--- TABLEAU DE CORRESPONDANCE (Strophe $stanzaNum) ---") -// println(String.format("%-5s | %-10s | %-15s", "Idx", "Template", "Syllabe")) -// println("-------------------------------------------") - - var actualTokenPointer = 0 - for (i in fragments.indices) { - val fragment = fragments[i] - val isProlongation = fragment.trim() == "-" - - val associatedSyllable = allTokens.getOrNull(actualTokenPointer) ?: "" - actualTokenPointer++ - - val marker = if (i == targetIdx) " <== [CIBLE]" else "" - -// println(String.format("%-5d | %-10s | %-15s %s", -// i, -// "[$fragment]", -// "[$associatedSyllable]", -// marker)) - } - -// println("\nAction finale : Remplacer l'index $targetIdx (valeur: ${allTokens.getOrNull(targetIdx)})") - - if (targetIdx < allTokens.size) { - allTokens[targetIdx] = newSyllable - - if(deleteCount > 1){ - var deleted = 0 - var j = targetIdx + 1 - while (deleted < deleteCount - 1 && j < allTokens.size) { - if (allTokens[j].isNotEmpty()) { - allTokens[j] = "" - deleted++ - } - j++ - } - } - } - - val rawResult = allTokens.filter { it.isNotEmpty() }.joinToString(" ") - println("allToken ${allTokens.joinToString("")}") -// println("RAWRes ${rawResult}") - val formattedResult = rawResult - .replace(Regex("-\\s+"), "") - .replace(Regex("-"), "") - - println("FORMATTER $formattedResult") - - val lyrIdx = lines.indexOfFirst { line -> - line.contains(Regex("^[EY]$stanzaNum:")) - } - val lyrics = lines[lyrIdx] - println("Originl $lyrics") - - - lines[lineIdx] = prefix + formattedResult - //lines[lineIdx] = prefix + reconstructLyrics(finalTokens) + val textTokenIndices = tokens.indices.filter { + !tokens[it].startsWith("\${") && tokens[it] != "/" } + +// println("--- TABLEAU DE DEBUG SYLLABES (Strophe $stanzaNum) ---") +// println(String.format("%-5s | %-15s | %-10s", "Idx", "Token (Original)", "IsTarget")) + tokens.forEachIndexed { i, token -> + val isTarget = if (textTokenIndices.contains(i)) " (Syllabe)" else " (Structure)" +// println(String.format("%-5d | %-15s | %-10s", i, token, isTarget)) + } + + if (targetIdx < textTokenIndices.size) { + val idxToReplace = textTokenIndices[targetIdx] +// println(">>> Remplacement index $idxToReplace (Cible $targetIdx) : '${tokens[idxToReplace]}' -> '$newSyllable'") + tokens[idxToReplace] = newSyllable + } + + val reconstructed = tokens.joinToString("") + lines[lineIdx] = prefix + reconstructed +// println("Résultat final : ${lines[lineIdx]}") } -// println("--- FIN DEBUG ---") + } + + private fun tokenizeLyricsPreserving(input: String): MutableList { + val result = mutableListOf() + val regex = Regex("""(\$\{.*?\})|(/)|([^\s/]+(?:\s+|))""") + + regex.findAll(input).forEach { match -> + result.add(match.value) + } + return result } @@ -806,7 +762,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository private fun updateMarkerSource( templateLine: String, targetPointer: Int, - newMarkerValue: String + newMarkerValue: String, + editState: TUOEditState ): String { val isT0 = templateLine.startsWith("T0") @@ -872,9 +829,12 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository var blockToProcess = if (currentLogicalIdx == targetPointer) { actionTaken = if (newMarkerValue.isEmpty()) "DELETE" else "UPDATE" - val note = rawBlockText.replace(Regex("""\$\{.*?\}|\$."""), "").trim() - newMarkerValue + note + val markerMatch = Regex("""\$\{.*?\}|\$.""").find(rawBlockText) + val existingMarker = markerMatch?.value ?: "" + + val newTemplate = getGlobalTemplate(editState) + newMarkerValue + existingMarker + newTemplate } else { rawBlockText } @@ -1212,10 +1172,21 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository 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" + 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() } } @@ -1378,6 +1349,17 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository if (prefixBuffer.isNotEmpty()) sb.insert(0, prefixBuffer.toString()) var resultBlock = sb.toString() + // TRIOLET --- + val trioletRegex = Regex("""(?:\u0024T)?4(?:\u0024T)?4(?:\u0024T)?4(?:\u0024T)?""") + +// println("Mon actuel resBlk $resultBlock et ça contient? ${trioletRegex.containsMatchIn(resultBlock)} mon regex ${trioletRegex}") + if (trioletRegex.containsMatchIn(resultBlock)) { + resultBlock = resultBlock.replace(trioletRegex) { _ -> + "t" + } +// println(" -> [RÈGLE TRIOLET] Motif absorbé et converti en 't'") + } + if (resultBlock.contains("31")) { resultBlock = resultBlock.replace("31", "y") // println(" -> Motif 'y' détecté") 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 a8fdccb..fddb4dc 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUODetailDialog.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUODetailDialog.kt @@ -60,12 +60,34 @@ fun TUODetailDialog( var marker by remember { mutableStateOf(editState.marker) } - val lyricsLines = remember { + LaunchedEffect(notes.values.toList()) { + val tempState = TUOEditState( + tuoIndex = editState.tuoIndex, + notesByVoice = notes, + originalNotes = editState.originalNotes, + lyricsByStanza = editState.lyricsByStanza, + originalLyricsByStanza = editState.originalLyricsByStanza, + templateFragment = templateFragment, + marker = editState.marker + ) + + // Màj auto template + val newTemplate = getGlobalTemplate(tempState) + if (newTemplate != templateFragment) { + templateFragment = newTemplate + } + } + + val result = remember { mutableStateListOf().apply { val existing = editState.lyricsByStanza[currentStanza] ?: "" if (existing.isEmpty() && canAdd) add("_") else add(existing) } } + val lyricsLines = result + .flatMap { it.split("\n") } + .map { it.trim() } + .toMutableList() var canAddMark = mutableStateOf(false) Popup( @@ -109,8 +131,8 @@ fun TUODetailDialog( color = Color.Yellow, customPadding = 8.dp, customBrush = SolidColor(Color.White), - isEditable = isEditable, - isAddable = canAdd, + isEditable = false, + isAddable = false, onValueChng = { templateFragment = it } ) @@ -329,7 +351,7 @@ fun MyTextEditField( }, keyboardOptions = KeyboardOptions( autoCorrectEnabled = false, - keyboardType = KeyboardType.Password + keyboardType = KeyboardType.Ascii ), textStyle = TextStyle( color = color, @@ -398,4 +420,73 @@ fun validateMusicalInput(input: String, template: String): Boolean { """.trimIndent())*/ return isMatched +} +public fun getGlobalTemplate(editState: TUOEditState): String { +// println("\n--- DÉBOGAGE getGlobalTemplate ---") + + val voiceTemplates = (0..3).map { voiceIdx -> + val notes = editState.notesByVoice[voiceIdx] ?: "" + val tpl = notesToTemplate(notes) +// println("Voix $voiceIdx | Notes: '$notes' -> Template: '$tpl'") + tpl + } + + val maxLen = voiceTemplates.maxOfOrNull { it.length } ?: 1 +// println("Longueur max détectée: $maxLen") + + val result = StringBuilder() + + for (i in 0 until maxLen) { + val chars = voiceTemplates.mapNotNull { it.getOrNull(i) }.toSet() + + val lastChar = if (result.isNotEmpty()) result.last() else null + + if (lastChar == '.' && chars.contains(',')) { + result.append('z') + } + +// println(" Indice $i | Caractères collectés: $chars") + + val combinedChar = when { + chars.contains('D') || chars.contains('-') -> 'D' + chars.contains('.') -> '.' + chars.contains(',') -> ',' + chars.contains('(') -> '(' + chars.contains(')') -> ')' + else -> 'z' + } + result.append(combinedChar) + } + +// println("Template Global Final: '${result.toString()}'") +// println("----------------------------------\n") + return result.toString() +} + +private fun notesToTemplate(notes: String): String { + if (notes.isEmpty()) return "z" + + val cleaned = notes + .replace("―", "-") + .replace("•", ".") + .replace(Regex("[³₃²₂¹₁]"), "") + .replace("di", "D") + .replace("ri", "R") + .replace("fi", "F") + .replace("si", "S") + .replace("ta", "T") + + val noteRegex = Regex("([drmfsltDRFSTz]+|[.,()-])") + val template = StringBuilder() + val matches = noteRegex.findAll(cleaned) + + for (match in matches) { + val value = match.value + when { + value.matches(Regex("[.,()-]")) -> template.append(value) + else -> template.append("D") + } + } + + return template.toString() } \ 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 82adbbe..a923ee7 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt @@ -849,7 +849,7 @@ fun LazyVerticalGridTUO( i to fixedNote }, lyricsByStanza = (1..currentStanza).associate { s -> - s to oneTUO.getSingleSyllable(s).firstOrNull().orEmpty() + s to oneTUO.lyricsAsMultiString(currentStanza) }.toMutableMap(), templateFragment = oneTUO.pTemplate.template, marker = listOfNotNull( @@ -1229,7 +1229,7 @@ fun EditSourceCompose( onValueChange = { codeContent = it }, keyboardOptions = KeyboardOptions( autoCorrectEnabled = false, - keyboardType = KeyboardType.Password + keyboardType = KeyboardType.Ascii ), modifier = Modifier.fillMaxSize().padding(start = 8.dp, top = 8.dp), textStyle = TextStyle( @@ -1261,6 +1261,7 @@ fun EditorActionButtons(isUndoVisible: Boolean, onUndo: () -> Unit, onBuild: () } fun autoFixNote(rawNote: String, template: String): String { + println("ça on a t $template => $rawNote") if (rawNote.isEmpty() || rawNote == "_") return rawNote val noteRegex = Regex("([drmfsltDRFSTw][ia]?|―)[0-9'¹²³⁴₁₂₃₄,]*") @@ -1281,7 +1282,12 @@ fun autoFixNote(rawNote: String, template: String): String { when { (char in 'A'..'Y' || char in 'a'..'y') -> { if (noteIdx < actualNotes.size) { - result.append(actualNotes[noteIdx]) + val noteToAdd = actualNotes[noteIdx] + if (noteIdx > 0) { + result.append(" ") + } + + result.append(noteToAdd) noteIdx++ } else { result.append("―")