Add '<' to drop note, shift left notes & '>' to add note, shift right notes on EditingDialog
This commit is contained in:
parent
335b9b9edc
commit
13154d6acd
2 changed files with 235 additions and 72 deletions
|
|
@ -8,6 +8,8 @@ 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
|
||||||
|
import mg.dot.feufaro.transformLyricsInput
|
||||||
|
import mg.dot.feufaro.viewmodel.PartitionMetadata
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
@ -56,6 +58,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
val REGEX_PAREN_RECURSIVE = Regex("(\\([^\\(\\)]*)\\(([^\\)]*)\\)")
|
val REGEX_PAREN_RECURSIVE = Regex("(\\([^\\(\\)]*)\\(([^\\)]*)\\)")
|
||||||
val REGEX_COMMENT = Regex("\\$\\{[^\\}]*\\}")
|
val REGEX_COMMENT = Regex("\\$\\{[^\\}]*\\}")
|
||||||
val REGEX_STRIP_DC = Regex("\\$\\{D[^:]*:[^\\}]*\\}")
|
val REGEX_STRIP_DC = Regex("\\$\\{D[^:]*:[^\\}]*\\}")
|
||||||
|
val REGEX_NOTE_TOADD = Regex("([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)>([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)")
|
||||||
|
val REGEX_NOTE_TODROP = Regex("([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)<")
|
||||||
}
|
}
|
||||||
|
|
||||||
var nextTIndex: Int = -1
|
var nextTIndex: Int = -1
|
||||||
|
|
@ -465,7 +469,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
|
|
||||||
val finalString = updatedSource.joinToString("\n")
|
val finalString = updatedSource.joinToString("\n")
|
||||||
|
|
||||||
val tempDir = System.getProperty("java.io.tmpdir")
|
val tempDir = System.getProperty("java.io.tmpdir")?: ""
|
||||||
val fileName = filePath.substringAfterLast('/')
|
val fileName = filePath.substringAfterLast('/')
|
||||||
val currentTempFile = File(tempDir, fileName)
|
val currentTempFile = File(tempDir, fileName)
|
||||||
currentTempFile?.deleteOnExit()
|
currentTempFile?.deleteOnExit()
|
||||||
|
|
@ -665,6 +669,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
val resultTokens = mutableListOf<String>()
|
val resultTokens = mutableListOf<String>()
|
||||||
var currentLogicalIdx = 0
|
var currentLogicalIdx = 0
|
||||||
var i = 0
|
var i = 0
|
||||||
|
val consumedTokenIndices = mutableSetOf<Int>()
|
||||||
|
|
||||||
// println("\nSuivi pour la voix $linePrefix (Pointeur cible: $targetPointer)")
|
// println("\nSuivi pour la voix $linePrefix (Pointeur cible: $targetPointer)")
|
||||||
// println(
|
// println(
|
||||||
|
|
@ -678,6 +683,14 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
// )
|
// )
|
||||||
// )
|
// )
|
||||||
while (i < allTokens.size) {
|
while (i < allTokens.size) {
|
||||||
|
if (consumedTokenIndices.contains(i)) {
|
||||||
|
val token = allTokens[i]
|
||||||
|
if (token.matches(Regex("[drmfsltDRFSTzw].*|[-―]"))) {
|
||||||
|
currentLogicalIdx++
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
val token = allTokens[i]
|
val token = allTokens[i]
|
||||||
val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")"
|
val isStructural = token.contains("#") || token == "/" || token == "(" || token == ")"
|
||||||
|
|
||||||
|
|
@ -691,7 +704,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
if (currentLogicalIdx == targetPointer) {
|
if (currentLogicalIdx == targetPointer) {
|
||||||
val originalToken = allTokens[i]
|
val originalToken = allTokens[i]
|
||||||
|
|
||||||
//println("Insertition Multiples : $newNot")
|
// println("Insertition Multiples : $newNot")
|
||||||
val rawUserTokens = regex.findAll(revertMusicalInput(newNot)).map { it.value }.toList()
|
val rawUserTokens = regex.findAll(revertMusicalInput(newNot)).map { it.value }.toList()
|
||||||
/** Si y a '-' dans template inutile d' ajouter au notes */
|
/** Si y a '-' dans template inutile d' ajouter au notes */
|
||||||
val hasDashInTemplate = editState.templateFragment.contains("-")
|
val hasDashInTemplate = editState.templateFragment.contains("-")
|
||||||
|
|
@ -700,7 +713,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
} else {
|
} else {
|
||||||
rawUserTokens
|
rawUserTokens
|
||||||
}
|
}
|
||||||
//val newUserTokens = regex.findAll(revertMusicalInput(newNot)).map { it.value }.toList()
|
// val newUserTokens = regex.findAll(revertMusicalInput(newNot)).map { it.value }.toList()
|
||||||
val oldUserTokens = regex.findAll(revertMusicalInput(oldNot)).map { it.value }.toList()
|
val oldUserTokens = regex.findAll(revertMusicalInput(oldNot)).map { it.value }.toList()
|
||||||
|
|
||||||
val firstNewNote = newUserTokens.firstOrNull { it.matches(Regex("[drmfsltDRFSTzw].*")) } ?: ""
|
val firstNewNote = newUserTokens.firstOrNull { it.matches(Regex("[drmfsltDRFSTzw].*")) } ?: ""
|
||||||
|
|
@ -713,12 +726,36 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
|
|
||||||
val oldTokensForThisFragment = regex.findAll(revertMusicalInput(oldNot))
|
val oldTokensForThisFragment = regex.findAll(revertMusicalInput(oldNot))
|
||||||
.map { it.value }
|
.map { it.value }
|
||||||
.filter { it.matches(Regex("[drmfsltDRFSTzw].*|[-―]")) }
|
.filter { it.matches(Regex("[drmfsltDRFSTzw].*")) }
|
||||||
.toList()
|
.toList()
|
||||||
val dynamicDeleteCount = oldTokensForThisFragment.size
|
val dynamicDeleteCount = oldTokensForThisFragment.size
|
||||||
|
|
||||||
|
|
||||||
newUserTokens.forEach { ut ->
|
newUserTokens.forEach { ut ->
|
||||||
|
val dropRegex = Regex("${Regex.escape(ut)}<")
|
||||||
|
val isThisNoteDropped = dropRegex.containsMatchIn(revertMusicalInput(newNot))
|
||||||
|
|
||||||
|
if (isThisNoteDropped) {
|
||||||
|
if (ut == "z" || ut == "-" || ut == "―") {
|
||||||
|
// println("Suppression de résonance/silence ($ut)...")
|
||||||
|
var lookAheadIdx = i
|
||||||
|
var foundDashIdx = -1
|
||||||
|
|
||||||
|
while (lookAheadIdx < allTokens.size) {
|
||||||
|
val lat = allTokens[lookAheadIdx]
|
||||||
|
if ((lat.matches(Regex("[-―]")) || lat.startsWith("z")) && !consumedTokenIndices.contains(lookAheadIdx)) {
|
||||||
|
foundDashIdx = lookAheadIdx
|
||||||
|
break
|
||||||
|
}
|
||||||
|
lookAheadIdx++
|
||||||
|
}
|
||||||
|
if (foundDashIdx != -1) {
|
||||||
|
consumedTokenIndices.add(foundDashIdx)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// println("Suppression de la note $ut (gérée par le saut du fragment d'origine)")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
val processedToken = if (ut.matches(Regex("[drmfsltDRFSTzw].*"))) {
|
val processedToken = if (ut.matches(Regex("[drmfsltDRFSTzw].*"))) {
|
||||||
val (fileBase, fileLvl) = parseNoteAndOctave(allTokens[i])
|
val (fileBase, fileLvl) = parseNoteAndOctave(allTokens[i])
|
||||||
val (uBase, _) = parseNoteAndOctave(ut)
|
val (uBase, _) = parseNoteAndOctave(ut)
|
||||||
|
|
@ -731,18 +768,29 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
resultTokens.add(processedToken)
|
resultTokens.add(processedToken)
|
||||||
|
|
||||||
if (processedToken.matches(Regex("[drmfsltDRFSTzw].*|[-―]"))) {
|
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++
|
currentLogicalIdx++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var skipped = 0
|
var skipped = 0
|
||||||
while (skipped < dynamicDeleteCount && i < allTokens.size) {
|
while (skipped < dynamicDeleteCount && i < allTokens.size) {
|
||||||
|
if (consumedTokenIndices.contains(i)) {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
val nextT = allTokens[i]
|
val nextT = allTokens[i]
|
||||||
if (!nextT.contains("#") && nextT != "/" && nextT != "(" && nextT != ")") {
|
if (!nextT.contains("#") && nextT != "/" && nextT != "(" && nextT != ")") {
|
||||||
skipped++
|
skipped++
|
||||||
} else if (hasAnchor) {
|
} else if (hasAnchor) {
|
||||||
resultTokens.add(nextT) // Garder les ancres si on est en mode ancre
|
resultTokens.add(nextT)
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
@ -779,9 +827,6 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
|
||||||
initMarkerValue: String,
|
initMarkerValue: String,
|
||||||
editState: TUOEditState
|
editState: TUOEditState
|
||||||
): String {
|
): String {
|
||||||
/* On ne modifie pas le template tant que les notes ne sont pas modifiées
|
|
||||||
* Et tant qu'un marker est ajouter
|
|
||||||
* Ou une séparateur de mesure est ajouter */
|
|
||||||
val notesByVoice = editState.notesByVoice
|
val notesByVoice = editState.notesByVoice
|
||||||
val originalNotes = editState.originalNotes
|
val originalNotes = editState.originalNotes
|
||||||
val separat = editState.sep
|
val separat = editState.sep
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,17 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.text.TextRange
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.*
|
import androidx.compose.ui.unit.*
|
||||||
import androidx.compose.ui.window.Popup
|
import androidx.compose.ui.window.Popup
|
||||||
import androidx.compose.ui.window.PopupProperties
|
import androidx.compose.ui.window.PopupProperties
|
||||||
|
import mg.dot.feufaro.solfa.Solfa
|
||||||
import mg.dot.feufaro.solfa.TUOEditState
|
import mg.dot.feufaro.solfa.TUOEditState
|
||||||
import mg.dot.feufaro.solfa.TimeUnitObject
|
import mg.dot.feufaro.solfa.TimeUnitObject
|
||||||
|
|
||||||
|
|
@ -349,6 +352,36 @@ fun TUODetailDialog(
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
val activeMessages = (0..3).mapNotNull { voice ->
|
||||||
|
val currentNote = notes[voice] ?: ""
|
||||||
|
val validation = validateMusicalInput(currentNote, templateFragment)
|
||||||
|
if (validation.message.isNotEmpty() && currentNote.isNotEmpty()) validation else null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (activeMessages.isNotEmpty()) {
|
||||||
|
val firstValidation = activeMessages.first()
|
||||||
|
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 12.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
color = if (firstValidation.isValid) Color(0xFF10B981) else Color(0xFFF59E0B),
|
||||||
|
shadowElevation = 2.dp
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = firstValidation.message,
|
||||||
|
color = if (firstValidation.isValid) Color.White else Color.Black,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// --- SECTION NOTES ---
|
// --- SECTION NOTES ---
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|
@ -356,23 +389,8 @@ fun TUODetailDialog(
|
||||||
) {
|
) {
|
||||||
(0..3).forEach { voice ->
|
(0..3).forEach { voice ->
|
||||||
val currentNote = notes[voice] ?: ""
|
val currentNote = notes[voice] ?: ""
|
||||||
val isValid = validateMusicalInput(currentNote, templateFragment)
|
val validation = validateMusicalInput(currentNote, templateFragment)
|
||||||
val tooltipState = rememberTooltipState(isPersistent = false)
|
Box(modifier = Modifier.wrapContentSize()
|
||||||
Row(
|
|
||||||
) {
|
|
||||||
TooltipBox(
|
|
||||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
|
||||||
tooltip = {
|
|
||||||
if (!isValid) {
|
|
||||||
PlainTooltip(
|
|
||||||
containerColor = Color(0xFFF59E0B),
|
|
||||||
contentColor = Color.Black
|
|
||||||
) {
|
|
||||||
Text("Veuillez suivre ce format: $templateFragment")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
state = tooltipState
|
|
||||||
) {
|
) {
|
||||||
MyTextEditField(
|
MyTextEditField(
|
||||||
value = currentNote ?: "",
|
value = currentNote ?: "",
|
||||||
|
|
@ -382,7 +400,7 @@ fun TUODetailDialog(
|
||||||
customBrush = SolidColor(Color.White),
|
customBrush = SolidColor(Color.White),
|
||||||
isEditable = isEditable,
|
isEditable = isEditable,
|
||||||
isAddable = canAdd,
|
isAddable = canAdd,
|
||||||
isWarn = !isValid,
|
isWarn = !validation.isValid,
|
||||||
funTransform = ::transformMusicalInput,
|
funTransform = ::transformMusicalInput,
|
||||||
onValueChng = { newValue ->
|
onValueChng = { newValue ->
|
||||||
notes[voice] = newValue
|
notes[voice] = newValue
|
||||||
|
|
@ -390,14 +408,6 @@ fun TUODetailDialog(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LaunchedEffect(isValid, currentNote) {
|
|
||||||
if (!isValid && currentNote.isNotEmpty()) {
|
|
||||||
tooltipState.show()
|
|
||||||
} else {
|
|
||||||
tooltipState.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
@ -495,13 +505,17 @@ private fun transformMusicalInput(input: String): String {
|
||||||
.replace("¹'", "²")
|
.replace("¹'", "²")
|
||||||
.replace("²¹", "³")
|
.replace("²¹", "³")
|
||||||
.replace("²'", "³")
|
.replace("²'", "³")
|
||||||
|
.replace("³'", "⁴")
|
||||||
|
.replace("³¹", "⁴")
|
||||||
.replace("₁,", "₂")
|
.replace("₁,", "₂")
|
||||||
.replace("₁₁", "₂")
|
.replace("₁₁", "₂")
|
||||||
.replace("₂,", "₃")
|
.replace("₂,", "₃")
|
||||||
.replace("₂₁", "₃")
|
.replace("₂₁", "₃")
|
||||||
|
.replace("₃,", "₄")
|
||||||
|
.replace("₃₁", "₄")
|
||||||
.replace("'", "¹")
|
.replace("'", "¹")
|
||||||
}
|
}
|
||||||
fun transformMarkerInput(input: String): String {
|
private fun transformMarkerInput(input: String): String {
|
||||||
return input
|
return input
|
||||||
.replace("dc", "DC")
|
.replace("dc", "DC")
|
||||||
.replace("ds", "DS")
|
.replace("ds", "DS")
|
||||||
|
|
@ -522,14 +536,38 @@ fun MyTextEditField(
|
||||||
funTransform: ((String) -> String)? = null,
|
funTransform: ((String) -> String)? = null,
|
||||||
onValueChng: (String) -> Unit
|
onValueChng: (String) -> Unit
|
||||||
) {
|
) {
|
||||||
val textToShow = if (isAddable && value == "_") "" else value
|
var textFieldValueState by remember {
|
||||||
|
mutableStateOf(TextFieldValue(text = value, selection = TextRange(value.length)))
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(value) {
|
||||||
|
if (textFieldValueState.text != value) {
|
||||||
|
val newSelection = if (textFieldValueState.selection.start <= value.length) {
|
||||||
|
textFieldValueState.selection
|
||||||
|
} else {
|
||||||
|
TextRange(value.length)
|
||||||
|
}
|
||||||
|
textFieldValueState = TextFieldValue(text = value, selection = newSelection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val textToShow = if (isAddable && value == "_") "" else textFieldValueState.text
|
||||||
|
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = textToShow,
|
value = textFieldValueState,
|
||||||
onValueChange = { newValue ->
|
onValueChange = { newValue ->
|
||||||
if (isEditable || isAddable) {
|
if (isEditable || isAddable) {
|
||||||
val processedVal = funTransform?.invoke(newValue) ?: newValue
|
val transformedText = funTransform?.invoke(newValue.text) ?: newValue.text
|
||||||
onValueChng(processedVal)
|
val finalSelection = if (transformedText.length != newValue.text.length) {
|
||||||
|
if (newValue.selection.start <= transformedText.length) newValue.selection else TextRange(transformedText.length)
|
||||||
|
} else {
|
||||||
|
newValue.selection
|
||||||
|
}
|
||||||
|
|
||||||
|
textFieldValueState = TextFieldValue(
|
||||||
|
text = transformedText,
|
||||||
|
selection = finalSelection
|
||||||
|
)
|
||||||
|
onValueChng(transformedText)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
|
|
@ -555,17 +593,60 @@ fun MyTextEditField(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validateMusicalInput(input: String, template: String): Boolean {
|
data class ValidNoteResult(
|
||||||
if (template.isEmpty()) return true
|
val isValid: Boolean,
|
||||||
|
val message: String
|
||||||
|
)
|
||||||
|
|
||||||
val cleanInput = input.replace("(", "").replace(")", "").replace(" ", "")
|
private fun validateMusicalInput(input: String, template: String): ValidNoteResult {
|
||||||
|
if (template.isEmpty()) return ValidNoteResult(false, "")
|
||||||
|
|
||||||
|
/* Ajout > */
|
||||||
|
val matchAddNote = Solfa.REGEX_NOTE_TOADD.find(input)
|
||||||
|
if (matchAddNote != null) {
|
||||||
|
val addedNote = matchAddNote.value.replace(">", "")
|
||||||
|
val inserted = matchAddNote.groupValues[1] + matchAddNote.groupValues[2]
|
||||||
|
val shifted = matchAddNote.groupValues[3] + matchAddNote.groupValues[4]
|
||||||
|
|
||||||
|
return ValidNoteResult(
|
||||||
|
isValid = true,
|
||||||
|
message = "+ $inserted ↠ $shifted"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Suppress < */
|
||||||
|
if (input.contains("<")) {
|
||||||
|
val matchDropNote = Solfa.REGEX_NOTE_TODROP.find(input)
|
||||||
|
val isValidDrop = matchDropNote != null/* && input.endsWith("<")*/
|
||||||
|
|
||||||
|
if (isValidDrop) {
|
||||||
|
val noteBase: String = matchDropNote.groupValues[1]
|
||||||
|
val octave: String = matchDropNote.groupValues[2]
|
||||||
|
val droppedNote = noteBase + octave
|
||||||
|
return ValidNoteResult(
|
||||||
|
isValid = true,
|
||||||
|
message = "$droppedNote ⌫ ?"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
return ValidNoteResult(
|
||||||
|
isValid = false,
|
||||||
|
message = "< incorrect"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val cleanInput = input
|
||||||
|
.replace("(", "")
|
||||||
|
.replace(")", "")
|
||||||
|
.replace(" ", "")
|
||||||
|
.replace(Solfa.REGEX_NOTE_TOADD, "$3$4")
|
||||||
|
.replace(Solfa.REGEX_NOTE_TODROP, "$1$2")
|
||||||
val cleanTemplate = template.replace("(", "").replace(")", "")
|
val cleanTemplate = template.replace("(", "").replace(")", "")
|
||||||
|
|
||||||
val regexPattern = buildString {
|
val regexPattern = buildString {
|
||||||
cleanTemplate.forEachIndexed { index, char ->
|
cleanTemplate.forEachIndexed { index, char ->
|
||||||
when (char) {
|
when (char) {
|
||||||
in 'A'..'Y', in 'a'..'y' -> {
|
in 'A'..'Y', in 'a'..'y' -> {
|
||||||
append("(?:―|di|ri|fi|si|ta|[drmfsltz])[¹²³₁₂₃]*")
|
append("(?:―|di|ri|fi|si|ta|[drmfsltz])[₄₃₂₁¹²³⁴]*")
|
||||||
}
|
}
|
||||||
'z' -> {
|
'z' -> {
|
||||||
append(" ?")
|
append(" ?")
|
||||||
|
|
@ -602,21 +683,59 @@ fun validateMusicalInput(input: String, template: String): Boolean {
|
||||||
└──────────────────────────────────────────────────────────
|
└──────────────────────────────────────────────────────────
|
||||||
""".trimIndent())*/
|
""".trimIndent())*/
|
||||||
|
|
||||||
return isMatched
|
return if (isMatched) {
|
||||||
|
ValidNoteResult(isValid = true, message = "")
|
||||||
|
} else {
|
||||||
|
ValidNoteResult(isValid = false, message = "Erreur")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun validateLyricsInput(cleanInput: String, template: String): Boolean {
|
||||||
|
val vowels = "aeiouyòàéìỳAEIOUY"
|
||||||
|
|
||||||
|
val expectedSyllableCount = template.count { it.lowercaseChar() != 'z' && it.isLetter() }
|
||||||
|
|
||||||
|
val syllableRegex = Regex("[^aeiouyòàéìỳAEIOUY]*[aeiouyòàéìỳAEIOUY]+[^aeiouyòàéìỳAEIOUY]*", RegexOption.IGNORE_CASE)
|
||||||
|
|
||||||
|
val foundSyllables = syllableRegex.findAll(cleanInput).toList()
|
||||||
|
val rawSyllableCount = foundSyllables.size
|
||||||
|
val backslashCount = cleanInput.count { it == '\\' }
|
||||||
|
|
||||||
|
val actualSyllableCount = if (backslashCount > 0) {
|
||||||
|
(rawSyllableCount - backslashCount).coerceAtLeast(1)
|
||||||
|
} else {
|
||||||
|
rawSyllableCount
|
||||||
|
}
|
||||||
|
/*println("""
|
||||||
|
┌──────────────────────────────────────────────────────────
|
||||||
|
│ VALIDATION LYRICS
|
||||||
|
├──────────────────────────────────────────────────────────
|
||||||
|
│ Template : "$template"
|
||||||
|
│ Input : "$cleanInput"
|
||||||
|
│ Attendu : $expectedSyllableCount syllabes
|
||||||
|
│ Trouvé : $actualSyllableCount syllabes
|
||||||
|
└──────────────────────────────────────────────────────────
|
||||||
|
""".trimIndent())*/
|
||||||
|
|
||||||
|
return actualSyllableCount == expectedSyllableCount
|
||||||
|
}
|
||||||
|
|
||||||
public fun getGlobalTemplate(editState: TUOEditState): String {
|
public fun getGlobalTemplate(editState: TUOEditState): String {
|
||||||
// println("\n--- DÉBOGAGE getGlobalTemplate ---")
|
// println("\n--- DÉBOGAGE getGlobalTemplate ---")
|
||||||
|
|
||||||
val voiceTemplates = (0..3).map { voiceIdx ->
|
val voiceTemplates = (0..3).map { voiceIdx ->
|
||||||
val notes = editState.notesByVoice[voiceIdx] ?: ""
|
val notes = editState.notesByVoice[voiceIdx] ?: ""
|
||||||
val tpl = notesToTemplate(notes)
|
val filteredNotes = notes
|
||||||
// println("Voix $voiceIdx | Notes: '$notes' -> Template: '$tpl'")
|
.replace(Solfa.REGEX_NOTE_TOADD, "$3$4")
|
||||||
|
.replace("<", "")
|
||||||
|
val tpl = notesToTemplate(filteredNotes)
|
||||||
|
// println("Voix $voiceIdx | Notes: '$filteredNotes' -> Template: '$tpl'")
|
||||||
tpl
|
tpl
|
||||||
}
|
}
|
||||||
|
|
||||||
val rythmicTemplates = voiceTemplates.map { tpl -> tpl.replace(Regex("[()]"), "") }
|
val rythmicTemplates = voiceTemplates.map { tpl -> tpl.replace(Regex("[()]"), "") }
|
||||||
val maxLen = rythmicTemplates.maxOfOrNull { it.length } ?: 1
|
val maxLen = rythmicTemplates.maxOfOrNull { it.length } ?: 1
|
||||||
// println("Longueur rythmique max détectée: $maxLen")
|
// println("Longueur rythmique max détectée: $maxLen")
|
||||||
|
|
||||||
val openBefore = BooleanArray(maxLen)
|
val openBefore = BooleanArray(maxLen)
|
||||||
val closeBefore = BooleanArray(maxLen)
|
val closeBefore = BooleanArray(maxLen)
|
||||||
|
|
@ -673,7 +792,6 @@ public fun getGlobalTemplate(editState: TUOEditState): String {
|
||||||
previousChars.contains('.') && chars.contains(' ') -> 'z'
|
previousChars.contains('.') && chars.contains(' ') -> 'z'
|
||||||
else -> ' '
|
else -> ' '
|
||||||
}
|
}
|
||||||
// Supprimer espace avant une virgule
|
|
||||||
if (combinedChar == ',' && result.isNotEmpty() && result.last() == ' ') {
|
if (combinedChar == ',' && result.isNotEmpty() && result.last() == ' ') {
|
||||||
result.deleteCharAt(result.length - 1)
|
result.deleteCharAt(result.length - 1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue