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 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é")
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
@ -399,3 +421,72 @@ fun validateMusicalInput(input: String, template: String): Boolean {
|
||||||
|
|
||||||
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()
|
||||||
|
}
|
||||||
|
|
@ -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("―")
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue