From bba28fd7c16f3e8c63f07fa371316304fcca4476 Mon Sep 17 00:00:00 2001 From: dotmg Date: Fri, 28 Nov 2025 10:06:54 +0100 Subject: [PATCH] Hasinjato : FilePicker --- .../kotlin/mg/dot/feufaro/LauchFilePicker.kt | 8 + .../kotlin/mg/dot/feufaro/ScreenSolfa.kt | 43 ++- .../kotlin/mg/dot/feufaro/solfa/Solfa.kt | 254 ++++++++++++------ .../feufaro/viewmodel/SharedScreenModel.kt | 28 +- .../dot/feufaro/viewmodel/SolfaScreenModel.kt | 10 +- .../kotlin/mg/dot/feufaro/LauchFilePicker.kt | 32 +++ 6 files changed, 272 insertions(+), 103 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/LauchFilePicker.kt create mode 100644 composeApp/src/desktopMain/kotlin/mg/dot/feufaro/LauchFilePicker.kt diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/LauchFilePicker.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/LauchFilePicker.kt new file mode 100644 index 0000000..3370334 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/LauchFilePicker.kt @@ -0,0 +1,8 @@ +package mg.dot.feufaro + +import androidx.compose.runtime.Composable + +expect fun launchFilePicker( + mimeTypes: Array, + onFileSelected: (path: String?) -> Unit +): Unit \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt index 118b354..8f6f722 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt @@ -51,8 +51,8 @@ object ScreenSolfa : Screen { @Composable override fun Content() { val solfaScreenModel = koinScreenModel() - var menuPosition by remember { mutableStateOf(Offset.Zero)} - var showContextualMenu by remember { mutableStateOf(false)} + var menuPosition by remember { mutableStateOf(Offset.Zero) } + var showContextualMenu by remember { mutableStateOf(false) } var gridWidthPx by rememberSaveable { mutableStateOf(0) } val sharedScreenModel = koinScreenModel() val tuoList by sharedScreenModel.tuoList.collectAsState() @@ -61,7 +61,9 @@ object ScreenSolfa : Screen { val gridTUOData = GridTUOData(measure, tuoList, stanza) val coroutineScope = rememberCoroutineScope() val scrollState = rememberScrollState() - Box(Modifier.fillMaxSize() + + Box( + Modifier.fillMaxSize() ) { Column( Modifier @@ -122,13 +124,35 @@ object ScreenSolfa : Screen { gridWidthPx = gridWidthPx, onGridWidthMeasured = { width -> gridWidthPx = width } ) - MGButton(onClick = { - //showContent = !showContent + FlowRow( + horizontalArrangement = Arrangement.spacedBy(16.dp), + verticalArrangement = Arrangement.spacedBy(4.dp) + ) { + MGButton(onClick = { + //showContent = !showContent - solfaScreenModel.loadNextInPlaylist() - }, modifier = Modifier.height(40.dp)) { - val nextLabel: String by sharedScreenModel.nextLabel.collectAsState() - Text(nextLabel) + solfaScreenModel.loadNextInPlaylist() + }, modifier = Modifier.height(40.dp)) { + val nextLabel: String by sharedScreenModel.nextLabel.collectAsState() + Text(nextLabel) + } + MGButton(onClick = { + /*println("Load btn clicked") + launchFilePicker( + mimeTypes = arrayOf("text/plain"), + onFileSelected = { path -> + if (path != null) { + println("fichier $path"); + } else { + println("Pas de dichier") + } + } + )*/ + solfaScreenModel.loadCustomFile() + }) { + val loadFile: String by sharedScreenModel.loadFile.collectAsState() + Text(loadFile) + } } FlowRow( modifier = Modifier.fillMaxWidth() @@ -168,6 +192,7 @@ object ScreenSolfa : Screen { ) } } + @Throws(ObjectStreamException::class) // C'est une méthode de sérialisation Java, donc l'exception est nécessaire private fun readResolve(): Any { return this // Toujours retourner l'instance unique de ce singleton 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 5a9c311..34b93cc 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt @@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import mg.dot.feufaro.FileRepository +import mg.dot.feufaro.launchFilePicker import mg.dot.feufaro.midi.MidiPitch import mg.dot.feufaro.midi.MidiWriterKotlin @@ -23,28 +24,31 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository private var refrainBeginsAt = -1 private var smartLyricsType = "L" private val pitches = mutableListOf() + companion object { var currentFile = "" - val REGEX_SHIFT_PAREN_UPWARD = Regex("([^a-yA-Y\\(\\[]+)([\\)\\]])") - val REGEX_SHIFT_PAREN_DOWNWARD = Regex("([\\(\\[])([^a-yA-y\\)\\]]+)") - val REGEX_COLLAPSE_PARENS_OPEN = Regex("([\\(\\[])[\\(\\[]+") + val REGEX_SHIFT_PAREN_UPWARD = Regex("([^a-yA-Y\\(\\[]+)([\\)\\]])") + val REGEX_SHIFT_PAREN_DOWNWARD = Regex("([\\(\\[])([^a-yA-y\\)\\]]+)") + val REGEX_COLLAPSE_PARENS_OPEN = Regex("([\\(\\[])[\\(\\[]+") val REGEX_COLLAPSE_PARENS_CLOSE = Regex("([\\)\\]])[\\)\\]]+") - val REGEX_SINGLETON_PAREN = Regex("[\\(\\[](.[¹²³₁₂₃]?)?[\\)\\]]") // @todo - val REGEX_SHIFT_COMMA_UPWARD = Regex("([^a-zA-Z]+)([',])") - val REGEX_TEMPLATE_COMMENT = Regex("(\\$[A-Z]|\\$\\{[^\\}]*\\})") - val REGEX_REPETITION = Regex("([zwdrmfslt-](?>[',]*))([1-9][0-9]*)", RegexOption.IGNORE_CASE) - val REGEX_PARSE_META = Regex("\\|(?=[a-z]:)") - val REGEX_LYRICS_COMMENT = Regex("\\$\\{([^\\}][^:]*:([^\\}]*))\\}|\\$\\{R!\\}") - val REGEX_LYRICS_REPETITION = Regex("_(\\d+)") - val REGEX_VOWELS_STAGE1 = Regex("[aeiouyòàéìỳ](?![,;\\.\\-:!\\?\\}»_\"]*([ aeiouyòàéìỳ/]|_[1-9]))", RegexOption.IGNORE_CASE) - val REGEX_VOWELS_STAGE2 = Regex("(?<=[aeiouyòàéìỳ])_([,;\\.\\-:!\\?\\}»_\"]+)", RegexOption.IGNORE_CASE) - val REGEX_VOWELS_STAGE3 = Regex("_([\\?\\!:,;\\.»\\)]+|(\\$\\{[^\\}]*\\})+)") - val REGEX_MALAGASY_MN = Regex("([aeio])_([nm])([tdjkbp])") - val REGEX_MALAGASY_MN_STAGE2 = Regex("_([mn])-") - val REGEX_PAREN_RECURSIVE = Regex("(\\([^\\(\\)]*)\\(([^\\)]*)\\)") - val REGEX_COMMENT = Regex("\\$\\{[^\\}]*\\}") - val REGEX_STRIP_DC = Regex("\\$\\{D[^:]*:[^\\}]*\\}") + val REGEX_SINGLETON_PAREN = Regex("[\\(\\[](.[¹²³₁₂₃]?)?[\\)\\]]") // @todo + val REGEX_SHIFT_COMMA_UPWARD = Regex("([^a-zA-Z]+)([',])") + val REGEX_TEMPLATE_COMMENT = Regex("(\\$[A-Z]|\\$\\{[^\\}]*\\})") + val REGEX_REPETITION = Regex("([zwdrmfslt-](?>[',]*))([1-9][0-9]*)", RegexOption.IGNORE_CASE) + val REGEX_PARSE_META = Regex("\\|(?=[a-z]:)") + val REGEX_LYRICS_COMMENT = Regex("\\$\\{([^\\}][^:]*:([^\\}]*))\\}|\\$\\{R!\\}") + val REGEX_LYRICS_REPETITION = Regex("_(\\d+)") + val REGEX_VOWELS_STAGE1 = + Regex("[aeiouyòàéìỳ](?![,;\\.\\-:!\\?\\}»_\"]*([ aeiouyòàéìỳ/]|_[1-9]))", RegexOption.IGNORE_CASE) + val REGEX_VOWELS_STAGE2 = Regex("(?<=[aeiouyòàéìỳ])_([,;\\.\\-:!\\?\\}»_\"]+)", RegexOption.IGNORE_CASE) + val REGEX_VOWELS_STAGE3 = Regex("_([\\?\\!:,;\\.»\\)]+|(\\$\\{[^\\}]*\\})+)") + val REGEX_MALAGASY_MN = Regex("([aeio])_([nm])([tdjkbp])") + val REGEX_MALAGASY_MN_STAGE2 = Regex("_([mn])-") + val REGEX_PAREN_RECURSIVE = Regex("(\\([^\\(\\)]*)\\(([^\\)]*)\\)") + val REGEX_COMMENT = Regex("\\$\\{[^\\}]*\\}") + val REGEX_STRIP_DC = Regex("\\$\\{D[^:]*:[^\\}]*\\}") } + var nextTIndex: Int = -1 var nextNIndex: Int = -1 var nextLIndex: Int = -1 @@ -75,26 +79,31 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository templateCharArray.forEach { // D.-R when (it) { 'D', 'R', 'M', 'F', 'S', 'L', 'T', - 'd', 'r', 'm', 'f', 's', 'l', 't', -> { + 'd', 'r', 'm', 'f', 's', 'l', 't', + -> { nextNIndex++ if (!inGroup) { - nextLIndex ++ + nextLIndex++ if (L.size > nextLIndex) { unitObject.addLyrics(L[nextLIndex]) } } - N.forEachIndexed { voiceNumber, pOneVoiceNote -> run { - unitObject.addNote(voiceNumber, pOneVoiceNote.oneVoiceNote, nextNIndex, nextMarker) + N.forEachIndexed { voiceNumber, pOneVoiceNote -> + run { + unitObject.addNote(voiceNumber, pOneVoiceNote.oneVoiceNote, nextNIndex, nextMarker) } } nextMarker = '0' } - '.', ',', -> + + '.', ',' -> unitObject.addMarker(it) + '-', 'Z', 'z', 'w' -> N.indices.forEach { voiceNumber -> unitObject.addBlank(voiceNumber, it) } + '(', '[' -> { nextLIndex++ if (L.size > nextLIndex) { @@ -102,6 +111,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository } inGroup = true } + ')', ']' -> { inGroup = false } @@ -112,11 +122,13 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository } nextTimeUnitObject() } + fun loadSolfa() { val sourceFileName: String = sharedScreenModel.currentPlayed() sharedScreenModel.reset() parse(sourceFileName) } + fun loadNextInPlaylist() { val playlist = sharedScreenModel.playlist.value if (playlist.isNotEmpty()) { @@ -124,6 +136,24 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository loadSolfa() } } + + fun loadFile() { + println("Load btn clicked") + launchFilePicker( + mimeTypes = arrayOf("text/plain"), + onFileSelected = { path -> + if (path != null) { + println("fichier $path"); + sharedScreenModel.reset() + parse(path) + //loadSolfa() + } else { + println("Pas de dichier") + } + } + ) + } + fun parse(sourceFile: String) { currentFile = sourceFile val parseScope = CoroutineScope(Dispatchers.Default) @@ -166,39 +196,40 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository } catch (e: Exception) { println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}") } - val pitches = pitches.sortedWith( compareBy({it.tick}, {it.voiceNumber})) + val pitches = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber })) val midiWriter = MidiWriterKotlin(fileRepository) midiWriter.process(pitches) midiWriter.save("whawyd3.mid") } } + private fun rearrangeNote(noteString: String, infiniteIter: Int = 0): String { var result: String = noteString - result = result .replace(REGEX_PAREN_RECURSIVE, "$1$2") - result = result .replace(REGEX_PAREN_RECURSIVE, "$1$2") - result = result .replace(REGEX_PAREN_RECURSIVE, "$1$2") - result = result .replace(REGEX_SHIFT_PAREN_UPWARD, "$2$1") - result = result .replace(REGEX_SHIFT_PAREN_DOWNWARD, "$2$1") - result = result .replace(REGEX_COLLAPSE_PARENS_OPEN, "$1") - result = result .replace(REGEX_COLLAPSE_PARENS_CLOSE, "$1") - result = result .replace(REGEX_SINGLETON_PAREN, "$1") - result = result .replace(REGEX_SHIFT_COMMA_UPWARD, "$2$1") + result = result.replace(REGEX_PAREN_RECURSIVE, "$1$2") + result = result.replace(REGEX_PAREN_RECURSIVE, "$1$2") + result = result.replace(REGEX_PAREN_RECURSIVE, "$1$2") + result = result.replace(REGEX_SHIFT_PAREN_UPWARD, "$2$1") + result = result.replace(REGEX_SHIFT_PAREN_DOWNWARD, "$2$1") + result = result.replace(REGEX_COLLAPSE_PARENS_OPEN, "$1") + result = result.replace(REGEX_COLLAPSE_PARENS_CLOSE, "$1") + result = result.replace(REGEX_SINGLETON_PAREN, "$1") + result = result.replace(REGEX_SHIFT_COMMA_UPWARD, "$2$1") if (result != noteString && infiniteIter < 75) { - result = rearrangeNote(result, infiniteIter+1) + result = rearrangeNote(result, infiniteIter + 1) } if (infiniteIter >= 70) { println("$infiniteIter\n$noteString\n$result") } return result } + private fun preloadN(noteLine: String) { val voiceNumber = noteLine.substring(0, 1).toInt() val templateComments = mutableListOf() - val templateStripped = REGEX_TEMPLATE_COMMENT.replace(templateString) { - matchResult -> - templateComments.add(matchResult.value) - "&" + val templateStripped = REGEX_TEMPLATE_COMMENT.replace(templateString) { matchResult -> + templateComments.add(matchResult.value) + "&" } val templateCharArray = templateStripped.toCharArray() val noteLine = noteLine.replace(REGEX_TEMPLATE_COMMENT, "") @@ -234,12 +265,12 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository if (it in listOf('d', 'r', 'm', 'f', 's', 'l', 't', 'D', 'R', 'M', 'F', 'S', 'L', 'T')) { noteChar2 += anchorSuffix if ( - (anchorNote == 'r' && it in listOf('d', 'D')) - || (anchorNote == 'm' && it in listOf('d', 'D', 'r', 'R')) - || (anchorNote == 'f' && it in listOf('d', 'D', 'r', 'R', 'm', 'M')) + (anchorNote == 'r' && it in listOf('d', 'D')) + || (anchorNote == 'm' && it in listOf('d', 'D', 'r', 'R')) + || (anchorNote == 'f' && it in listOf('d', 'D', 'r', 'R', 'm', 'M')) || (anchorNote == 's' && it !in listOf('s', 'S', 'l', 'L', 't', 'T')) - || (anchorNote == 'l' && it !in listOf( 'l', 'L', 't', 'T')) - || (anchorNote == 't' && it !in listOf( 't', 'T')) + || (anchorNote == 'l' && it !in listOf('l', 'L', 't', 'T')) + || (anchorNote == 't' && it !in listOf('t', 'T')) ) { noteChar2 += '\'' } @@ -253,28 +284,33 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository val noteCharIterator = noteChar2.replace("/", "").toCharArray().iterator() var result = "" var firstInLoop = true - var replacement : Char = 'x' + var replacement: Char = 'x' templateCharArray.forEach { when (it) { 'D', 'R', 'M', 'F', 'S', 'L', 'T', - 'd', 'r', 'm', 'f', 's', 'l', 't', -> { - if (firstInLoop) { - replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd' - firstInLoop = false - } - if (replacement == '(') { - result += replacement - replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd' - } - for (repetition in 0 until 6) { - result += replacement - replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd' - if (replacement in setOf('d', 'r', 'm', 'f', 's', 'l', 't' , 'z', '(', - 'D', 'R', 'M', 'F', 'S', 'L', 'T', '-', 'w', 'Z')) { - break - } + 'd', 'r', 'm', 'f', 's', 'l', 't', + -> { + if (firstInLoop) { + replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd' + firstInLoop = false + } + if (replacement == '(') { + result += replacement + replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd' + } + for (repetition in 0 until 6) { + result += replacement + replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd' + if (replacement in setOf( + 'd', 'r', 'm', 'f', 's', 'l', 't', 'z', '(', + 'D', 'R', 'M', 'F', 'S', 'L', 'T', '-', 'w', 'Z' + ) + ) { + break } + } } + 'w', '-' -> result += "~" 'z', 'Z' -> result += "@" ',' -> result += ";" @@ -285,6 +321,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository result = rearrangeNote(result) loadN(voiceNumber, result, templateComments) } + private suspend fun parseOneLine(line: String) { val index: Int val value: String @@ -292,7 +329,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository if (lineLength <= 3) { return } - val key : String = line.trim().substring(0, 1) + val key: String = line.trim().substring(0, 1) if (lineLength > 4 && line.trim().substring(3, 4) == ":") { index = line.substring(1, 3).toIntOrNull() ?: 0 value = line.trim().substring(4) @@ -305,27 +342,36 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository "T" -> if (index == 0) { loadT(value) } + "N" -> // Les lignes de type N seront parsées à la fin. preloadN() se chargera d'abord de réarranger la ligne // pour bien gérer les parenthèses. - unparsedNote.add(index.toString()+value) + unparsedNote.add(index.toString() + value) + "M" -> loadM(value) + "L" -> loadL(index, value) + "Y" -> loadY(index, value) + "E" -> loadE(index, value) + "U" -> loadU(value) + "O" -> loadO(index, value) + "I" -> loadI(value) } } + suspend fun loadI(line: String) { val lineSplit = line.split(":") val lastSlashInFilename = currentFile.lastIndexOf('/') @@ -349,10 +395,11 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository parseOneLine(it) } } - } catch(e: Exception) { + } catch (e: Exception) { } } + private fun loadM(value: String) { val metaChunks: List = value.split(REGEX_PARSE_META) metaChunks.forEach { parseMeta(it) } @@ -363,6 +410,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository sharedScreenModel.setSongComposer(meta["h"] ?: "") sharedScreenModel.setSongRhythm(meta["r"] ?: "") } + private fun loadN(voiceNumber: Int, line: String, templateComments: MutableList) { var midiDuration = 0 var nextDuration = 0 @@ -421,11 +469,13 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository lineChar.forEach { when (it) { '|', ':', '!', '/' -> { - if (lastIt == '|' || lastIt == ':' || lastIt == '!' || lastIt == '/') {} else { + if (lastIt == '|' || lastIt == ':' || lastIt == '!' || lastIt == '/') { + } else { midiDuration += nextDuration nextDuration = 60 } } + '.' -> { if (lastIt == ';') { pushMidi() @@ -436,6 +486,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository midiDuration += nextDuration - 30 nextDuration = 30 } + ';' -> { if (lastIt == '.') { pushMidi() @@ -451,16 +502,18 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository nextDuration = 15 } } + '\'', ',' -> { newN.appendLastNote(it) lastNoteString += it } - '(', '[' -> newN.addNextMarker(it) + + '(', '[' -> newN.addNextMarker(it) ')', ']' -> newN.appendLastMarker(it) 'd', 'r', 'm', 'f', 's', 'l', 't', 'D', 'R', 'F', 'S', 'T', '-', 'Z', 'z' - -> { + -> { var noteString = it.toString().lowercase() if (it == 'D' || it == 'R' || it == 'F' || it == 'S') { noteString += "i" @@ -474,6 +527,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository midiDuration += nextDuration nextDuration = 0 } + "z", "@" -> { if (lastNoteString == "z") { midiDuration += nextDuration @@ -484,6 +538,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository } lastNoteString = "z" } + "'", "(", ")", "[", "]", "," -> {} else -> { pushMidi() @@ -491,6 +546,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository } } } + '@' -> { if (lastNoteString == "z") { midiDuration += nextDuration @@ -499,10 +555,12 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository } lastNoteString = "z" } + 'i', 'a' -> lastNoteString += it + '&' -> { - commentNumber ++ + commentNumber++ nextComment.add(templateComments[commentNumber]) } } @@ -516,6 +574,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository midiPitch.setNote("z", 0) pitches.add(midiPitch.copy()) } + private fun loadT(line: String) { templateString = line var nextTemplate = "" @@ -528,7 +587,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository if (tMarker != "") { tMarker += it if ((it == '}') - || (tMarker.length == 2 && it != '{')) { + || (tMarker.length == 2 && it != '{') + ) { TimeUnitObject.hasMarker(true) nextMarker.add(PMarkers(tMarker, offset - templateOffset)) tMarker = "" @@ -539,7 +599,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository tMarker = "$" return@forEach } - offset ++ + offset++ if (it == ':' || it == '|' || it == '/' || it == '!') { val newTemplateItem = PTemplate(nextTemplate, it.toString(), nextMarker.toMutableList()) @@ -562,9 +622,11 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository val parsedULine = uObject.parsed() loadT(parsedULine) } - fun loadO(index:Int, line:String) { + + fun loadO(index: Int, line: String) { O.getOrPut(index) { mutableListOf() }.add(line) } + fun parseMeta(line: String) { /* $_a_keyAbbrev = array( 'a' => 'author', @@ -585,7 +647,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository return } if (subKey != "" && "M" != subKey) { - meta[subKey] = line.substring(2) + meta[subKey] = line.substring(2) } if ("c" == subKey) { val tonality = line.uppercaseFirstMeta() @@ -610,7 +672,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository .replace("--_", "-_") .replace(Regex("_$"), "") } - private fun unpackLyrics(lyrics: String) :String { + + private fun unpackLyrics(lyrics: String): String { val comments = REGEX_COMMENT.findAll(lyrics) val commentsIterator = comments.iterator() val loadedLyrics = lyrics//.replace(REGEX_LYRICS_COMMENT, "") @@ -624,16 +687,19 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository } return lyricsFinal } + private fun loadL(stanzaNumber: Int, lyrics: String) { val unpackedLyrics = unpackLyrics(lyrics) loadLyrics(stanzaNumber, unpackedLyrics) } + fun setOverrideLyrics(stanzaNumber: Int, i: Int, voice: Int, text: String) { while (L.size <= i) { L.add(POneStanzaLyrics()) } - L[i].setAlternativeLyrics(stanzaNumber, voice, text) + L[i].setAlternativeLyrics(stanzaNumber, voice, text) } + private fun loadLyrics(stanzaNumber: Int, lyrics: String) { val overrideIterator: MutableMap> = mutableMapOf() try { @@ -645,14 +711,14 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository if (REGEX_OVERRIDE.matches(lyricsItem)) { val overrideNumber = lyricsItem.replace(REGEX_OVERRIDE, "$1").toInt() val override = O.getOrElse(overrideNumber) { null } - override?.forEachIndexed { index, value -> + override?.forEachIndexed { index, value -> val matchResult = Regex("v(\\d+):(.*)").find(value) val overrideString = when (smartLyricsType) { "E" -> smartELyrics(matchResult?.groupValues[2] ?: value) "Y" -> smartYLyrics(matchResult?.groupValues[2] ?: value) else -> value } - for ( iN in (1..9) ) { + for (iN in (1..9)) { if (matchResult?.groupValues[1]?.contains(iN.digitToChar()) ?: false) { val overrideSyllabus = overrideString.split(Regex("[_/]")).iterator() overrideIterator[iN] = overrideSyllabus @@ -669,7 +735,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository } addLyricsItem(stanzaNumber, i, originalLyrics) } - if (refrainBeginsAt > 0 && stanzaNumber > 1 && !lyrics.contains($$"${R!}")) { + if (refrainBeginsAt > 0 && stanzaNumber > 1 && !lyrics.contains($$"${R!}")) { copyRefrainToStanza(stanzaNumber) } @@ -690,12 +756,12 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository "Y" -> smartYLyrics(item) else -> item } - item + item .addHyphens() .split(Regex("[_/]")) .forEachIndexed { i, xval -> - appendLyricsItem(stanzaNumber, i + positionDS, xval) - } + appendLyricsItem(stanzaNumber, i + positionDS, xval) + } lyricsIterator.remove() } } @@ -708,6 +774,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository } lyricsComment.clear() } + fun smartYLyrics(lyrics: String): String { // Les ${O:1} risquent de changer en ${O:_1}. Sauvegardons-les dans comments. val comments = REGEX_COMMENT.findAll(lyrics) @@ -728,13 +795,15 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository commentsIterator.next().value } return unpackLyrics(lyricsFinal) - } + } + // loadY is a smart lyrics parser for Malagasy language private fun loadY(intKey: Int, lyrics: String) { smartLyricsType = "Y" val smartLyrics = smartYLyrics(lyrics) loadL(intKey, smartLyrics) } + fun smartELyrics(lyrics: String): String { val loadedLyrics = lyrics .replace(" ", " _") @@ -745,12 +814,14 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository //getLyricsComments(loadedLyrics) return unpackLyrics(loadedLyrics) } + // loadE is a smart Lyrics parser for English language private fun loadE(intKey: Int, lyrics: String) { smartLyricsType = "E" val smartLyrics = smartELyrics(lyrics) loadL(intKey, smartLyrics) } + private fun getLyricsComments(lyrics: String): String { val matchResult = REGEX_LYRICS_COMMENT.findAll(lyrics) .map { it.value }.toList() @@ -759,35 +830,40 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository } return REGEX_STRIP_DC.replace(lyrics, "") } + private fun defineRefrainFrom(tuoNumber: Int) { refrainBeginsAt = tuoNumber } + private fun copyRefrainToStanza(stanzaNumber: Int) { - for (i in refrainBeginsAt..L.size-1) { + for (i in refrainBeginsAt..L.size - 1) { L[i].setLyrics(stanzaNumber, L[i].getLyrics(1)) L[i].copyAlternativeLyrics(1, stanzaNumber) } } + private fun addLyricsItem(stanzaNumber: Int, i: Int, lyricsItem: String) { while (L.size <= i) { L.add(POneStanzaLyrics()) } val strippedLyrics = - if (lyricsItem.contains($$"${R=}")) { - if (stanzaNumber == 1) { - defineRefrainFrom(i) + if (lyricsItem.contains($$"${R=}")) { + if (stanzaNumber == 1) { + defineRefrainFrom(i) + } + lyricsItem.replace($$"${R=}", "") + } else { + lyricsItem } - lyricsItem.replace($$"${R=}", "") - } else { - lyricsItem - } L[i].setLyrics(stanzaNumber, strippedLyrics) } + private fun appendLyricsItem(stanzaNumber: Int, i: Int, lyricsItem: String) { if (L.size > i) { L[i].appendDSLyrics(stanzaNumber, lyricsItem) } } + fun MutableList.addWithPadding(index: Int, element: POneVoiceNote) { if (index < this.size) { // L'index existe déjà, on remplace l'élément. @@ -803,4 +879,4 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository this.add(element) } } -} +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt index ed19862..eda7205 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt @@ -8,11 +8,15 @@ import mg.dot.feufaro.solfa.TimeUnitObject class SharedScreenModel() : ScreenModel { private val _nextLabel = MutableStateFlow("Next ...") val nextLabel: StateFlow = _nextLabel.asStateFlow() + + private val _loadFile = MutableStateFlow("Load ...") + + val loadFile: StateFlow = _loadFile.asStateFlow() private val _measure = MutableStateFlow("") val measure: StateFlow = _measure.asStateFlow() private val _songTitle = MutableStateFlow("") val songTitle: StateFlow = _songTitle.asStateFlow() - private val _stanza = MutableStateFlow( 0) + private val _stanza = MutableStateFlow(0) val stanza: StateFlow = _stanza.asStateFlow() private val _songKey = MutableStateFlow("") val songKey: StateFlow = _songKey.asStateFlow() @@ -37,70 +41,88 @@ class SharedScreenModel() : ScreenModel { private var _nextPlayed = MutableStateFlow(0) val nextPlayed: StateFlow = _nextPlayed.asStateFlow() private val tempTimeUnitObjectList = mutableListOf() - var _hasMarker = MutableStateFlow( false) + var _hasMarker = MutableStateFlow(false) val hasMarker: StateFlow = _hasMarker.asStateFlow() fun appendData(otherData: String) { _nextLabel.value += otherData } + fun reset() { tempTimeUnitObjectList.clear() } + fun lastTUO(): TimeUnitObject? { return tempTimeUnitObjectList.lastOrNull() } + fun addTUO(newTUO: TimeUnitObject) { tempTimeUnitObjectList.add(newTUO) } + fun doneTUOList() { _tuoList.value = tempTimeUnitObjectList.toList() } + fun setMeasure(theMeasure: String) { _measure.value = theMeasure } + fun setSongTitle(theTitle: String) { _songTitle.value = theTitle } + fun setStanza(theStanza: Int) { try { _stanza.value = theStanza - } catch(e: NumberFormatException) { + } catch (e: NumberFormatException) { _stanza.value = 0 } } + fun setSongKey(theSongKey: String) { _songKey.value = theSongKey } + fun setTransposeTo(key: String) { _transposeTo.value = key } + fun setTransposeAsIf(key: String) { _transposeAsIf.value = key } + fun setSongAuthor(theSongAuthor: String) { _songAuthor.value = theSongAuthor } + fun setSongComposer(theSongComposer: String) { _songComposer.value = theSongComposer } + fun setSongRhythm(theSongRhythm: String) { _songRhythm.value = theSongRhythm } + fun setNbStanzas(nbStanzas: Int) { _nbStanzas.value = nbStanzas } + fun setPlaylist(thePlaylist: List) { _playlist.value = thePlaylist } + fun setHasMarker(theHasMarker: Boolean) { _hasMarker.value = theHasMarker } + fun playNext() { val nextIndex = (_nextPlayed.value + 1) % _playlist.value.size _nextPlayed.value = nextIndex setStanza(1) } + fun currentPlayed(): String { val playlistIndex = _nextPlayed.value return _playlist.value[playlistIndex] diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt index 1ff642c..c08af74 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt @@ -3,14 +3,20 @@ package mg.dot.feufaro.viewmodel import cafe.adriel.voyager.core.model.ScreenModel import mg.dot.feufaro.solfa.Solfa -class SolfaScreenModel ( +class SolfaScreenModel( private val solfa: Solfa -) : ScreenModel{ +) : ScreenModel { init {} + fun loadNextInPlaylist() { solfa.loadNextInPlaylist() } + fun reload() { solfa.loadSolfa() } + + fun loadCustomFile() { + solfa.loadFile() + } } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/LauchFilePicker.kt b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/LauchFilePicker.kt new file mode 100644 index 0000000..9102ed7 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/LauchFilePicker.kt @@ -0,0 +1,32 @@ +package mg.dot.feufaro + +import javax.swing.JFileChooser +import javax.swing.filechooser.FileNameExtensionFilter +import java.awt.EventQueue + +actual fun launchFilePicker( + mimeTypes: Array, + onFileSelected: (path: String?) -> Unit +) { + EventQueue.invokeLater { + val fileChooser = JFileChooser() + + if (mimeTypes.isNotEmpty()) { + val filter = FileNameExtensionFilter( + "Fichiers texte", + "txt" + ) + fileChooser.fileFilter = filter + } + + fileChooser.dialogTitle = "Sélectionnez votre fichier personnalisé" + + val result = fileChooser.showOpenDialog(null) + + if (result == JFileChooser.APPROVE_OPTION) { + onFileSelected(fileChooser.selectedFile.absolutePath) + } else { + onFileSelected(null) + } + } +} \ No newline at end of file