musicXML solfa

This commit is contained in:
dotmg 2025-07-04 18:07:32 +02:00
parent a2e6070f95
commit 2657b64f35
67 changed files with 2547 additions and 3 deletions

View file

@ -42,7 +42,9 @@ kotlin {
implementation(libs.koin.core) // Koin core for shared logic implementation(libs.koin.core) // Koin core for shared logic
implementation(libs.koin.compose) // Koin for Compose Multiplatform UI implementation(libs.koin.compose) // Koin for Compose Multiplatform UI
implementation(libs.koin.core.viewmodel) // Koin for KMP ViewModels 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 { commonTest.dependencies {
implementation(libs.kotlin.test) implementation(libs.kotlin.test)

View file

@ -17,10 +17,21 @@ import org.jetbrains.compose.ui.tooling.preview.Preview
import feufaro.composeapp.generated.resources.Res import feufaro.composeapp.generated.resources.Res
import feufaro.composeapp.generated.resources.compose_multiplatform import feufaro.composeapp.generated.resources.compose_multiplatform
import org.koin.compose.koinInject
@Composable @Composable
@Preview @Preview
fun App() { fun App() {
val fileRepository = koinInject<FileRepository>()
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 { MaterialTheme {
var showContent by remember { mutableStateOf(false) } var showContent by remember { mutableStateOf(false) }
Column( Column(
@ -29,6 +40,7 @@ fun App() {
.fillMaxSize(), .fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Text(text = fileContent)
Button(onClick = { showContent = !showContent }) { Button(onClick = { showContent = !showContent }) {
Text("Click me!") Text("Click me!")
} }

View file

@ -0,0 +1,33 @@
package mg.dot.feufaro
object CommonTools {
//companion object {
fun tonalityToNumber(tonality: String) : Int {
val result: Map<String, Int> = 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<String> = arrayOf("", "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B")
return if (number in 1..12) {
result[number]
} else {
""
}
}
}

View file

@ -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<String>(viewModelScope, "Next ...")
val data: StateFlow<String> = _data.asStateFlow()
private val _measure = MutableStateFlow<String>(viewModelScope, "")
val measure: StateFlow<String> = _measure.asStateFlow()
private val _songTitle = MutableStateFlow<String>(viewModelScope, "")
val songTitle: StateFlow<String> = _songTitle.asStateFlow()
private val _stanza = MutableStateFlow<Int>(viewModelScope, 0)
val stanza: StateFlow<Int> = _stanza.asStateFlow()
private val _songKey = MutableStateFlow<String>(viewModelScope, "")
val songKey: StateFlow<String> = _songKey.asStateFlow()
private val _songAuthor = MutableStateFlow<String>(viewModelScope,"")
val songAuthor: StateFlow<String> = _songAuthor.asStateFlow()
private val _songComposer = MutableStateFlow<String>(viewModelScope,"")
val songComposer: StateFlow<String> = _songComposer.asStateFlow()
private val _songRhythm = MutableStateFlow<String>(viewModelScope,"")
val songRhythm: StateFlow<String> = _songRhythm.asStateFlow()
private val _nbStanzas = MutableStateFlow<Int>(viewModelScope,0)
val nbStanzas: StateFlow<Int> = _nbStanzas.asStateFlow()
private var _tuoList = MutableStateFlow<List<TimeUnitObject>>(viewModelScope, emptyList())
val tuoList: StateFlow<List<TimeUnitObject>> = _tuoList.asStateFlow()
private var _playlist = MutableStateFlow<List<String>>(viewModelScope, emptyList())
val playlist: StateFlow<List<String>> = _playlist.asStateFlow()
private val tempTimeUnitObjectList = mutableListOf<TimeUnitObject>()
var _hasMarker = MutableStateFlow<Boolean>(viewModelScope, false)
val hasMarker: StateFlow<Boolean> = _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<String>) {
_playlist.value = thePlaylist
}
fun setHasMarker(theHasMarker: Boolean) {
_hasMarker.value = theHasMarker
}
}

View file

@ -1,10 +1,13 @@
package mg.dot.feufaro.di package mg.dot.feufaro.di
import mg.dot.feufaroo.musicXML.MusicXML
import org.koin.dsl.module import org.koin.dsl.module
import org.koin.core.module.dsl.singleOf
val commonModule = module { val commonModule = module {
// Déclarez FileRepository comme un singleton. // Déclarez FileRepository comme un singleton.
// L'implémentation concrète (actual) sera résolue par Koin en fonction de la plateforme. // 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(). // Pour Android, Koin injectera le Context que vous avez fourni via androidContext().
singleOf(::MusicXML)
} }

View file

@ -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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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,
)

View file

@ -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
)

View file

@ -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,
)

View file

@ -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<String> = listOf(),
@Serializable
@XmlElement
@XmlSerialName("encoding-date", "", "")
var encodingDate: String? = null,
@Serializable
@XmlElement
@XmlSerialName("supports", "", "")
var supports: List<MXSupports> = listOf()
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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<MXCreator> = 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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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,
)

View file

@ -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
)

View file

@ -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<MXNote> = 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,
)

View file

@ -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
)

View file

@ -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<MXMiscellaneousField> = listOf()
)

View file

@ -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,
)

View file

@ -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
)

View file

