Auto Update Template struct when Editing notes - template with note format
This commit is contained in:
parent
0bd92b6143
commit
2f536ba45b
3 changed files with 176 additions and 97 deletions
|
|
@ -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<String>,
|
||||
fragments: List<String>,
|
||||
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
|
||||
|
||||
if (lineIdx != -1) {
|
||||
val currentLine = lines[lineIdx]
|
||||
val prefix = currentLine.substringBefore(":") + ":"
|
||||
val lyricsBody = currentLine.substringAfter(":")
|
||||
val body = currentLine.substringAfter(":")
|
||||
|
||||
// On traite les paroles pour avoir la liste brute
|
||||
val lyricsContent = if(lineYIdx != -1) smartYLyrics(lyricsBody) else smartELyrics(lyricsBody)
|
||||
val tokens = tokenizeLyricsPreserving(body)
|
||||
|
||||
// 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 textTokenIndices = tokens.indices.filter {
|
||||
!tokens[it].startsWith("\${") && tokens[it] != "/"
|
||||
}
|
||||
|
||||
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("--- 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))
|
||||
}
|
||||
|
||||
// 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++
|
||||
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]}")
|
||||
}
|
||||
}
|
||||
|
||||
val rawResult = allTokens.filter { it.isNotEmpty() }.joinToString(" ")
|
||||
println("allToken ${allTokens.joinToString("")}")
|
||||
// println("RAWRes ${rawResult}")
|
||||
val formattedResult = rawResult
|
||||
.replace(Regex("-\\s+"), "")
|
||||
.replace(Regex("-"), "")
|
||||
private fun tokenizeLyricsPreserving(input: String): MutableList<String> {
|
||||
val result = mutableListOf<String>()
|
||||
val regex = Regex("""(\$\{.*?\})|(/)|([^\s/]+(?:\s+|))""")
|
||||
|
||||
println("FORMATTER $formattedResult")
|
||||
|
||||
val lyrIdx = lines.indexOfFirst { line ->
|
||||
line.contains(Regex("^[EY]$stanzaNum:"))
|
||||
regex.findAll(input).forEach { match ->
|
||||
result.add(match.value)
|
||||
}
|
||||
val lyrics = lines[lyrIdx]
|
||||
println("Originl $lyrics")
|
||||
|
||||
|
||||
lines[lineIdx] = prefix + formattedResult
|
||||
//lines[lineIdx] = prefix + reconstructLyrics(finalTokens)
|
||||
}
|
||||
}
|
||||
// println("--- FIN DEBUG ---")
|
||||
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é")
|
||||
|
|
|
|||
|
|
@ -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<String>().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,
|
||||
|
|
@ -399,3 +421,72 @@ fun validateMusicalInput(input: String, template: String): Boolean {
|
|||
|
||||
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()
|
||||
}
|
||||
|
|
@ -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("―")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue