Auto Update Template struct when Editing notes - template with note format

This commit is contained in:
Hasinjato 2026-05-22 12:32:11 +03:00
parent 0bd92b6143
commit 2f536ba45b
3 changed files with 176 additions and 97 deletions

View file

@ -4,6 +4,7 @@ import SharedScreenModel
import kotlinx.coroutines.* import kotlinx.coroutines.*
import mg.dot.feufaro.FileRepository import mg.dot.feufaro.FileRepository
import mg.dot.feufaro.SaveSettings import mg.dot.feufaro.SaveSettings
import mg.dot.feufaro.getGlobalTemplate
import mg.dot.feufaro.launchFilePicker import mg.dot.feufaro.launchFilePicker
import mg.dot.feufaro.midi.MidiPitch import mg.dot.feufaro.midi.MidiPitch
import mg.dot.feufaro.midi.MidiWriterKotlin 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 */ /* MODIF SOURCE Note&Lyrics */
val updatedSource = updateSourceLines(lines, templatArray, targetIdx, editState) 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()) val templateUpd = reconstructIt(markerUpdated, measureString[0].digitToInt())
@ -557,94 +558,49 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
lines: MutableList<String>, lines: MutableList<String>,
fragments: List<String>, fragments: List<String>,
targetIdx: Int, targetIdx: Int,
deleteCount: Int,
editState: TUOEditState editState: TUOEditState
) { ) {
editState.lyricsByStanza.forEach { (stanzaNum, newSyllable) -> editState.lyricsByStanza.forEach { (stanzaNum, newSyllable) ->
val lineYIdx = lines.indexOfFirst { it.matches(Regex("^[Y]$stanzaNum:.*")) } val lineIdx = lines.indexOfFirst { it.matches(Regex("^[YE]$stanzaNum:.*")) }
val lineEIdx = lines.indexOfFirst { it.matches(Regex("^[E]$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 tokens = tokenizeLyricsPreserving(body)
val currentLine = lines[lineIdx]
val prefix = currentLine.substringBefore(":") + ":"
val lyricsBody = currentLine.substringAfter(":")
// On traite les paroles pour avoir la liste brute val textTokenIndices = tokens.indices.filter {
val lyricsContent = if(lineYIdx != -1) smartYLyrics(lyricsBody) else smartELyrics(lyricsBody) !tokens[it].startsWith("\${") && tokens[it] != "/"
// 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)
} }
// 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<String> {
val result = mutableListOf<String>()
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( private fun updateMarkerSource(
templateLine: String, templateLine: String,
targetPointer: Int, targetPointer: Int,
newMarkerValue: String newMarkerValue: String,
editState: TUOEditState
): String { ): String {
val isT0 = templateLine.startsWith("T0") val isT0 = templateLine.startsWith("T0")
@ -872,9 +829,12 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
var blockToProcess = if (currentLogicalIdx == targetPointer) { var blockToProcess = if (currentLogicalIdx == targetPointer) {
actionTaken = if (newMarkerValue.isEmpty()) "DELETE" else "UPDATE" 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 { } else {
rawBlockText rawBlockText
} }
@ -1212,10 +1172,21 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
fun encodeDuration(duration: Int): String { fun encodeDuration(duration: Int): String {
return when (duration) { return when (duration) {
1 -> "1"; 2 -> "2"; 3 -> "3"; 4 -> "4" 1 -> "1";
6 -> "6"; 8 -> "8"; 10 -> "A"; 11 -> "B"; 2 -> "2";
12 -> "C"; 14 -> "E"; 16 -> "G"; 20 -> "K"; 3 -> "3";
24 -> "O"; 28 -> "S"; 32 -> "W" 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() else -> duration.toString()
} }
} }
@ -1378,6 +1349,17 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
if (prefixBuffer.isNotEmpty()) sb.insert(0, prefixBuffer.toString()) if (prefixBuffer.isNotEmpty()) sb.insert(0, prefixBuffer.toString())
var resultBlock = sb.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")) { if (resultBlock.contains("31")) {
resultBlock = resultBlock.replace("31", "y") resultBlock = resultBlock.replace("31", "y")
// println(" -> Motif 'y' détecté") // println(" -> Motif 'y' détecté")

View file

@ -60,12 +60,34 @@ fun TUODetailDialog(
var marker by remember { mutableStateOf(editState.marker) } 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<String>().apply { mutableStateListOf<String>().apply {
val existing = editState.lyricsByStanza[currentStanza] ?: "" val existing = editState.lyricsByStanza[currentStanza] ?: ""
if (existing.isEmpty() && canAdd) add("_") else add(existing) if (existing.isEmpty() && canAdd) add("_") else add(existing)
} }
} }
val lyricsLines = result
.flatMap { it.split("\n") }
.map { it.trim() }
.toMutableList()
var canAddMark = mutableStateOf(false) var canAddMark = mutableStateOf(false)
Popup( Popup(
@ -109,8 +131,8 @@ fun TUODetailDialog(
color = Color.Yellow, color = Color.Yellow,
customPadding = 8.dp, customPadding = 8.dp,
customBrush = SolidColor(Color.White), customBrush = SolidColor(Color.White),
isEditable = isEditable, isEditable = false,
isAddable = canAdd, isAddable = false,
onValueChng = { templateFragment = it } onValueChng = { templateFragment = it }
) )
@ -329,7 +351,7 @@ fun MyTextEditField(
}, },
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
autoCorrectEnabled = false, autoCorrectEnabled = false,
keyboardType = KeyboardType.Password keyboardType = KeyboardType.Ascii
), ),
textStyle = TextStyle( textStyle = TextStyle(
color = color, color = color,
@ -398,4 +420,73 @@ fun validateMusicalInput(input: String, template: String): Boolean {
""".trimIndent())*/ """.trimIndent())*/
return isMatched 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()
} }

View file

@ -849,7 +849,7 @@ fun LazyVerticalGridTUO(
i to fixedNote i to fixedNote
}, },
lyricsByStanza = (1..currentStanza).associate { s -> lyricsByStanza = (1..currentStanza).associate { s ->
s to oneTUO.getSingleSyllable(s).firstOrNull().orEmpty() s to oneTUO.lyricsAsMultiString(currentStanza)
}.toMutableMap(), }.toMutableMap(),
templateFragment = oneTUO.pTemplate.template, templateFragment = oneTUO.pTemplate.template,
marker = listOfNotNull( marker = listOfNotNull(
@ -1229,7 +1229,7 @@ fun EditSourceCompose(
onValueChange = { codeContent = it }, onValueChange = { codeContent = it },
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
autoCorrectEnabled = false, autoCorrectEnabled = false,
keyboardType = KeyboardType.Password keyboardType = KeyboardType.Ascii
), ),
modifier = Modifier.fillMaxSize().padding(start = 8.dp, top = 8.dp), modifier = Modifier.fillMaxSize().padding(start = 8.dp, top = 8.dp),
textStyle = TextStyle( textStyle = TextStyle(
@ -1261,6 +1261,7 @@ fun EditorActionButtons(isUndoVisible: Boolean, onUndo: () -> Unit, onBuild: ()
} }
fun autoFixNote(rawNote: String, template: String): String { fun autoFixNote(rawNote: String, template: String): String {
println("ça on a t $template => $rawNote")
if (rawNote.isEmpty() || rawNote == "_") return rawNote if (rawNote.isEmpty() || rawNote == "_") return rawNote
val noteRegex = Regex("([drmfsltDRFSTw][ia]?|―)[0-9'¹²³⁴₁₂₃₄,]*") val noteRegex = Regex("([drmfsltDRFSTw][ia]?|―)[0-9'¹²³⁴₁₂₃₄,]*")
@ -1281,7 +1282,12 @@ fun autoFixNote(rawNote: String, template: String): String {
when { when {
(char in 'A'..'Y' || char in 'a'..'y') -> { (char in 'A'..'Y' || char in 'a'..'y') -> {
if (noteIdx < actualNotes.size) { if (noteIdx < actualNotes.size) {
result.append(actualNotes[noteIdx]) val noteToAdd = actualNotes[noteIdx]
if (noteIdx > 0) {
result.append(" ")
}
result.append(noteToAdd)
noteIdx++ noteIdx++
} else { } else {
result.append("") result.append("")