@ -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<MXLyric> = 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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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<MXMeasure> = listOf(),
@Serializable
@XmlSerialName("id", "", "")
var id: String? = null
)

View file

@ -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<MXScorePart> = listOf()
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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
)

View file

@ -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<MXPart> = listOf(),
@Serializable
@XmlElement
@XmlSerialName("credit", "", "")
var credit: List<MXCredit> = listOf(),
@Serializable
@XmlSerialName("version", "", "")
var version: String? = null,
@Serializable
@XmlElement
@XmlSerialName("part-list", "", "")
var partList: MXPartList? = null
)

View file

@ -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
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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,
)

View file

@ -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<MXScorePartwise>(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()
}
}
}

View file

@ -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<Boolean>()
var inSquareParen = mutableListOf<Boolean>()
}
var underlineSpec = mutableListOf<UnderlineSpec>()
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
}
}
}
}
}
}
}
}

View file

@ -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 }
}

View file

@ -0,0 +1,8 @@
package mg.dot.feufaro.solfa
class Markers {
var marker: String = ""
fun unMark(mark: Char) {
marker.replace(mark.toString(), "", ignoreCase = false)
}
}

View file

@ -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<String> = mutableListOf()
var markerBefore: String = ""
fun addMarker(newMarker: String) {
if (marker.contains(newMarker)) return
marker.add(newMarker)
}
fun setMarkers(newMarkers: MutableList<String>) {
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
}
}

View file

@ -0,0 +1,19 @@
package mg.dot.feufaro.solfa
class PNotes (var note: String, private var markers: MutableList<String> = 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
}
}

View file

@ -0,0 +1,25 @@
package mg.dot.feufaro.solfa
class POneStanzaLyrics {
private val lyrics: MutableList<String> = 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<String> {
return if (this.lyrics.size > stanzaNumber) {
this.lyrics[stanzaNumber].split("\n")
} else
emptyList()
}
}

View file

@ -0,0 +1,24 @@
package mg.dot.feufaro.solfa
class POneVoiceNote {
val oneVoiceNote: MutableList<PNotes> = mutableListOf()
companion object {
var nextMarkers: MutableList<String> = 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()
}
}

View file

@ -0,0 +1,20 @@
package mg.dot.feufaro.solfa
class PTemplate (val template: String, val separatorAfter: String, private val markers: MutableList<String> = 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")
}
}
}

View file

@ -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<Char, List<String>>(
'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")
}
}

View file

@ -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<PTemplate> = mutableListOf()
private val N: MutableList<POneVoiceNote> = mutableListOf()
private val L: MutableList<POneStanzaLyrics> = mutableListOf()
private val unparsedNote = mutableListOf<String>()
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<String, String> = mutableMapOf()
private val lyricsComment: MutableList<String> = 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<String> = 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<String> = 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 ""
}

View file

@ -0,0 +1,62 @@
package mg.dot.feufaro.solfa
class TUNote (private val detailNote: MutableList<Note> = 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
}
}

View file

@ -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<POneStanzaLyrics> = mutableListOf()
var sep0: String = ""
val tuNotes: MutableList<TUNote> = 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<PNotes>, 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<AnnotatedTUO> {
val xreturn = tuNotes.mapNotNull {
it.annotations
}
return xreturn
}
fun lyricsAsMultiString(stanzaNumber: Int): String {
var result: MutableList<String> = emptyList<String>().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<TimeUnitObject>): 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
)
}
}
}
}

View file

@ -24,7 +24,10 @@ class DesktopFileRepository : FileRepository { // IMPORTS AND IMPLEMENTS THE com
throw IOException("Failed to read file or asset '$filePath'") 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<String> { private fun readAssetFileLines(assetFileName: String): List<String> {
return try { return try {
//myApplicationContext.assets.open(filename.removePrefix("assets://")).bufferedReader().use { //myApplicationContext.assets.open(filename.removePrefix("assets://")).bufferedReader().use {

View file

@ -5,7 +5,9 @@ import androidx.compose.ui.window.application
import mg.dot.feufaro.di.commonModule import mg.dot.feufaro.di.commonModule
import mg.dot.feufaro.di.desktopModule import mg.dot.feufaro.di.desktopModule
import org.koin.core.context.GlobalContext.startKoin import org.koin.core.context.GlobalContext.startKoin
import org.koin.core.context.KoinContext
import org.koin.core.logger.Level import org.koin.core.logger.Level
import org.koin.compose.KoinContext
fun main() = application { fun main() = application {
startKoin { startKoin {
@ -21,6 +23,8 @@ fun main() = application {
onCloseRequest = ::exitApplication, onCloseRequest = ::exitApplication,
title = "Feufaro", title = "Feufaro",
) { ) {
KoinContext {
App() App()
} }
}
} }

View file

@ -17,6 +17,9 @@ kotlin = "2.2.0"
kotlinx-coroutines = "1.10.2" kotlinx-coroutines = "1.10.2"
#20250704 #20250704
koin = "4.0.4" koin = "4.0.4"
core = "0.91.1"
kmpObservableviewmodelCore = "1.0.0-BETA-3"
[libraries] [libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" } 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-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" }
koin-core-viewmodel = { module = "io.insert-koin:koin-core-viewmodel", 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" } 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] [plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" } androidApplication = { id = "com.android.application", version.ref = "agp" }