diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 9b4943f..5be6a16 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -42,7 +42,9 @@ kotlin { implementation(libs.koin.core) // Koin core for shared logic implementation(libs.koin.compose) // Koin for Compose Multiplatform UI implementation(libs.koin.core.viewmodel) // Koin for KMP ViewModels - + implementation(libs.core) // Dépendance sous-jacente pour XML + implementation(libs.serialization) + api(libs.kmp.observableviewmodel.core) } commonTest.dependencies { implementation(libs.kotlin.test) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt index 1824a15..2e0dd88 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt @@ -17,10 +17,21 @@ import org.jetbrains.compose.ui.tooling.preview.Preview import feufaro.composeapp.generated.resources.Res import feufaro.composeapp.generated.resources.compose_multiplatform +import org.koin.compose.koinInject @Composable @Preview fun App() { + val fileRepository = koinInject() + var fileContent by remember { mutableStateOf("Chargement asset ....") } + LaunchedEffect(Unit) { + try { + fileContent = "xxx"+ fileRepository.readFileContent("assets://ews-1.txt") + }catch (e: Exception) { + fileContent = "Erreur : ${e.message}" + e.printStackTrace() + } + } MaterialTheme { var showContent by remember { mutableStateOf(false) } Column( @@ -29,6 +40,7 @@ fun App() { .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, ) { + Text(text = fileContent) Button(onClick = { showContent = !showContent }) { Text("Click me!") } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/CommonTools.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/CommonTools.kt new file mode 100644 index 0000000..d4326f7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/CommonTools.kt @@ -0,0 +1,33 @@ +package mg.dot.feufaro + +object CommonTools { + //companion object { + fun tonalityToNumber(tonality: String) : Int { + val result: Map = mapOf( + "C" to 1, + "C#" to 2, "Db" to 2, + "D" to 3, + "D#" to 4, "Eb" to 4, + "E" to 5, + "F" to 6, + "F#" to 7, "Gb" to 7, + "G" to 8, + "G#" to 9, "Ab" to 9, + "A" to 10, + "A#" to 11, "Bb" to 11, + "B" to 12 + ) + if (result.containsKey(tonality)) { + return result[tonality]!! + } + return -1 + } + fun numberToTonality(number: Int): String { + val result: Array = arrayOf("", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B") + return if (number in 1..12) { + result[number] + } else { + "" + } + } + } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/SharedViewModel.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/SharedViewModel.kt new file mode 100644 index 0000000..575b86e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/SharedViewModel.kt @@ -0,0 +1,86 @@ +package mg.dot.feufaro + +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import com.rickclephas.kmp.observableviewmodel.ViewModel +import com.rickclephas.kmp.observableviewmodel.MutableStateFlow +import mg.dot.feufaro.solfa.TimeUnitObject + +open class SharedViewModel(): ViewModel() { + private val _data = MutableStateFlow(viewModelScope, "Next ...") + val data: StateFlow = _data.asStateFlow() + private val _measure = MutableStateFlow(viewModelScope, "") + val measure: StateFlow = _measure.asStateFlow() + private val _songTitle = MutableStateFlow(viewModelScope, "") + val songTitle: StateFlow = _songTitle.asStateFlow() + private val _stanza = MutableStateFlow(viewModelScope, 0) + val stanza: StateFlow = _stanza.asStateFlow() + private val _songKey = MutableStateFlow(viewModelScope, "") + val songKey: StateFlow = _songKey.asStateFlow() + private val _songAuthor = MutableStateFlow(viewModelScope,"") + val songAuthor: StateFlow = _songAuthor.asStateFlow() + private val _songComposer = MutableStateFlow(viewModelScope,"") + val songComposer: StateFlow = _songComposer.asStateFlow() + private val _songRhythm = MutableStateFlow(viewModelScope,"") + val songRhythm: StateFlow = _songRhythm.asStateFlow() + private val _nbStanzas = MutableStateFlow(viewModelScope,0) + val nbStanzas: StateFlow = _nbStanzas.asStateFlow() + private var _tuoList = MutableStateFlow>(viewModelScope, emptyList()) + val tuoList: StateFlow> = _tuoList.asStateFlow() + private var _playlist = MutableStateFlow>(viewModelScope, emptyList()) + val playlist: StateFlow> = _playlist.asStateFlow() + private val tempTimeUnitObjectList = mutableListOf() + var _hasMarker = MutableStateFlow(viewModelScope, false) + val hasMarker: StateFlow = _hasMarker.asStateFlow() + + fun appendData(otherData: String) { + _data.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: String) { + try { + _stanza.value = theStanza.toInt() + } catch(e: NumberFormatException) { + _stanza.value = 0 + } + } + fun setSongKey(theSongKey: String) { + _songKey.value = theSongKey + } + 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 + + } +} diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/di/AppModule.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/di/AppModule.kt index 5df77e5..96df2fd 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/di/AppModule.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/di/AppModule.kt @@ -1,10 +1,13 @@ package mg.dot.feufaro.di +import mg.dot.feufaroo.musicXML.MusicXML import org.koin.dsl.module +import org.koin.core.module.dsl.singleOf val commonModule = module { // Déclarez FileRepository comme un singleton. // L'implémentation concrète (actual) sera résolue par Koin en fonction de la plateforme. // Pour Android, Koin injectera le Context que vous avez fourni via androidContext(). + singleOf(::MusicXML) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXAttributes.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXAttributes.kt new file mode 100644 index 0000000..cea44fa --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXAttributes.kt @@ -0,0 +1,30 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("attributes ", "", "") +data class MXAttributes ( +@Serializable +@XmlElement +@XmlSerialName("staff-details", "", "") + var staffDetails: MXStaffDetails? = null, +@Serializable +@XmlElement +@XmlSerialName("clef", "", "") + var clef: MXClef? = null, +@Serializable +@XmlElement +@XmlSerialName("time", "", "") + var time: MXTime? = null, +@Serializable +@XmlElement +@XmlSerialName("divisions", "", "") + var divisions: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("key", "", "") + var key: MXKey? = null +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXBackup.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXBackup.kt new file mode 100644 index 0000000..d4d0f11 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXBackup.kt @@ -0,0 +1,14 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("backup ", "", "") +data class MXBackup ( +@Serializable +@XmlElement +@XmlSerialName("duration", "", "") + var duration: Int? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXBeam.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXBeam.kt new file mode 100644 index 0000000..a6f00a5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXBeam.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XML +import nl.adaptivity.xmlutil.serialization.XmlSerialName +import nl.adaptivity.xmlutil.serialization.XmlValue + +@Serializable +@XmlSerialName("beam ", "", "") +data class MXBeam ( +@Serializable +@XmlSerialName("number", "", "") + var number: Int? = null, +@Serializable +@XmlValue +@XmlSerialName("content", "", "") + var content: String? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXClef.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXClef.kt new file mode 100644 index 0000000..61e526c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXClef.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("clef ", "", "") +data class MXClef ( +@Serializable +@XmlElement +@XmlSerialName("line", "", "") + var line: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("sign", "", "") + var sign: String? = null +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXCreator.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXCreator.kt new file mode 100644 index 0000000..fa960b6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXCreator.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName +import nl.adaptivity.xmlutil.serialization.XmlValue + +@Serializable +@XmlSerialName("creator", "", "") +data class MXCreator( +@Serializable +@XmlSerialName("type", "", "") + var type: String? = null, +@Serializable +@XmlValue +@XmlSerialName("content", "", "") + var content: String? = null +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXCredit.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXCredit.kt new file mode 100644 index 0000000..77e2330 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXCredit.kt @@ -0,0 +1,21 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("credit ", "", "") +data class MXCredit ( +@Serializable +@XmlElement +@XmlSerialName("credit-type", "", "") + var creditType: String? = null, +@Serializable +@XmlSerialName("page", "", "") + var page: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("credit-words", "", "") + var creditWords: MXCreditWords? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXCreditWords.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXCreditWords.kt new file mode 100644 index 0000000..b7c9b63 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXCreditWords.kt @@ -0,0 +1,30 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName +import nl.adaptivity.xmlutil.serialization.XmlValue + +@Serializable +@XmlSerialName("credit-words ", "", "") +data class MXCreditWords ( +@Serializable +@XmlSerialName("font-weight", "", "") + var fontWeight: String? = null, +@Serializable +@XmlSerialName("font-size", "", "") + var fontSize: Int? = null, +@Serializable +@XmlSerialName("font-family", "", "") + var fontFamily: String? = null, +@Serializable +@XmlSerialName("default-y", "", "") + var defaultY: Int? = null, +@Serializable +@XmlSerialName("default-x", "", "") + var defaultX: Int? = null, +@Serializable +@XmlValue +@XmlSerialName("content", "", "") + var content: String? = null +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDefaults.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDefaults.kt new file mode 100644 index 0000000..c908c21 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDefaults.kt @@ -0,0 +1,22 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("defaults ", "", "") +data class MXDefaults ( +@Serializable +@XmlElement +@XmlSerialName("scaling", "", "") + var scaling: MXScaling? = null, +@Serializable +@XmlElement +@XmlSerialName("page-layout", "", "") + var pageLayout: MXPageLayout? = null, +@Serializable +@XmlElement +@XmlSerialName("lyric-font", "", "") + var lyricFont: MXLyricFont? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirection.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirection.kt new file mode 100644 index 0000000..801e55c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirection.kt @@ -0,0 +1,21 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("direction ", "", "") +data class MXDirection ( +@Serializable +@XmlElement +@XmlSerialName("sound", "", "") + var sound: MXSound? = null, +@Serializable +@XmlSerialName("placement", "", "") + var placement: String? = null, +@Serializable +@XmlElement +@XmlSerialName("direction-type", "", "") + var directionType: MXDirectionType? = null +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirectionType.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirectionType.kt new file mode 100644 index 0000000..8a6070a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirectionType.kt @@ -0,0 +1,14 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("direction-type ", "", "") +data class MXDirectionType ( +@Serializable +@XmlElement +@XmlSerialName("words", "", "") + var words: String? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXEncoding.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXEncoding.kt new file mode 100644 index 0000000..5f20a15 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXEncoding.kt @@ -0,0 +1,22 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("encoding ", "", "") +data class MXEncoding ( +@Serializable +@XmlElement +@XmlSerialName("software", "", "") + var software: List = listOf(), +@Serializable +@XmlElement +@XmlSerialName("encoding-date", "", "") + var encodingDate: String? = null, +@Serializable +@XmlElement +@XmlSerialName("supports", "", "") + var supports: List = listOf() + ) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXForward.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXForward.kt new file mode 100644 index 0000000..ce6a80c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXForward.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("forward ", "", "") +data class MXForward ( +@Serializable +@XmlElement +@XmlSerialName("duration", "", "") + var duration: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("voice", "", "") + var voice: Int? = null +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXHarmony.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXHarmony.kt new file mode 100644 index 0000000..8dab67e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXHarmony.kt @@ -0,0 +1,30 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("harmony ", "", "") +data class MXHarmony ( +@Serializable +@XmlElement +@XmlSerialName("kind", "", "") + var kind: MXKind? = null, +@Serializable +@XmlSerialName("relative-x", "", "") + var relativeX: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("root", "", "") + var root: MXHarmonyRoot? = null, +@Serializable +@XmlSerialName("font-size", "", "") + var fontSize: Double? = null, +@Serializable +@XmlSerialName("default-y", "", "") + var defaultY: Int? = null, +@Serializable +@XmlSerialName("placement", "", "") + var placement: String? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXHarmonyRoot.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXHarmonyRoot.kt new file mode 100644 index 0000000..29dd85b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXHarmonyRoot.kt @@ -0,0 +1,14 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("harmonyRoot ", "", "") +data class MXHarmonyRoot ( +@Serializable +@XmlElement +@XmlSerialName("root-step", "", "") + var rootStep: String? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXIdentification.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXIdentification.kt new file mode 100644 index 0000000..50377c7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXIdentification.kt @@ -0,0 +1,26 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("identification ", "", "") +data class MXIdentification ( +@Serializable +@XmlElement +@XmlSerialName("creator", "", "") + var creator: List = listOf(), +@Serializable +@XmlElement +@XmlSerialName("miscellaneous", "", "") + var miscellaneous: MXMiscellaneous? = null, +@Serializable +@XmlElement +@XmlSerialName("source", "", "") + var source: String? = null, +@Serializable +@XmlElement +@XmlSerialName("encoding", "", "") + var encoding: MXEncoding? = null +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXKey.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXKey.kt new file mode 100644 index 0000000..9919038 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXKey.kt @@ -0,0 +1,14 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("key ", "", "") +data class MXKey ( +@Serializable +@XmlElement +@XmlSerialName("fifths", "", "") + var fifths: Int? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXKind.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXKind.kt new file mode 100644 index 0000000..84d60f7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXKind.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName +import nl.adaptivity.xmlutil.serialization.XmlValue + +@Serializable +@XmlSerialName("kind ", "", "") +data class MXKind ( +@Serializable +@XmlSerialName("text", "", "") + var text: String? = null, +@Serializable +@XmlValue +@XmlSerialName("content", "", "") + var content: String? = null +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXLyric.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXLyric.kt new file mode 100644 index 0000000..2742205 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXLyric.kt @@ -0,0 +1,27 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("lyric ", "", "") +data class MXLyric ( +@Serializable +@XmlSerialName("number", "", "") + var number: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("syllabic", "", "") + var syllabic: String? = null, +@Serializable +@XmlSerialName("default-y", "", "") + var defaultY: Int? = null, +@Serializable +@XmlSerialName("placement", "", "") + var placement: String? = null, +@Serializable +@XmlElement +@XmlSerialName("text", "", "") + var text: String? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXLyricFont.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXLyricFont.kt new file mode 100644 index 0000000..381abe7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXLyricFont.kt @@ -0,0 +1,16 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("lyric-font ", "", "") +data class MXLyricFont ( +@Serializable +@XmlSerialName("font-size", "", "") + var fontSize: Int? = null, +@Serializable +@XmlSerialName("font-family", "", "") + var fontFamily: String? = null +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMeasure.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMeasure.kt new file mode 100644 index 0000000..bed6527 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMeasure.kt @@ -0,0 +1,44 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("measure ", "", "") +data class MXMeasure ( +@Serializable +@XmlSerialName("number", "", "") + var number: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("note", "", "") + var note: List = listOf(), +@Serializable +@XmlElement +@XmlSerialName("print", "", "") + var print: MXPrint? = null, +@Serializable +@XmlSerialName("width", "", "") + var width: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("attributes", "", "") + var attributes: MXAttributes? = null, +@Serializable +@XmlElement +@XmlSerialName("direction", "", "") + var direction: MXDirection? = null, +@Serializable +@XmlElement +@XmlSerialName("backup", "", "") + var backup: MXBackup? = null, +@Serializable +@XmlElement +@XmlSerialName("harmony", "", "") + var harmony: MXHarmony? = null, +@Serializable +@XmlElement +@XmlSerialName("forward", "", "") + var forward: MXForward? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMidiInstrument.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMidiInstrument.kt new file mode 100644 index 0000000..49f56d3 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMidiInstrument.kt @@ -0,0 +1,25 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("midi-instrument ", "", "") +data class MXMidiInstrument ( +@Serializable +@XmlElement +@XmlSerialName("volume", "", "") + var volume: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("midi-channel", "", "") + var midiChannel: Int? = null, +@Serializable +@XmlSerialName("id", "", "") + var id: String? = null, +@Serializable +@XmlElement +@XmlSerialName("midi-program", "", "") + var midiProgram: Int? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMiscellaneous.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMiscellaneous.kt new file mode 100644 index 0000000..6137224 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMiscellaneous.kt @@ -0,0 +1,14 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("miscellaneous ", "", "") +data class MXMiscellaneous ( +@Serializable +@XmlElement +@XmlSerialName("miscellaneous-field", "", "") + var miscellaneousField: List = listOf() +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMiscellaneousField.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMiscellaneousField.kt new file mode 100644 index 0000000..8015cae --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXMiscellaneousField.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName +import nl.adaptivity.xmlutil.serialization.XmlValue + +@Serializable +@XmlSerialName("miscellaneous-field ", "", "") +data class MXMiscellaneousField ( +@Serializable +@XmlSerialName("name", "", "") + var name: String? = null, +@Serializable +@XmlValue +@XmlSerialName("content", "", "") + var content: String? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXNotations.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXNotations.kt new file mode 100644 index 0000000..5f40cb6 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXNotations.kt @@ -0,0 +1,14 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("notations ", "", "") +data class MXNotations ( +@Serializable +@XmlElement +@XmlSerialName("slur", "", "") + var slur: MXSlur? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXNote.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXNote.kt new file mode 100644 index 0000000..442fce0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXNote.kt @@ -0,0 +1,57 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("note ", "", "") +data class MXNote ( +@Serializable +@XmlElement +@XmlSerialName("duration", "", "") + var duration: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("voice", "", "") + var voice: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("lyric", "", "") + var lyric: List = listOf(), +@Serializable +@XmlSerialName("default-x", "", "") + var defaultX: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("pitch", "", "") + var pitch: MXPitch? = null, +@Serializable +@XmlElement +@XmlSerialName("type", "", "") + var type: String? = null, +@Serializable +@XmlElement +@XmlSerialName("stem", "", "") + var stem: MXStem? = null, +@Serializable +@XmlElement +@XmlSerialName("chord", "", "") + var chord: String? = null, +@Serializable +@XmlElement +@XmlSerialName("dot", "", "") + var dot: String? = null, +@Serializable +@XmlElement +@XmlSerialName("beam", "", "") + var beam: MXBeam? = null, +@Serializable +@XmlElement +@XmlSerialName("notations", "", "") + var notations: MXNotations? = null, +@Serializable +@XmlElement +@XmlSerialName("accidental", "", "") + var accidental: String? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPageLayout.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPageLayout.kt new file mode 100644 index 0000000..86fe15b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPageLayout.kt @@ -0,0 +1,22 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("page-layout ", "", "") +data class MXPageLayout ( +@Serializable +@XmlElement +@XmlSerialName("page-margins", "", "") + var pageMargins: MXPageMargins? = null, +@Serializable +@XmlElement +@XmlSerialName("page-height", "", "") + var pageHeight: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("page-width", "", "") + var pageWidth: Int? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPageMargins.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPageMargins.kt new file mode 100644 index 0000000..602742b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPageMargins.kt @@ -0,0 +1,29 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("page-margins ", "", "") +data class MXPageMargins ( +@Serializable +@XmlElement +@XmlSerialName("right-margin", "", "") + var rightMargin: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("left-margin", "", "") + var leftMargin: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("bottom-margin", "", "") + var bottomMargin: Int? = null, +@Serializable +@XmlSerialName("type", "", "") + var type: String? = null, +@Serializable +@XmlElement +@XmlSerialName("top-margin", "", "") + var topMargin: Int? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPart.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPart.kt new file mode 100644 index 0000000..61604b7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPart.kt @@ -0,0 +1,17 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("part ", "", "") +data class MXPart ( +@Serializable +@XmlElement +@XmlSerialName("measure", "", "") + var measure: List = listOf(), +@Serializable +@XmlSerialName("id", "", "") + var id: String? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPartList.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPartList.kt new file mode 100644 index 0000000..f8a3413 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPartList.kt @@ -0,0 +1,14 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("part-list ", "", "") +data class MXPartList ( +@Serializable +@XmlElement +@XmlSerialName("score-part", "", "") + var scorePart: List = listOf() +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPitch.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPitch.kt new file mode 100644 index 0000000..433488c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPitch.kt @@ -0,0 +1,22 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("pitch ", "", "") +data class MXPitch ( +@Serializable +@XmlElement +@XmlSerialName("octave", "", "") + var octave: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("step", "", "") + var step: String? = null, +@Serializable +@XmlElement +@XmlSerialName("alter", "", "") + var alter: Int? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPrint.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPrint.kt new file mode 100644 index 0000000..0f9a1a8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPrint.kt @@ -0,0 +1,25 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("print ", "", "") +data class MXPrint ( +@Serializable +@XmlElement +@XmlSerialName("system-layout", "", "") + var systemLayout: MXSystemLayout? = null, +@Serializable +@XmlElement +@XmlSerialName("measure-numbering", "", "") + var measureNumbering: String? = null, +@Serializable +@XmlElement +@XmlSerialName("staff-layout", "", "") + var staffLayout: MXStaffLayout? = null, +@Serializable +@XmlSerialName("new-system", "", "") + var newSystem: String? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXRoot.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXRoot.kt new file mode 100644 index 0000000..72b6807 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXRoot.kt @@ -0,0 +1,14 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("root ", "", "") +data class MXRoot ( +@Serializable +@XmlElement +@XmlSerialName("score-partwise", "", "") + var scorePartwise: MXScorePartwise? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScaling.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScaling.kt new file mode 100644 index 0000000..a9a8b25 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScaling.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("scaling ", "", "") +data class MXScaling ( +@Serializable +@XmlElement +@XmlSerialName("millimeters", "", "") + var millimeters: Double? = null, +@Serializable +@XmlElement +@XmlSerialName("tenths", "", "") + var tenths: Int? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScoreInstrument.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScoreInstrument.kt new file mode 100644 index 0000000..ab57270 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScoreInstrument.kt @@ -0,0 +1,17 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("score-instrument", "", "") +data class MXScoreInstrument ( +@Serializable +@XmlElement +@XmlSerialName("instrument-name", "", "") + var instrumentName: String? = null, +@Serializable +@XmlSerialName("id", "", "") + var id: String? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScorePart.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScorePart.kt new file mode 100644 index 0000000..2df370b --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScorePart.kt @@ -0,0 +1,29 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("score-part ", "", "") +data class MXScorePart ( +@Serializable +@XmlElement +@XmlSerialName("midi-instrument", "", "") + var midiInstrument: MXMidiInstrument? = null, +@Serializable +@XmlElement +@XmlSerialName("score-instrument", "", "") + var scoreInstrument: MXScoreInstrument? = null, +@Serializable +@XmlSerialName("id", "", "") + var id: String? = null, +@Serializable +@XmlElement +@XmlSerialName("part-name", "", "") + var partName: String? = null, +@Serializable +@XmlElement +@XmlSerialName("part-abbreviation", "", "") + var partAbbreviation: String? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScorePartwise.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScorePartwise.kt new file mode 100644 index 0000000..a6d6178 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXScorePartwise.kt @@ -0,0 +1,37 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("score-partwise", "", "") +data class MXScorePartwise ( +@Serializable +@XmlElement +@XmlSerialName("identification", "", "") + var identification: MXIdentification? = null, +@Serializable +@XmlElement +@XmlSerialName("defaults", "", "") + var defaults: MXDefaults? = null, +@Serializable +@XmlElement +@XmlSerialName("work", "", "") + var work: MXWork? = null, +@Serializable +@XmlElement +@XmlSerialName("part", "", "") + var part: List = listOf(), +@Serializable +@XmlElement +@XmlSerialName("credit", "", "") + var credit: List = listOf(), +@Serializable +@XmlSerialName("version", "", "") + var version: String? = null, +@Serializable +@XmlElement +@XmlSerialName("part-list", "", "") + var partList: MXPartList? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSlur.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSlur.kt new file mode 100644 index 0000000..d983cf5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSlur.kt @@ -0,0 +1,31 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("slur ", "", "") +data class MXSlur ( +@Serializable +@XmlSerialName("number", "", "") + var number: Int? = null, +@Serializable +@XmlSerialName("bezier-y", "", "") + var bezierY: Int? = null, +@Serializable +@XmlSerialName("bezier-x", "", "") + var bezierX: Int? = null, +@Serializable +@XmlSerialName("default-y", "", "") + var defaultY: Int? = null, +@Serializable +@XmlSerialName("default-x", "", "") + var defaultX: Int? = null, +@Serializable +@XmlSerialName("placement", "", "") + var placement: String? = null, +@Serializable +@XmlSerialName("type", "", "") + var type: String? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSound.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSound.kt new file mode 100644 index 0000000..6da2e73 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSound.kt @@ -0,0 +1,13 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("sound ", "", "") +data class MXSound ( +@Serializable +@XmlSerialName("tempo", "", "") + var tempo: Int? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXStaffDetails.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXStaffDetails.kt new file mode 100644 index 0000000..4c7b6b2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXStaffDetails.kt @@ -0,0 +1,13 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("staff-details ", "", "") +data class MXStaffDetails ( +@Serializable +@XmlSerialName("print-object", "", "") + var printObject: String? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXStaffLayout.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXStaffLayout.kt new file mode 100644 index 0000000..4e15f20 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXStaffLayout.kt @@ -0,0 +1,17 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("staff-layout ", "", "") +data class MXStaffLayout ( +@Serializable +@XmlSerialName("number", "", "") + var number: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("staff-distance", "", "") + var staffDistance: Int? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXStem.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXStem.kt new file mode 100644 index 0000000..5188d6c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXStem.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName +import nl.adaptivity.xmlutil.serialization.XmlValue + +@Serializable +@XmlSerialName("stem ", "", "") +data class MXStem ( +@Serializable +@XmlSerialName("default-y", "", "") + var defaultY: Int? = null, +@Serializable +@XmlValue +@XmlSerialName("content", "", "") + var content: String? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSupports.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSupports.kt new file mode 100644 index 0000000..9c11089 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSupports.kt @@ -0,0 +1,21 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("supports ", "", "") +data class MXSupports ( +@Serializable +@XmlSerialName("attribute", "", "") + var attribute: String? = null, +@Serializable +@XmlSerialName("type", "", "") + var type: String? = null, +@Serializable +@XmlSerialName("value", "", "") + var value: String? = null, +@Serializable +@XmlSerialName("element", "", "") + var element: String? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSystemLayout.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSystemLayout.kt new file mode 100644 index 0000000..94dc447 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSystemLayout.kt @@ -0,0 +1,22 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("system-layout ", "", "") +data class MXSystemLayout ( +@Serializable +@XmlElement +@XmlSerialName("system-margins", "", "") + var systemMargins: MXSystemMargins? = null, +@Serializable +@XmlElement +@XmlSerialName("top-system-distance", "", "") + var topSystemDistance: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("system-distance", "", "") + var systemDistance: Int? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSystemMargins.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSystemMargins.kt new file mode 100644 index 0000000..be1a11e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXSystemMargins.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("system-margins ", "", "") +data class MXSystemMargins ( +@Serializable +@XmlElement +@XmlSerialName("right-margin", "", "") + var rightMargin: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("left-margin", "", "") + var leftMargin: Int? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTime.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTime.kt new file mode 100644 index 0000000..78f83cd --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTime.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("time ", "", "") +data class MXTime ( +@Serializable +@XmlElement +@XmlSerialName("beats", "", "") + var beats: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("beat-type", "", "") + var beatType: Int? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXWork.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXWork.kt new file mode 100644 index 0000000..cb683be --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXWork.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("work ", "", "") +data class MXWork ( +@Serializable +@XmlElement +@XmlSerialName("work-title", "", "") + var workTitle: String? = null, +@Serializable +@XmlElement +@XmlSerialName("work-number", "", "") + var workNumber: String? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MusicXML.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MusicXML.kt new file mode 100644 index 0000000..2a365ee --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MusicXML.kt @@ -0,0 +1,59 @@ +package mg.dot.feufaroo.musicXML + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.modules.SerializersModule +import mg.dot.feufaro.FileRepository +import nl.adaptivity.xmlutil.serialization.XML +import org.koin.compose.koinInject + +class MusicXML(private val fileRepository: FileRepository) { + val module = SerializersModule { + } + + val xml = XML (module) { + defaultPolicy { + ignoreUnknownChildren() + } + } + var xmlString = "" + suspend fun load() { + + xmlString = fileRepository.readFileContent("assets://13.xml") + try { + val xmlObject = xml.decodeFromString(xmlString) + var keyFifths = 0 + var timeIndex = 0 + xmlObject.part.forEach { partNode -> + partNode.measure.forEach { measureNode -> + if (measureNode.backup?.duration != null) { + val backupDuration = measureNode.backup!!.duration!! + timeIndex -= backupDuration + } + if (measureNode.attributes?.key?.fifths != null) { + keyFifths = measureNode.attributes!!.key!!.fifths!! + } + val keySign = measureNode.attributes?.clef?.sign + val nominator = measureNode.attributes?.time?.beats + val denominator = measureNode.attributes?.time?.beatType + measureNode.note.forEach { noteNode -> + val voiceNumber = noteNode.voice + val duration = noteNode.duration + val chord = noteNode.chord + } + } + } + } catch(e: Exception) { + println("${e.message}") + } + } + + init { + val parseScope = CoroutineScope(Dispatchers.Default) + parseScope.launch { + load() + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/AnnotatedTUO.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/AnnotatedTUO.kt new file mode 100644 index 0000000..f9ffc87 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/AnnotatedTUO.kt @@ -0,0 +1,89 @@ +package mg.dot.feufaro.solfa + +enum class LineType { + ROUND, + SQUARE +} +class UnderlineSpec ( + var x: Int, + val lineType: LineType +) { + var y: Int = -1 + var okX = false +} +class AnnotatedTUO (originalString: String, val voiceNumber: Int){ + var finalText: String = "" + companion object { + var inRoundParen = mutableListOf() + var inSquareParen = mutableListOf() + } + var underlineSpec = mutableListOf() + private fun toggleInRoundParen(voiceNumber: Int, value: Boolean, x: Int) { + if (value && ( + (inRoundParen.size > voiceNumber && inRoundParen[voiceNumber]) + || (inSquareParen.size > voiceNumber && inSquareParen[voiceNumber]) )) { + return + } + if (!value && (inRoundParen.size <= voiceNumber || !inRoundParen[voiceNumber])) { + return + } + while (inRoundParen.size <= voiceNumber) { + inRoundParen.add(false) + } + inRoundParen[voiceNumber] = value + if (value) { + underlineSpec.add(UnderlineSpec(x, LineType.ROUND)) + } else { + underlineSpec.lastOrNull()?.y = x + } + } + private fun toggleInSquareParen(voiceNumber: Int, value: Boolean, x: Int) { + if (value && ( + (inRoundParen.size > voiceNumber && inRoundParen[voiceNumber]) + || (inSquareParen.size > voiceNumber && inSquareParen[voiceNumber]) )) { + return + } + if (!value && (inSquareParen.size <= voiceNumber || !inSquareParen[voiceNumber])) { + return + } + while (inSquareParen.size <= voiceNumber) { + inSquareParen.add(false) + } + inSquareParen[voiceNumber] = value + if (value) { + underlineSpec.add(UnderlineSpec(x, LineType.SQUARE)) + } else { + underlineSpec.lastOrNull()?.y = x + } + } + init { + var x = -1 + if (voiceNumber > 0) { + if (inRoundParen.size > voiceNumber && inRoundParen[voiceNumber]) { + underlineSpec.add(UnderlineSpec(0, LineType.ROUND)) + } + if (inSquareParen.size > voiceNumber && inSquareParen[voiceNumber]) { + underlineSpec.add(UnderlineSpec(0, LineType.SQUARE)) + } + originalString.toCharArray().forEach { + when (it) { + '(' -> toggleInRoundParen(voiceNumber, true, x) + '[' -> toggleInSquareParen(voiceNumber, true, x) + ')' -> toggleInRoundParen(voiceNumber, false, x) + ']' -> toggleInSquareParen(voiceNumber, false, x) + else -> { + x++ + finalText += it + if (it in listOf('d', 'r', 'm', 'f', 's', 'l', 't')) { + val lastUnderLineSpec = underlineSpec.lastOrNull() + if (lastUnderLineSpec?.okX == false) { + lastUnderLineSpec.okX = true + lastUnderLineSpec.x = x + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Lyrics.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Lyrics.kt new file mode 100644 index 0000000..52b8869 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Lyrics.kt @@ -0,0 +1,5 @@ +package mg.dot.feufaro.solfa + +class Lyrics (val text: String, val stanza: Int, val refrain: Boolean = false, val stanzaDSIn: Int = -1){ + val stanzaDS = if (stanzaDSIn == -1) { stanza } else { stanzaDSIn } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Markers.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Markers.kt new file mode 100644 index 0000000..78d2cf5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Markers.kt @@ -0,0 +1,8 @@ +package mg.dot.feufaro.solfa + +class Markers { + var marker: String = "" + fun unMark(mark: Char) { + marker.replace(mark.toString(), "", ignoreCase = false) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Note.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Note.kt new file mode 100644 index 0000000..7502065 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Note.kt @@ -0,0 +1,39 @@ +package mg.dot.feufaro.solfa + +class Note (val note: String, private var separator: String= "") { + + private var duration: Int = 0 + var marker: MutableList = mutableListOf() + var markerBefore: String = "" + fun addMarker(newMarker: String) { + if (marker.contains(newMarker)) return + marker.add(newMarker) + } + fun setMarkers(newMarkers: MutableList) { + marker = newMarkers + } + fun markBefore(newMarker: String) { + markerBefore += newMarker + } + /*fun unMark(badMarker: String) { + if (marker.contains(badMarker)) { + marker.remove(badMarker) + } + } + fun setSeparator(newSeparator : String) { + separator = newSeparator + }*/ + private fun String.asMarker(): String { + if (this == "(") return "" + if (this == ",") return " ," + return this + } + override fun toString(): String { + var result = "" + if (marker.contains("(")) { + result += "(" + } + result += markerBefore.toCharArray().joinToString("") { it.toString().asMarker() } + note + marker.joinToString(""){ it.asMarker() } + return result + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/PNotes.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/PNotes.kt new file mode 100644 index 0000000..6aaaf30 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/PNotes.kt @@ -0,0 +1,19 @@ +package mg.dot.feufaro.solfa + +class PNotes (var note: String, private var markers: MutableList = mutableListOf("")) { + fun addMarker(newMarker: String) { + markers.add(newMarker) + } + fun hasMarker(marker: String): Boolean { + return markers.contains(marker) + } + fun toNote(): Note { + val newNote = Note(note) + newNote.setMarkers(markers) + return newNote + } + // appendNote is used in POneVoiceNote.appendLastNote() + fun appendNote(noteSuffix: Char) { + note += noteSuffix + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/POneStanzaLyrics.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/POneStanzaLyrics.kt new file mode 100644 index 0000000..59cf910 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/POneStanzaLyrics.kt @@ -0,0 +1,25 @@ +package mg.dot.feufaro.solfa + +class POneStanzaLyrics { + private val lyrics: MutableList = mutableListOf("") + fun setLyrics(stanzaNumber: Int, lyrics: String) { + while (this.lyrics.size <= stanzaNumber) { + this.lyrics.add("") + } + this.lyrics[stanzaNumber] = lyrics + } + override fun toString(): String { + return lyrics[1]?: "" + } + fun appendDSLyrics(stanzaNumber: Int, lyrics: String) { + if (this.lyrics.size > stanzaNumber) { + this.lyrics[stanzaNumber] += "\n$lyrics" + } + } + fun toList(stanzaNumber: Int): List { + return if (this.lyrics.size > stanzaNumber) { + this.lyrics[stanzaNumber].split("\n") + } else + emptyList() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/POneVoiceNote.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/POneVoiceNote.kt new file mode 100644 index 0000000..3269a0d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/POneVoiceNote.kt @@ -0,0 +1,24 @@ +package mg.dot.feufaro.solfa + +class POneVoiceNote { + val oneVoiceNote: MutableList = mutableListOf() + companion object { + var nextMarkers: MutableList = mutableListOf() + } + private var nextNote: String = "" + fun addNextMarker(marker: Char) { + nextMarkers.add(marker.toString()) + } + fun appendLastNote(note: Char) { + oneVoiceNote.lastOrNull()?.appendNote(note) + } + fun appendLastMarker(marker: Char) { + oneVoiceNote.lastOrNull()?.addMarker(marker.toString()) + } + fun addItem(noteString: String = "") { + val newNote: PNotes = PNotes(noteString, nextMarkers) + oneVoiceNote.add(newNote) + nextNote = "" + nextMarkers = mutableListOf() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/PTemplate.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/PTemplate.kt new file mode 100644 index 0000000..0edfd7e --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/PTemplate.kt @@ -0,0 +1,20 @@ +package mg.dot.feufaro.solfa + +class PTemplate (val template: String, val separatorAfter: String, private val markers: MutableList = mutableListOf("")){ + fun addMarker(newMarker: String) { + markers.add(newMarker) + } + fun hasMarker(marker: String): Boolean { + return markers.contains(marker) + } + fun markerToString(): String { + if (markers.size == 0) { + return "" + } + return markers.joinToString(separator = "") { + it.replace(Regex("^\\$\\{([a-z]:)?(.*)\\}"), "$2") + .replace("\$Q", "\uD834\uDD10") + } + } + +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/ParseULine.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/ParseULine.kt new file mode 100644 index 0000000..6f7d8d4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/ParseULine.kt @@ -0,0 +1,135 @@ +package mg.dot.feufaro.solfa + +class ParseULine (var line: String, var measure: Int) { + private var cursorX = 0 + var parsedString = "" + private var afterDollar = false + private var inComment = false + private var charX = ' ' + companion object { + val mapXLate = mutableMapOf>( + '1' to listOf("," , "." , "," , ":"), + '2' to listOf("." , ".-," , ":" , ":-,"), + '3' to listOf(".-," , ".-:" , ":-," , ":-."), + '4' to listOf(":" , ".-;-," , ":-." , ":-.-,"), + 'y' to listOf(".,D:", ".-,:D,", ":,D.", ":-,.D,") + ) + } + + fun parsed() : String { + val matchResult = Regex("^z([048C]):(.+)$").find(line) + if (matchResult != null) { + val capturedChar = matchResult.groups[1]!!.value + cursorX = when (capturedChar) { + "C" -> 12 + else -> capturedChar.toInt() + } + line = matchResult.groups[2]!!.value + parsedString = ":" + } + parseUStage1() + parseUStage3() + return parsedString + } + + private fun parseUStage1() { + val parsedChars = line + // Beware, Android Regex requires "}" to be escaped!!! + .replace(Regex("\\$\\{x([1-9]\\d*)\\}(.*?)\\$\\{x0\\}")) { matchResult -> + val (nbIteration, iterable) = matchResult.destructured + val nTimes = nbIteration.toInt() + iterable.repeat(nTimes) + }.toCharArray() + var actualComment = "" + parsedChars.forEach { + if (inComment) { + parsedString += it + if (it == '}') { + inComment = false + val matchMeasure = Regex("^m:(\\d+)(/.*)?").find(actualComment) + if (matchMeasure != null) { + measure = matchMeasure.groups[1]!!.value.toInt() + } + actualComment = "" + } else { + actualComment += it + } + return@forEach + } + if (afterDollar) { + if (it == '{') { + inComment = true + } + afterDollar = false + parsedString += it + return@forEach + } + if (charX != 'z') { + charX = 'D' + } + when (it) { + '$' -> run { + afterDollar = !afterDollar + parsedString += "$" + } + 'z', '-' -> run { + charX = it + } + 'W' -> repeat(8) { parseUStage2('4')} + 'G' -> repeat(4) { parseUStage2('4') } + 'C' -> repeat(3) { parseUStage2('4') } + '8' -> repeat(2) { parseUStage2('4') } + '4', '3', '2', '1', 'y' -> parseUStage2(it) + '6' -> repeat(2) { parseUStage2('3')} + '9' -> repeat(3) { parseUStage2('3')} + 'A' -> run { + parseUStage2('4') + parseUStage2('4') + parseUStage2('2') + } + '(', ')', '/' -> parsedString += it + } + } + } + private fun parseUStage2(codeChar : Char) { + val indexByFour = cursorX % 4 + var sequence = mapXLate[codeChar]!![indexByFour]!! + if (charX == 'z') { + sequence = sequence.replace('-', charX) + } + cursorX += codeChar.toString().toInt() + if (sequence.contains(':')) { + val bar: Int = (cursorX / 4) % measure + var replacement = ':' + if ( (bar == 2 && measure == 4 ) + || (bar == 3 && measure in listOf(6, 9, 12)) + || (bar in listOf(6, 9) && measure in listOf(9, 12))) + { + replacement = '!' + } + if (bar == 0) { + replacement = '|' + } + if (replacement != ':') { + sequence = sequence.replace(':', replacement) + } + } + var noteChar = 'D' + if (charX in listOf('z', '-')) { + noteChar = charX + } + parsedString += noteChar + sequence + charX = '-' + } + private fun parseUStage3() { + parsedString = parsedString.replace("-,-", "") + .replace(Regex("([:!|])-,-([:!|])"), "$1-$2") + .replace(Regex("([:!|])-\\.-([:!|])"), "$1-$2") + .replace(Regex("[\\.,]-([:!|])"), "$1") + .replace(Regex("[\\.,]([:!|])"), "$1") + .replace(Regex("([:!|])\\)/"), ")$1/") + .replace(Regex("[:!|]/"), "/") + .replace(Regex("/[:!|]"), "/") + .replace(Regex("[z0]([:!|])"), "$1") + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt new file mode 100644 index 0000000..a5760c4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt @@ -0,0 +1,520 @@ +package mg.dot.feufaro.solfa + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import mg.dot.feufaro.FileRepository +import mg.dot.feufaro.CommonTools +import mg.dot.feufaro.SharedViewModel +//import mg.dot.feufaro.getOpt + +class Solfa(val sharedViewModel: SharedViewModel, private val fileRepository: FileRepository) { + private val T: MutableList = mutableListOf() + private val N: MutableList = mutableListOf() + private val L: MutableList = mutableListOf() + private val unparsedNote = mutableListOf() + companion object { + 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("([zdrmfslt-](?>[',]*))([1-9][0-9]*)") + val REGEX_PARSE_META = Regex("\\|(?=[a-z]:)") + val REGEX_LYRICS_COMMENT = Regex("\\$\\{([^\\}]*)\\}") + val REGEX_LYRICS_REPETITION = Regex("_(\\d)") + val REGEX_VOWELS_STAGE1 = Regex("[aeiouyàéỳ,;\\.\\-:!](?![ aeiouyàéỳ,;\\.\\-:!])", RegexOption.IGNORE_CASE) + val REGEX_VOWELS_STAGE2 = Regex("([aeiouyàéỳ,;\\.\\-:!])__", RegexOption.IGNORE_CASE) + val REGEX_MALAGASY_MN = Regex("([aeio])_([nm])([tdjkbp])") + val REGEX_MALAGASY_MN_DASHED = Regex("([aeio][nm])\\-([tdjkbp])") + } + var nextTIndex: Int = -1 + var nextNIndex: Int = -1 + var nextLIndex: Int = -1 + var inGroup: Boolean = false + var templateString: String = "" + var nextPlayed = -1 + + private val meta: MutableMap = mutableMapOf() + private val lyricsComment: MutableList = mutableListOf() + suspend fun nextTimeUnitObject() { + val lastTUO = sharedViewModel.lastTUO() + nextTIndex++ + if (nextTIndex == 5) { + sharedViewModel.doneTUOList() + } + if (T.getOrNull(nextTIndex) == null) { + sharedViewModel.doneTUOList() + return + } + val pTemplate = T[nextTIndex] + val unitObject = TimeUnitObject(pTemplate, lastTUO) + val templateCharArray = pTemplate.template.toCharArray() + var nextMarker: Char = '0' + templateCharArray.forEach { // D.-R + when (it) { + 'D', 'R', 'M', 'F', 'S', 'L', 'T', + 'd', 'r', 'm', 'f', 's', 'l', 't', 'w' -> { + nextNIndex++ + if (!inGroup) { + nextLIndex ++ + if (L.size > nextLIndex) { + unitObject.addLyrics(L[nextLIndex]) + } + } + N.forEachIndexed { voiceNumber, pOneVoiceNote -> run { + unitObject.addNote(voiceNumber, pOneVoiceNote.oneVoiceNote, nextNIndex, nextMarker) + } + } + nextMarker = '0' + } + '.', ',', -> + unitObject.addMarker(it) + '-', 'Z' -> + N.indices.forEach { voiceNumber -> + unitObject.addBlank(voiceNumber, it) + } + '(', '[' -> { + nextLIndex++ + if (L.size > nextLIndex) { + unitObject.addLyrics(L[nextLIndex]) + } + inGroup = true + } + ')', ']' -> { + inGroup = false + } + } + } + withContext(Dispatchers.Main) { + sharedViewModel.addTUO(unitObject) + } + nextTimeUnitObject() + } + fun loadSolfa(sourceFileName: String) { + sharedViewModel.reset() + parse(sourceFileName) + } + fun loadNextInPlaylist() { + val playlist = sharedViewModel?.playlist?.value ?: listOf() + if (playlist.isNotEmpty()) { + nextPlayed = (nextPlayed + 1) % playlist.size + loadSolfa(playlist[nextPlayed]) + } + } + fun parse(sourceFile: String) { + val parseScope = CoroutineScope(Dispatchers.Default) + parseScope.launch { + val lines = try { + fileRepository.readFileLines(sourceFile) + } catch (e: Exception) { + println("Opening $sourceFile raised exception {${e.message}") + emptyList() + } + T.clear() + N.clear() + L.clear() + unparsedNote.clear() + templateString = "" + nextTIndex = -1 + nextNIndex = -1 + nextLIndex = -1 + lyricsComment.clear() + sharedViewModel.setStanza("1") + sharedViewModel.setNbStanzas(0) + TimeUnitObject.hasMarker(false) + lines.forEach { line -> + run { + parseOneLine(line) + } + } + unparsedNote.forEach { + preloadN(it) + } + try { + nextTimeUnitObject() + } catch (e: Exception) { + println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}") + } + } + } + private fun rearrangeNote(noteString: String, infiniteIter: Int = 0): String { + var result: String = noteString + 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) + } + if (infiniteIter >= 70) { + println("$infiniteIter\n$noteString\n$result") + } + return result + } + private fun preloadN(noteLine: String) { + val voiceNumber = noteLine.substring(0, 1).toInt() + val templateStripped = REGEX_TEMPLATE_COMMENT.replace(templateString, "") + val templateCharArray = templateStripped.toCharArray() + val lineRepeated = REGEX_REPETITION.replace(noteLine.substring(1)) { matchResult -> + val (charIterated, iteration) = matchResult.destructured + val nTimes = iteration.toInt() + charIterated.repeat(nTimes) + }.replace(" ", "") + var noteChar2 = "" + var anchor = "d" + var inAnchor = false + var anchorNote = ' ' + lineRepeated.toCharArray().forEach { + if (inAnchor) { + if (anchor.isEmpty()) { + anchorNote = it + } + if (anchor.isNotEmpty() && it !in listOf(',', '\'')) { + inAnchor = false + } else { + anchor += it + return@forEach + } + } + if (it == '#') { + inAnchor = true + anchor = "" + return@forEach + } + noteChar2 += it + val anchorSuffix = anchor.replace(Regex("^[drmfslt]"), "") + 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 == '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')) + ) { + noteChar2 += '\'' + } + } + } + noteChar2 = noteChar2.replace(",'", "") + .replace("',", "") + .replace(",'", "") + .replace("',", "") + + val noteCharIterator = noteChar2.toCharArray().iterator() + var result = "" + var firstInLoop = true + var replacement : Char = 'x' + templateCharArray.forEach { + when (it) { + 'D', 'R', 'M', 'F', 'S', 'L', 'T', + 'd', 'r', 'm', 'f', 's', 'l', 't', 'w' -> { + if (firstInLoop) { + replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd' + firstInLoop = false + } + 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', '-', 'Z')) { + break + } + } + } + '/', '-', 'z', 'Z' -> {} + ',' -> result += ";" + else -> result += it + } + } + result = rearrangeNote(result) + loadN(voiceNumber, result) + } + private fun parseOneLine(line: String) { + val index: Int + val value: String + val lineLength = line.trim().length + if (lineLength <= 3) { + return + } + 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) + } else { + index = line.substring(1, 2).toIntOrNull() ?: 0 + value = line.trim().substring(3) + } + + if (key != "") { + if ((key == "T") && (index == 0)) { + loadT(value) + } else if (key == "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) + } else if (key == "M") { + val metaChunks: List = value.split(REGEX_PARSE_META) + metaChunks.forEach { parseMeta(it) } + parseMeta(value) + sharedViewModel.setMeasure(meta["m"] ?: "") + sharedViewModel.setSongTitle(meta["t"] ?: "") + sharedViewModel.setSongAuthor(meta["a"] ?: "") + sharedViewModel.setSongComposer(meta["h"] ?: "") + sharedViewModel.setSongRhythm(meta["r"] ?: "") + } else if (key == "L") { + loadL(index, value) + } else if (key == "Y") { + loadY(index, value) + } else if (key == "E") { + loadE(index, value) + } else if (key == "U") { + loadU(value) + } else { + //setData(key, index, value) + } + } + + } + private fun loadN(voiceNumber: Int, line: String) { + val newN = POneVoiceNote() + val lineRepeated = REGEX_REPETITION.replace(line) { matchResult -> + val (charIterated, iteration) = matchResult.destructured + val nTimes = iteration.toInt() + charIterated.repeat(nTimes) + } + val lineChar = lineRepeated.toCharArray() + lineChar.forEach { + when (it) { + '\'', ',' -> newN.appendLastNote(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" + } + if (it == 'T') { + noteString += "a" + } + newN.addItem(noteString) + } + } + } + if (voiceNumber == 1) { + N.add(0, POneVoiceNote()) + } + N.add(voiceNumber, newN) + } + private fun loadT(line: String) { + templateString = line + var nextTemplate = "" + val nextMarker: MutableList = mutableListOf() + var tMarker = "" + val lineChar = line.toCharArray() + lineChar.forEach { + if (tMarker != "") { + tMarker += it + if ((it == '}') + || (tMarker.length == 2 && it != '{')) { + TimeUnitObject.hasMarker(true) + nextMarker.add(tMarker) + tMarker = "" + } + return@forEach + } + if (it == '$') { + tMarker = "$" + return@forEach + } + if (it == ':' || it == '|' || it == '/' || it == '!') { + val newTemplateItem = + PTemplate(nextTemplate, it.toString(), nextMarker.toMutableList()) + T.add(newTemplateItem) + nextMarker.clear() + nextTemplate = "" + } else { + nextTemplate += it + } + } + val newTemplateItem = PTemplate(nextTemplate, "}", nextMarker) + T.add(newTemplateItem) + } + + fun loadU(line: String) { + val measureLong = meta.getOrElse("m") { "4/4" } + val measure = measureLong.split("/")[0].toIntOrNull() ?: 4 + val uObject = ParseULine(line, measure) + val parsedULine = uObject.parsed() + loadT(parsedULine) + } + fun parseMeta(line: String) { + /* $_a_keyAbbrev = array( + 'a' => 'author', + 'c' => 'tonality', + 'h' => 'composer', + 'i' => 'interline', + 'l' => 'lyrics font size', + 'm' => 'rhythm', + 'n' => 'note font size', + 'r' => 'speed', + 't' => 'title', +);*/ + if (line.trim() == "") { + return + } + val subKey: String = line.substring(0, 1).trim() + if (subKey == "|") { + return + } + if (subKey != "" && "M" != subKey) { + meta[subKey] = line.substring(2) + } + if ("c" == subKey) { + val tonality = line.uppercaseFirstMeta() + meta["C"] = tonality + sharedViewModel.setSongKey(tonality) + val keyOrigin = CommonTools.tonalityToNumber(tonality) + val transposeTo = getOpt("transposeto") + val transposeAsIf = getOpt("transposeasif") + val keyDest = if (transposeTo != "") { + CommonTools.tonalityToNumber(transposeTo) + } else { + keyOrigin + } + val keyAsIf = if (transposeAsIf != "") { + if (transposeAsIf.toIntOrNull() != null) { + keyOrigin + transposeAsIf.toInt() + } else { + CommonTools.tonalityToNumber(transposeAsIf) + } + } else { + keyOrigin + } + if (!meta.containsKey("transposeValue")) { + meta["transposeValue"] = (keyDest - keyAsIf).toString() + } + if (keyAsIf != keyOrigin) { + meta["C"] = CommonTools.numberToTonality(keyAsIf) + } + if (keyDest != keyOrigin) { + meta["C"] += " (" + CommonTools.numberToTonality(keyDest) + ")" + } + } + } + + private fun String.uppercaseFirstMeta(): String { + if (this.length < 2) { + return "" + } + return get(2).uppercaseChar() + substring(3) + } + + private fun String.addHyphens(): String { + return this + .replace("_/", "/") + //.also { println ("1 $it")} + .replace("_", "-_") + //.also { println ("2 $it")} + .replace(Regex("_-(?=_)"), "_") + //.also { println ("3 $it")} + .replace(" -_", "_") + //.also { println ("4 $it")} + .replace(" _", "_") + //.also { println ("5 $it")} + .replace("--_", "-_") + //.also { println ("6 $it")} + .replace(Regex("_$"), "") + } + private fun loadL(stanzaNumber: Int, lyrics: String) { + try { + getLyricsComments(lyrics) + var loadedLyrics = lyrics.replace(REGEX_LYRICS_COMMENT, "") + .replace(REGEX_LYRICS_REPETITION) { matchResult -> + val repeating = matchResult.destructured.match.groupValues[1] + "_".repeat(repeating.toString().toInt()) + } + //.replace("/", "_") + .addHyphens() + val arrayLyrics = loadedLyrics.split(Regex("[/_]")) + arrayLyrics.forEachIndexed { i, lyricsItem -> + addLyricsItem(stanzaNumber, i, lyricsItem) + } + if (lyricsComment.isNotEmpty()) { + val lyricsIterator = lyricsComment.iterator() + while (lyricsIterator.hasNext()) { + val item = lyricsIterator.next() + if (item.substring(0, 4) == "\${D:") { + item.replace(REGEX_LYRICS_COMMENT, "$1").addHyphens().split(Regex("[_/]")).drop(1).forEachIndexed { i, xval -> + appendLyricsItem(stanzaNumber, i, xval) + } + lyricsIterator.remove() + } + } + } + } catch (e: Exception) { + sharedViewModel.appendData("\nErr: $e") + } + if (stanzaNumber > sharedViewModel.nbStanzas.value) { + sharedViewModel.setNbStanzas(stanzaNumber) + } + } + // loadY is a smart lyrics parser for Malagasy language + private fun loadY(intKey: Int, lyrics: String) { + val loadedLyrics = lyrics + .replace(REGEX_MALAGASY_MN_DASHED, "$1-\\\\$2") + .replace(REGEX_VOWELS_STAGE1, "$0_") + .replace(REGEX_VOWELS_STAGE2, "$1_") + .replace(" ", " _") + .replace("_\\ _", " ") + .replace("_\\", "") + .replace("_/", "/") + .replace("_0", "") + .replace(REGEX_MALAGASY_MN, "$1$2_$3") + .replace("_n'", "n'_") + getLyricsComments(loadedLyrics) + loadL(intKey, loadedLyrics.replace(REGEX_LYRICS_COMMENT, "")) + } + // loadE is a smart Lyrics parser for English language + private fun loadE(intKey: Int, lyrics: String) { + val loadedLyrics = lyrics + .replace(" ", " _") + .replace("\\ _", " ") + .replace("_\\", "") + .replace("_/", "/") + .replace("_0", "") + getLyricsComments(loadedLyrics) + loadL(intKey, loadedLyrics.replace(REGEX_LYRICS_COMMENT, "")) + } + private fun getLyricsComments(lyrics: String) { + val matchResult = REGEX_LYRICS_COMMENT.findAll(lyrics) + .map { it.value }.toList() + if (matchResult.isNotEmpty()) { + lyricsComment.addAll(matchResult) + } + } + private fun addLyricsItem(stanzaNumber: Int, i: Int, lyricsItem: String) { + while (L.size <= i) { + L.add(POneStanzaLyrics()) + } + L[i].setLyrics(stanzaNumber, lyricsItem) + } + private fun appendLyricsItem(stanzaNumber: Int, i: Int, lyricsItem: String) { + if (L.size > i) { + L[i].appendDSLyrics(stanzaNumber, lyricsItem) + } + } +} +fun getOpt(x: String) : String { + return "" +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUNote.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUNote.kt new file mode 100644 index 0000000..f403beb --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TUNote.kt @@ -0,0 +1,62 @@ +package mg.dot.feufaro.solfa + +class TUNote (private val detailNote: MutableList = mutableListOf()){ + var prevTUNote: TUNote? = null + var prevTUO: TimeUnitObject? = null + var annotations: AnnotatedTUO? = null + fun addNote(noteString: String) { + val newNote = Note(noteString) + detailNote.add(newNote) + } + fun from(note: Note?) { + detailNote.add(note ?: Note("")) + } + fun addMarker(newMarker: Char, markerBefore: String = "" ) { + if (newMarker == '0' && markerBefore == "") return + val lastNote = detailNote.lastOrNull() + if (lastNote == null) { + val newNote = Note("") + if (newMarker != '0') { + newNote.addMarker(newMarker.toString()) + } + if (markerBefore != "") { + newNote.markBefore(markerBefore) + } + detailNote.add(newNote) + } else { + lastNote.addMarker(newMarker.toString()) + if (markerBefore != "") { + lastNote.markBefore(markerBefore) + } + } + } + fun annotate(voiceNumber: Int) { + val annotatedTUO = AnnotatedTUO(this.toString(), voiceNumber) + annotations = if (annotatedTUO.underlineSpec.size > 0) { + annotatedTUO + } else { + null + } + } + override fun toString() : String { + var result = detailNote.joinToString (separator = ""){ it.toString() } + .replace(Regex("\\. *,-$"), "") + .replace(Regex(",-$"), "") + .replace(Regex("(- *,z|- *,-|0$)"), "-") + .replace(Regex("([^\\.,])-"), "$1") + .replace(Regex("(?<=[drmfsltia]),,,"), "₃") + .replace(Regex("(?<=[drmfsltia]),,"), "₂") + .replace(Regex("(?<=[drmfsltia]),"), "₁") + .replace("'''", "³") + .replace("''", "²") + .replace("'", "¹") + .replace(Regex("[zZ][zZ\\.,]+"), "") + .replace(Regex("^-\\.,"), "") + .replace(Regex("\\.-$"), "") + .replace(Regex("\\((-[\\.,;][a-zA-Z][ia]*[¹²³₁₂₃]?)\\)"), "$1") + .replace("-", "―") + .replace(".", "\u2022") + .replace("z", "") + return result + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt new file mode 100644 index 0000000..4fabf47 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt @@ -0,0 +1,391 @@ +package mg.dot.feufaro.solfa + +import androidx.compose.foundation.background +import androidx.compose.runtime.Composable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.grid.items +import androidx.compose.material.LocalTextStyle +import androidx.compose.material.Text +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.rememberTextMeasurer +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp +import mg.dot.feufaro.SharedViewModel +import kotlin.math.min + +//import androidx.compose.ui.unit.times + +class TimeUnitObject (val pTemplate: PTemplate, val prevTUO: TimeUnitObject?) { + var mutableNoteVersion: Int by mutableStateOf(0) + private var lyrics: MutableList = mutableListOf() + var sep0: String = "" + val tuNotes: MutableList = mutableListOf() + val numBlock: Int + var markerBefore: String = "" + private var annotated : Boolean + companion object { + var nbBlock: Int = 0 + var sep1: String = "" + var _hasmarker: Boolean by mutableStateOf(false) + var nbStanzas: Int = 0 + fun hasMarker(yes: Boolean) { + _hasmarker = yes + } + } + init { + nbBlock++ + numBlock = nbBlock + sep0 = sep1 + sep1 = pTemplate.separatorAfter + nbStanzas = 0 + annotated = false + } + fun addLyrics(lyricsIn: POneStanzaLyrics) { + lyrics.add(lyricsIn) + } + private fun addNote(voiceNumber: Int, noteString: String, nextMarker: Char) { + val tuNote = tuNotes.getOrNull(voiceNumber)?.addNote(noteString) + if (tuNote == null) { + val prevTUNote: TUNote? = prevTUO?.tuNotes?.getOrNull(voiceNumber) + val newNote = TUNote() + newNote.prevTUNote = prevTUNote + newNote.prevTUO = prevTUO + newNote.addNote(noteString) + newNote.addMarker('0', markerBefore) + tuNotes.add(voiceNumber, newNote) + } + tuNotes[voiceNumber].addMarker(nextMarker) + } + fun addNote(voiceNumber: Int, oneVoiceNote: MutableList, nIndex: Int, nextMarker: Char) { + val thisNote: Note? = oneVoiceNote.getOrNull(nIndex)?.toNote() + val tuNote = tuNotes.getOrNull(voiceNumber) + if (tuNote == null) { + val newTUNote = newTUNote(voiceNumber, thisNote, nextMarker, markerBefore) + tuNotes.add(voiceNumber, newTUNote) + } else { + tuNote.from(thisNote) + } + } + private fun newTUNote(voiceNumber: Int, thisNote: Note?, newMarker: Char, markerBefore: String): TUNote { + val prevTUNote: TUNote? = prevTUO?.tuNotes?.getOrNull(voiceNumber) + val newNote = TUNote() + newNote.prevTUNote = prevTUNote + newNote.prevTUO = prevTUO + newNote.from(thisNote) + if (newMarker != ' ') { + newNote.addMarker(newMarker, markerBefore) + } + return newNote + } + fun markBefore(newMarker: Char) { + markerBefore += newMarker + } + fun addMarker(newMarker: Char) { + if (tuNotes.size == 0) { + markBefore(newMarker) + } + tuNotes.forEach { + it.addMarker(newMarker) + } + } + fun addBlank(voiceNumber: Int, blank: Char) { + val blankString = if (blank == '-') "-" else "" + addNote(voiceNumber, blankString, '0') + } + fun getNum(): Int { + return numBlock + } + fun noteAsMultiString(): String { + val separatorBefore = when (sep0) { + ":" -> sep0 + "!" -> "|" + else -> "" + } + val markersBefore = ""//pTemplate.template.replace(Regex("[a-yA-Y\\(\\[].*"), "") + return tuNotes.drop(1).joinToString (separator = "\n"){ + separatorBefore + markersBefore + it.toString().replace(Regex("[\\(\\)\\[\\]]"), "") + } + } + fun annotate() { + // We must annotate once, because, annotations can be called asynchronously after first filling + if (!annotated) { + tuNotes.forEachIndexed { voiceNumber, tuNote -> + tuNote.annotate(voiceNumber) + } + annotated = true + } + } + fun annotations(): List { + val xreturn = tuNotes.mapNotNull { + it.annotations + } + return xreturn + } + + fun lyricsAsMultiString(stanzaNumber: Int): String { + var result: MutableList = emptyList().toMutableList() + lyrics.forEach { + val byLines = it.toList(stanzaNumber) + byLines.forEachIndexed { i, v -> + while(result.size <= i) { + result.add(i, "") + } + result[i] += "\u00A0$v" + } + } + return result.joinToString(separator = "\n") { it } + } +} + +@Composable +fun TimeUnitComposable( + tuo: TimeUnitObject, + stanzaNumber: Int +) { + val col = if (tuo.getNum() % 2 == 0) Color(0xff, 0xfa, 0xf7) else Color(0xfb, 0xf3, 0xff) + Column( + modifier = Modifier + .background(col) + ) { + if (TimeUnitObject._hasmarker) { + AutoResizingText( + text = tuo.pTemplate.markerToString(), + minFontSize = 8.sp, + maxFontSize = 18.sp, + maxLines = 1, + modifier = Modifier.fillMaxWidth()) + } + Row (modifier = Modifier.height(IntrinsicSize.Min)){ + if (tuo.sep0 == "/" || tuo.sep0 == "|") { + Box( + modifier = Modifier + .width(1.dp) + .fillMaxHeight() + .background(Color.Black) + ) + if (tuo.sep0 == "/") { + Box(modifier = Modifier.width(2.dp)) + Box( + modifier = Modifier + .width(1.dp) + .fillMaxHeight() + .background(Color.Black) + ) + } + Box( + modifier = Modifier + .width(4.dp) + .fillMaxHeight() + ) + } + tuo.annotate() + // Utile pour que Compose recalcule la cellule. La valeur sera toutefois toujours égale à "" + val mutableNoteVersionX = if (tuo.mutableNoteVersion == -1) "7" else "" + val multiLineText = tuo.noteAsMultiString() + var textLayoutResult: TextLayoutResult? by remember { mutableStateOf(null) } + Box (modifier = Modifier.fillMaxWidth() + .drawBehind { + tuo.annotations().mapNotNull { ta -> + ta.underlineSpec.mapNotNull { us -> + var xStart = 0f + val separatorLength = if (tuo.sep0 in listOf(":", "!")) 1 else 0 + if (us.x > -1) { + val lineGlobalStartOffset = textLayoutResult?.getLineStart(ta.voiceNumber - 1) ?: 0 + val globalStartIndex = lineGlobalStartOffset + us.x + separatorLength + xStart = textLayoutResult?.getCursorRect(globalStartIndex)?.left ?: 0f + } + var xEnd = size.width + if (us.y > -1 ) { + val lineGlobalStartOffset = textLayoutResult?.getLineStart(ta.voiceNumber - 1) ?: 0 + // don't ask me why + 1 here makes it work, but it works. + val globalEndIndex = min(lineGlobalStartOffset + us.y + separatorLength + 1, multiLineText.length) + + xEnd = textLayoutResult?.getCursorRect(globalEndIndex)?.right ?: size.width + } + //if (it.underlineSpec.size > 0) { + var colorUnderline = when (tuo.mutableNoteVersion) { + 0 -> Color.Red + 1 -> Color.Yellow + 2 -> Color.Magenta + 3 -> Color.Blue + 4 -> Color.Green + else -> Color.Black + } + val totalHeight = textLayoutResult?.size?.height ?: 0 + val nbNotes = (multiLineText + mutableNoteVersionX).split("\n").size + val yPos = ta.voiceNumber * totalHeight.toFloat() / nbNotes + drawLine( + colorUnderline, + start = Offset(xStart, yPos), + end = Offset(xEnd, yPos) + ) + } + } + } + ){ + Text( + text = multiLineText + mutableNoteVersionX, + onTextLayout = { result -> + textLayoutResult = result + } + ) + } + } + AutoResizingText( + text = tuo.lyricsAsMultiString(stanzaNumber), + minFontSize = 8.sp, + maxFontSize = 16.sp, + maxLines = 1, + modifier = Modifier.fillMaxWidth() + ) + } +} + +@Composable +fun bestTUOWidth(items: List): Dp { + val textMeasurer = rememberTextMeasurer() + val density = LocalDensity.current + var maxWidth by remember { mutableStateOf(0.dp) } + LaunchedEffect(items) { + maxWidth = 0.dp + items.forEach { + val textLayoutResult: TextLayoutResult = textMeasurer.measure( + text = it.noteAsMultiString(), + maxLines = 1 + ) + val textWidth = with(density) { + textLayoutResult.size.width.toDp() + } + if (textWidth > maxWidth) { + maxWidth = textWidth + } + } + } + return maxWidth + 13.dp +} +@Composable +fun AutoResizingText( + text: String, + modifier: Modifier = Modifier, + maxFontSize: TextUnit = 24.sp, // Taille de police maximale de référence + minFontSize: TextUnit = 10.sp, // Taille de police minimale autorisée + textStyle: TextStyle = LocalTextStyle.current, + maxLines: Int = 1 // Pour s'assurer que le texte ne déborde jamais sur deux lignes +) { + var fontSize by remember { mutableStateOf(maxFontSize) } + val textMeasurer = rememberTextMeasurer() // Mesure le texte hors de la composition + val density = LocalDensity.current + + BoxWithConstraints(modifier = modifier) { + // La largeur disponible pour le texte + val maxWidthPx = with(density) { maxWidth.toPx() } + + // Un Text() invisible ou non affiché pour mesurer le texte sans le dessiner directement + // Vous pouvez aussi utiliser textMeasurer.measure() directement + // On rend le texte visible une fois la taille ajustée pour éviter les clignotements + var readyToDraw by remember { mutableStateOf(false) } + val textLinesCount = text.split("\n").size * maxLines + + Text( + text = text, + // Utiliser le même style que le Text final, mais ajuster la taille + style = textStyle.copy(fontSize = fontSize), + maxLines = textLinesCount, // Très important pour qu'il ne déborde pas en hauteur + overflow = TextOverflow.Clip, // Masque si ça déborde encore (ne devrait pas si le calcul est bon) + // onTextLayout est appelé après que le texte ait été mesuré + onTextLayout = { textLayoutResult: TextLayoutResult -> + if (textLayoutResult.hasVisualOverflow && fontSize > minFontSize) { + // Si le texte déborde en largeur, réduisez la taille de la police + // Vous pouvez utiliser un pas plus fin ou une méthode de recherche binaire pour optimiser + fontSize *= 0.9f // Réduire de 10% à chaque fois + } else { + readyToDraw = true // La taille est stable ou min atteinte, on peut dessiner + } + }, + // Le modificateur drawWithContent est utilisé pour retarder le dessin + // jusqu'à ce que la taille de police finale soit déterminée. + modifier = Modifier.fillMaxWidth() // Le modifier du Text interne peut être ajusté + .drawWithContent { + if (readyToDraw) { + drawContent() + } + } + ) + } +} +@Composable +fun LazyVerticalGridTUO( + viewModel: SharedViewModel, + gridWidthPx: Int, + onGridWidthMeasured: (Int) -> Unit, + modifier: Modifier = Modifier +) { + val regexMeasure = Regex("(\\d)/\\d").find(viewModel.measure.value) + val tuoList by viewModel.tuoList.collectAsState() + + Column( + modifier = Modifier + .fillMaxWidth() + .onSizeChanged { size -> + onGridWidthMeasured(size.width) + } + ) { + val density = LocalDensity.current + val horizontalArrangementSpacing: Dp = 0.dp + val columnGroupString = regexMeasure?.groupValues?.get(1) ?: "1" + val columnGroup = columnGroupString.toInt().coerceAtLeast(1) + val itemMinBaseWidth = bestTUOWidth(tuoList) + val gridWidthDp = with(density) { gridWidthPx.toDp() } + + val actualColumns = remember(gridWidthDp, itemMinBaseWidth, columnGroup) { + if (gridWidthDp == 0.dp) return@remember GridCells.Fixed(columnGroup) + var calculatedCols = + (gridWidthDp / (itemMinBaseWidth + horizontalArrangementSpacing)).toInt() + if (calculatedCols == 0) calculatedCols = columnGroup + if (calculatedCols % columnGroup != 0) { + calculatedCols = (calculatedCols / columnGroup) * columnGroup + if (calculatedCols == 0) calculatedCols = columnGroup + } + GridCells.Fixed(calculatedCols.coerceAtLeast(columnGroup)) + + } + val currentStanza by viewModel.stanza.collectAsState() + + LazyVerticalGrid( + columns = actualColumns, + modifier = Modifier.fillMaxWidth().heightIn(max = 800.dp), + + horizontalArrangement = Arrangement.spacedBy(horizontalArrangementSpacing), + verticalArrangement = Arrangement.spacedBy(8.dp), + contentPadding = PaddingValues(8.dp) // c'est juste le padding exterieur de toute la liste + ) { + items(items = tuoList.drop(1), key = { it.numBlock }) { oneTUO -> + TimeUnitComposable( + tuo = oneTUO, + currentStanza + ) + } + } + } +} + + diff --git a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/DesktopFileRepository.kt b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/DesktopFileRepository.kt index c37a017..b36cb0d 100644 --- a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/DesktopFileRepository.kt +++ b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/DesktopFileRepository.kt @@ -24,7 +24,10 @@ class DesktopFileRepository : FileRepository { // IMPORTS AND IMPLEMENTS THE com throw IOException("Failed to read file or asset '$filePath'") } } - override suspend fun readFileContent(filePath: String): String = withContext(Dispatchers.IO) { "" } + override suspend fun readFileContent(filePath: String): String = withContext(Dispatchers.IO) { + val lines = readFileLines(filePath) + lines.joinToString("\n") { it } + } private fun readAssetFileLines(assetFileName: String): List { return try { //myApplicationContext.assets.open(filename.removePrefix("assets://")).bufferedReader().use { diff --git a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/main.kt b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/main.kt index f06a369..2e527f8 100644 --- a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/main.kt +++ b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/main.kt @@ -5,7 +5,9 @@ import androidx.compose.ui.window.application import mg.dot.feufaro.di.commonModule import mg.dot.feufaro.di.desktopModule import org.koin.core.context.GlobalContext.startKoin +import org.koin.core.context.KoinContext import org.koin.core.logger.Level +import org.koin.compose.KoinContext fun main() = application { startKoin { @@ -21,6 +23,8 @@ fun main() = application { onCloseRequest = ::exitApplication, title = "Feufaro", ) { - App() + KoinContext { + App() + } } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 49b7096..a258006 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -17,6 +17,9 @@ kotlin = "2.2.0" kotlinx-coroutines = "1.10.2" #20250704 koin = "4.0.4" +core = "0.91.1" +kmpObservableviewmodelCore = "1.0.0-BETA-3" + [libraries] kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } @@ -36,6 +39,12 @@ koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } koin-core-viewmodel = { module = "io.insert-koin:koin-core-viewmodel", version.ref = "koin" } koin-android = { module = "io.insert-koin:koin-android", version.ref = "koin" } +#20250704 +core = { module = "io.github.pdvrieze.xmlutil:core", version.ref = "core" } +#core-android = { module = "io.github.pdvrieze.xmlutil:core-android", version.ref = "core" } +#core-jdk = { module = "io.github.pdvrieze.xmlutil:core-jdk", version.ref = "core" } +serialization = { module = "io.github.pdvrieze.xmlutil:serialization", version.ref = "core" } +kmp-observableviewmodel-core = { module = "com.rickclephas.kmp:kmp-observableviewmodel-core", version.ref = "kmpObservableviewmodelCore" } [plugins] androidApplication = { id = "com.android.application", version.ref = "agp" }