From 694292c0cd35193c5e1ba9e495cfdecef038d7c9 Mon Sep 17 00:00:00 2001 From: dotmg Date: Thu, 17 Jul 2025 19:19:58 +0200 Subject: [PATCH] toSolfa(), first take --- .../commonMain/kotlin/mg/dot/feufaro/App.kt | 3 +- .../kotlin/mg/dot/feufaro/di/AppModule.kt | 2 + .../mg/dot/feufaro/musicXML/MXBracket.kt | 18 ++ .../mg/dot/feufaro/musicXML/MXDirection.kt | 2 +- .../dot/feufaro/musicXML/MXDirectionType.kt | 12 ++ .../mg/dot/feufaro/musicXML/MXDynamics.kt | 36 ++++ .../kotlin/mg/dot/feufaro/musicXML/MXNote.kt | 29 ++- .../kotlin/mg/dot/feufaro/musicXML/MXPitch.kt | 31 ++- .../kotlin/mg/dot/feufaro/musicXML/MXRest.kt | 16 ++ .../feufaro/musicXML/MXTimeModification.kt | 21 ++ .../mg/dot/feufaro/musicXML/MXTuplet.kt | 56 ++++++ .../dot/feufaro/musicXML/MXTupletPortion.kt | 22 +++ .../kotlin/mg/dot/feufaro/musicXML/MXWedge.kt | 17 ++ .../mg/dot/feufaro/musicXML/MusicXML.kt | 182 +++++++++++++++++- .../mg/dot/feufaro/musicXML/SolfaXML.kt | 126 ++++++++++++ .../dot/feufaro/musicXML/SolfaXMLOneNote.kt | 6 + .../mg/dot/feufaro/musicXML/SolfaXMLTuo.kt | 51 +++++ .../dot/feufaro/musicXML/XSLTransformation.kt | 83 ++++++++ .../kotlin/mg/dot/feufaro/solfa/Transpose.kt | 15 +- 19 files changed, 708 insertions(+), 20 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXBracket.kt create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDynamics.kt create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXRest.kt create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTimeModification.kt create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTuplet.kt create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTupletPortion.kt create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXWedge.kt create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXML.kt create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXMLOneNote.kt create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXMLTuo.kt create mode 100644 composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/XSLTransformation.kt diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt index a412009..39b373a 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/App.kt @@ -52,7 +52,8 @@ fun App() { } } - val musicXML = MusicXML(fileRepository) + val musicXML: MusicXML = koinInject() + } 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 648689b..7c84898 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/di/AppModule.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/di/AppModule.kt @@ -5,6 +5,7 @@ import mg.dot.feufaro.CommonFileRepository import mg.dot.feufaro.FileRepository import mg.dot.feufaro.DisplayConfigManager // Importez DisplayConfigManager import mg.dot.feufaro.musicXML.MusicXML +import mg.dot.feufaro.musicXML.SolfaXML import mg.dot.feufaro.solfa.Solfa import mg.dot.feufaro.solfa.TimeUnitObject import mg.dot.feufaro.viewmodel.SolfaScreenModel @@ -19,4 +20,5 @@ val commonModule = module { single { Solfa(get(), get()) } single { SolfaScreenModel(get()) } + single { MusicXML(get()) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXBracket.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXBracket.kt new file mode 100644 index 0000000..2c6d8c5 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXBracket.kt @@ -0,0 +1,18 @@ +package mg.dot.feufaro.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("bracket ", "", "") +data class MXBracket ( + @Serializable + @XmlSerialName("number", "", "") + var number: Int? = null, + @Serializable + @XmlValue + @XmlSerialName("content", "", "") + var type: String = "" +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirection.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirection.kt index 1eeff27..dca981e 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirection.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirection.kt @@ -17,5 +17,5 @@ data class MXDirection ( @Serializable @XmlElement @XmlSerialName("direction-type", "", "") - var directionType: MXDirectionType? = null + var directionType: List = listOf() ) \ 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 index 55b6b53..8354049 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirectionType.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDirectionType.kt @@ -11,4 +11,16 @@ data class MXDirectionType ( @XmlElement @XmlSerialName("words", "", "") var words: String? = null, +@Serializable +@XmlElement + var segno: String? = null, +@Serializable +@XmlElement +var wedge: MXWedge? = null, +@Serializable +@XmlElement + var dynamics: List = listOf(), + @Serializable + @XmlElement + var bracket: MXBracket? = null ) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDynamics.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDynamics.kt new file mode 100644 index 0000000..d7b67ee --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXDynamics.kt @@ -0,0 +1,36 @@ +package mg.dot.feufaro.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("dynamics ", "", "") +data class MXDynamics ( + @Serializable @XmlElement var p: String? = null, + @Serializable @XmlElement var pp: String? = null, + @Serializable @XmlElement var ppp: String? = null, + @Serializable @XmlElement var pppp: String? = null, + @Serializable @XmlElement var ppppp: String? = null, + @Serializable @XmlElement var pppppp: String? = null, + @Serializable @XmlElement var f: String? = null, + @Serializable @XmlElement var ff: String? = null, + @Serializable @XmlElement var fff: String? = null, + @Serializable @XmlElement var ffff: String? = null, + @Serializable @XmlElement var fffff: String? = null, + @Serializable @XmlElement var ffffff: String? = null, + @Serializable @XmlElement var mp: String? = null, + @Serializable @XmlElement var mf: String? = null, + @Serializable @XmlElement var sf: String? = null, + @Serializable @XmlElement var sfp: String? = null, + @Serializable @XmlElement var sfpp: String? = null, + @Serializable @XmlElement var fp: String? = null, + @Serializable @XmlElement var rf: String? = null, + @Serializable @XmlElement var rfz: String? = null, + @Serializable @XmlElement var sfz: String? = null, + @Serializable @XmlElement var sffz: String? = null, + @Serializable @XmlElement var fz: String? = null, + @Serializable @XmlElement var n: String? = null, + @Serializable @XmlElement var sfzp: String? = null, + +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXNote.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXNote.kt index 89b420b..e7f6052 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXNote.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXNote.kt @@ -53,5 +53,32 @@ data class MXNote ( @Serializable @XmlElement @XmlSerialName("accidental", "", "") - var accidental: String? = null + var accidental: String? = null, +@Serializable +@XmlElement + var direction: MXDirection? = null, +@Serializable +@XmlElement +@XmlSerialName("backup-duration", "", "") +var backupDuration: Int? = null, +@Serializable +@XmlElement +@XmlSerialName("forward-duration", "", "") +var forwardDuration: Int? = null, +@Serializable +@XmlElement +var grace: String? = null, +@Serializable +@XmlElement +var cue: String? = null, +@Serializable +@XmlElement +@XmlSerialName("time-modification", "", "") +var timeModification: MXTimeModification? = null, +@Serializable +@XmlElement +var unpitched: String? = null, +@Serializable +@XmlElement +var rest: String? = null, ) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPitch.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPitch.kt index 4e878e8..4754cf8 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPitch.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXPitch.kt @@ -1,6 +1,7 @@ package mg.dot.feufaro.musicXML import kotlinx.serialization.Serializable +import mg.dot.feufaro.solfa.Transpose import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlSerialName @@ -18,5 +19,31 @@ data class MXPitch ( @Serializable @XmlElement @XmlSerialName("alter", "", "") - var alter: Int? = null -) + var alter: Int? = null, +@Serializable +@XmlElement +var unpitched: String? = null, + +) { + companion object { + val stepToSolfa: Map = mapOf( + "C" to "d", + "D" to "r", + "E" to "m", + "F" to "f", + "G" to "s", + "A" to "l", + "B" to "t" + ) + val octaveToSolfa = listOf("₄", "₃", "₂", "₁", "", "¹", "²", "³", "⁴") + } + fun toCNote(numPart: Int = 1): String { + val note = stepToSolfa.getOrElse(step ?: "") { "" } + val height = octaveToSolfa.getOrElse((octave ?: 0) + numPart - 1) { "" } + return note + height + } + fun toKey(key: String, numPart: Int): String { + if (unpitched != null) return "z" + return Transpose.transpose(toCNote(numPart), "C", key, alter ?: 0) + } +} diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXRest.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXRest.kt new file mode 100644 index 0000000..d12af3a --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXRest.kt @@ -0,0 +1,16 @@ +package mg.dot.feufaro.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("rest ", "", "") +data class MXRest ( +@Serializable +@XmlSerialName("display-step", "", "") + var displayStep: String? = null, +@Serializable +@XmlSerialName("display-octave", "", "") + var displayOctave: Int? = null, +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTimeModification.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTimeModification.kt new file mode 100644 index 0000000..eabb5f0 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTimeModification.kt @@ -0,0 +1,21 @@ +package mg.dot.feufaro.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("time-modification ", "", "") +data class MXTimeModification ( + @Serializable + @XmlSerialName("actual-notes", "", "") + var actualNotes: Int = 0, + @Serializable + @XmlSerialName("normal-notes", "", "") + var normalNotes: Int = 0, + @Serializable + @XmlSerialName("normal-type", "", "") + var normalType: String? = "", + @Serializable + @XmlSerialName("normal-dot", "", "") + var normalDot: List? = null +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTuplet.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTuplet.kt new file mode 100644 index 0000000..a30a1b7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTuplet.kt @@ -0,0 +1,56 @@ +package mg.dot.feufaro.musicXML + + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + + +data class MXTuplet ( + @Serializable + @XmlElement + @XmlSerialName("tuplet-actual", "", "") + var tupletActual: MXTupletPortion? = null, + /** + * The tuplet-normal element provide optional full control over how the normal part of the tuplet is displayed, including number and note type (with dots). If any of these elements are absent, their values are based on the time-modification element. + * + */ + @Serializable + @XmlElement + @XmlSerialName("tuplet-normal") + var tupletNormal: MXTupletPortion? = null, + @Serializable + var type : String, + @Serializable + var number: Int?, + @Serializable + var bracket: String?, + @Serializable + @XmlSerialName("show-tuplet") + var showTuplet: String?, + @Serializable + @XmlSerialName("show-number") + var showNumber: String?, + @Serializable + @XmlSerialName("show-type") + var showType: String?, + @Serializable + var id: String?, + @Serializable + @XmlSerialName("default-x") + var defaultX: Int, + @Serializable + @XmlSerialName("default-y") + var defaultY: Int, + @Serializable + @XmlSerialName("relative-x") + var relativeX: Int, + @Serializable + @XmlSerialName("relative-y") + var relativeY: Int, + @Serializable + var placement: String?, + @Serializable + @XmlSerialName("line-shape") + var lineShape: String?, +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTupletPortion.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTupletPortion.kt new file mode 100644 index 0000000..c5db839 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXTupletPortion.kt @@ -0,0 +1,22 @@ +package mg.dot.feufaro.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("typlet-portion ", "", "") +data class MXTupletPortion ( + @Serializable + @XmlElement + @XmlSerialName("tuplet-number") + var tupletNumber: Int = 0, + @Serializable + @XmlElement + @XmlSerialName("tuplet-type") + var tupletType: String? = null, + @Serializable + @XmlElement + @XmlSerialName("tuplet-dot") + var tupletDot: String? = null +) diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXWedge.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXWedge.kt new file mode 100644 index 0000000..ff84e3d --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MXWedge.kt @@ -0,0 +1,17 @@ +package mg.dot.feufaro.musicXML + +import kotlinx.serialization.Serializable +import nl.adaptivity.xmlutil.serialization.XmlElement +import nl.adaptivity.xmlutil.serialization.XmlSerialName + +@Serializable +@XmlSerialName("wedge ", "", "") +data class MXWedge ( + @Serializable + @XmlElement + var type: String = "", + @Serializable + @XmlElement + @XmlSerialName("number", "", "") + var number: Int? = 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 index 2ef79af..1345d1a 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MusicXML.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/MusicXML.kt @@ -7,7 +7,9 @@ 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 +import java.util.Locale +import java.util.Locale.getDefault +import kotlin.math.max class MusicXML(private val fileRepository: FileRepository) { val module = SerializersModule { @@ -18,33 +20,193 @@ class MusicXML(private val fileRepository: FileRepository) { ignoreUnknownChildren() } } + var dollarMarkers: MutableList = mutableListOf() var xmlString = "" - suspend fun load() { + fun processDirection(dir: MXDirection) { + dir.directionType.forEach { dirType -> + if (dirType.words != null) { + val daCapo = dirType.words!!.replace(".", "") + if (daCapo.startsWith("DC") || daCapo.startsWith("DS") || daCapo.lowercase(getDefault()).startsWith("rit")) { + dollarMarkers += daCapo + } + } + if (dirType.segno != null) { + dollarMarkers += "$" + } + if (dirType.wedge != null) { + when (dirType.wedge!!.type) { + "crescendo" -> dollarMarkers += "<" + "diminuendo" -> dollarMarkers += ">" + "stop" -> dollarMarkers += "=" + } + } + if (dirType.dynamics.isNotEmpty()) { + dirType.dynamics.forEach { it -> + when { + it.p != null -> dollarMarkers += "p" + it.pp != null -> dollarMarkers += "pp" + it.ppp != null -> dollarMarkers += "ppp" + it.pppp != null -> dollarMarkers += "pppp" + it.ppppp != null -> dollarMarkers += "ppppp" + it.pppppp != null -> dollarMarkers += "pppppp" + it.f != null -> dollarMarkers += "f" + it.ff != null -> dollarMarkers += "ff" + it.fff != null -> dollarMarkers += "fff" + it.ffff != null -> dollarMarkers += "ffff" + it.fffff != null -> dollarMarkers += "fffff" + it.ffffff != null -> dollarMarkers += "ffffff" + it.mp != null -> dollarMarkers += "mp" + it.mf != null -> dollarMarkers += "mf" + it.sf != null -> dollarMarkers += "sf" + it.sfp != null -> dollarMarkers += "sfp" + it.sfpp != null -> dollarMarkers += "sfpp" + it.fp != null -> dollarMarkers += "fp" + it.rf != null -> dollarMarkers += "rf" + it.rfz != null -> dollarMarkers += "rfz" + it.sfz != null -> dollarMarkers += "sfz" + it.sffz != null -> dollarMarkers += "sffz" + it.fz != null -> dollarMarkers += "fz" + it.n != null -> dollarMarkers += "n" + it.sfzp != null -> dollarMarkers += "sfzp" + } - xmlString = fileRepository.readFileContent("assets://13.xml") + } + } + if (dirType.bracket?.type in listOf("start", "stop")) { + when (dirType.bracket!!.type) { + "start" -> dollarMarkers += "B[" + "stop" -> dollarMarkers += "B]" + } + } + } + } + suspend fun load() { + val xmlContent = fileRepository.readFileContent("assets://12.xml") + val xslContent = fileRepository.readFileContent("assets://timepart.xsl") + xmlString = performXsltTransformation(xmlContent, xslContent) + val solfaXML = SolfaXML() try { val xmlObject = xml.decodeFromString(xmlString) - var keyFifths = 0 + var keyFifths = 6 + var measureLength = 0 var timeIndex = 0 + var tupletType = ' ' + var inTupletCount = 0 + var voiceAlter = 0 + var lastChordDuration = 0 + var timePointer = 0 + var maxTimePointer = 0 + var numPart = 0 + var firstMeasureLength = 0 + var workTitle = xmlObject.work?.workTitle + if (workTitle == null) { + workTitle = xmlObject.work?.workNumber + } + val creator = xmlObject.identification?.creator + var lyricist = "" + var composer = "" + creator?.map { it -> + if (it.type == "lyricist") { + lyricist = it.content ?: "" + } + if (it.type == "composer") { + composer = it.content ?: "" + } + } + val credits = xmlObject.credit.filter { credit -> + credit.creditType == "composer" + } + if (credits.isNotEmpty() && composer == "") { + composer = credits[0].creditWords?.content ?: "" + } + solfaXML.setMeta("t", workTitle) + solfaXML.setMeta("h", composer) + solfaXML.setMeta("a", lyricist) xmlObject.part.forEach { partNode -> + numPart ++ + maxTimePointer = 0 + var lastTimeCursor = 0 + measureLength = 0 partNode.measure.forEach { measureNode -> - if (measureNode.backup?.duration != null) { + /*if (measureNode.backup?.duration != null) { val backupDuration = measureNode.backup!!.duration!! timeIndex -= backupDuration + }*/ + timePointer = maxTimePointer + measureLength = max(measureLength, timePointer - lastTimeCursor) + if (lastTimeCursor == 0 && measureLength != 0) { + firstMeasureLength = measureLength } + lastTimeCursor = timePointer if (measureNode.attributes?.key?.fifths != null) { keyFifths = measureNode.attributes!!.key!!.fifths!! } - val keySign = measureNode.attributes?.clef?.sign - val nominator = measureNode.attributes?.time?.beats + solfaXML.setKeyFifths(keyFifths) + //val keySign = measureNode.attributes?.clef?.sign + val numerator = measureNode.attributes?.time?.beats val denominator = measureNode.attributes?.time?.beatType + solfaXML.setMeasure(numerator, denominator) measureNode.note.forEach { noteNode -> - val voiceNumber = noteNode.voice - val duration = noteNode.duration - val chord = noteNode.chord + // avec un xlst, il faut changer les en 1 en 1 "z" + noteNode.rest != null -> "z" + noteNode.pitch != null -> noteNode.pitch!!.toKey(solfaXML.currentKey, numPart) + else -> "z" + } + solfaXML.addNote(voiceNumber+voiceAlter, timePointer, duration ?: 0, nextNote, numPart, beam, dollarMarkers) + dollarMarkers = mutableListOf() + /*noteNode.notations?.forEach {notationsNode -> + }*/ + timePointer += duration ?: 0 + if (maxTimePointer < timePointer) { + maxTimePointer = timePointer + } } } } + solfaXML.toSolfa(measureLength, firstMeasureLength) + println(measureLength) } catch(e: Exception) { println("${e.message}") } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXML.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXML.kt new file mode 100644 index 0000000..141c86c --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXML.kt @@ -0,0 +1,126 @@ +package mg.dot.feufaro.musicXML + +class SolfaXML { + private val metaC = listOf("Gb", "Db", "Ab", "Eb", "Bb", "F", "C", "G", "D", "A", "E", "B", "Gb", "Db") + private var meta: MutableMap = mutableMapOf() + var tuo: MutableList = mutableListOf() + var currentKey = "C" + var curBeam: MXBeam? = null + var curMarkers: MutableList = mutableListOf() + var startTuplet = false + fun setMeta(key: String, value: String?) { + if (value != null && value != "") { + meta[key] = value + } + } + fun setTuplet(seton: Boolean = true) { + startTuplet = seton + } + fun setKeyFifths(keyFifths: Int) { + currentKey = metaC.getOrElse(keyFifths+6) { "C" } + if (!meta.containsKey("c")) { + meta["c"] = currentKey + } + } + fun setMeasure(numerator: Int?, denominator: Int?) { + var measure1 = numerator ?: 4 + var measure2 = denominator ?: 4 + if (measure2 == 2) { + measure1 *= 2 + measure2 *= 2 + } + meta["m"] = "$measure1/$measure2" + } + fun newTuo(newTimeLapse: Int, newDuration: Int): SolfaXMLTuo { + val new = SolfaXMLTuo().setTime(newTimeLapse, newDuration) + addTuo(new) + return new + } + fun addTuo(newTuo: SolfaXMLTuo) { + if (curBeam != null) { + if (curBeam?.content == "begin") { + newTuo.addMarker("(") + } + if (curBeam?.content == "end") { + newTuo.addMarker(")") + } + if (startTuplet) { + newTuo.addMarker("t") + startTuplet = false + } + } + if (curMarkers.isNotEmpty()) { + curMarkers.forEach { + newTuo.addMarker("\\$\\{$it\\}") + } + } + curBeam = null + curMarkers = mutableListOf() + tuo.add(newTuo) + } + fun addNote(voiceNumberIn: Int, timePointer: Int, duration: Int, note: String, numPart: Int? = 1, beam: MXBeam? = null, dollarMarkers: MutableList = mutableListOf()) { + curBeam = beam + if (curBeam != null) { + println(curBeam) + } + curMarkers = dollarMarkers + val tuoEdit: SolfaXMLTuo = tuo.find { foundTuo -> + timePointer >= foundTuo.timeLapse && timePointer < (foundTuo.timeLapse + foundTuo.duration) + } ?: newTuo(timePointer, duration) + val voiceNumber = 2 * ((numPart ?: 1) - 1) + (2 - (voiceNumberIn % 2)) + val lapse1 = tuoEdit.timeLapse + val lapse2 = timePointer + val lapse3 = timePointer + duration + val lapse4 = tuoEdit.timeLapse + tuoEdit.duration + val lapse5 = lapse3 + val duration1 = lapse2 - lapse1 + val duration2 = lapse3 - lapse2 + val duration3 = lapse4 - lapse3 + val duration4 = lapse5 - lapse4 + + if (duration1 > 0) { + val newTuo = tuoEdit.clone(lapse1, duration1) + addTuo(newTuo) + } + if (duration3 > 0) { + val newTuo = tuoEdit.clone(lapse3, duration3, newNote = "+") + addTuo(newTuo) + } + if (duration2 > 0) { + if (duration3 > 0) { + tuoEdit.setTime(lapse2, duration2) + } + if (duration1 > 0) { + tuoEdit.oneNote.forEachIndexed { itVoice, itNote -> + tuoEdit.setNote(itVoice, "+") + } + } + tuoEdit.setNote(voiceNumber, note) + } + if (duration4 > 0) { + addNote(voiceNumber, lapse4, duration4, "+", numPart) + } + } + fun toSolfa(measureLength: Int, firstMeasureLength: Int) { + val metaString = "M0:|" + meta.map { (key, value) -> + "$key:" + value.replace('|', '!') + }.joinToString(separator = "|") + if (tuo.isEmpty()) { + println("TUO Empty") + return + } + var uString = "U0:z" + val measure = meta.getOrElse("m") { "4/4"} + val numerator = measure.replace(Regex("/.*"), "").toIntOrNull() ?: 0 + val multiplier = 4 * numerator / measureLength + val blanksAtStart = 4 * numerator - (firstMeasureLength * multiplier) + val letters = "0123456789ABCDEFGHIJKLMNOPQRSTUVW" + if (blanksAtStart >= 0 && blanksAtStart < 32) { + uString += letters[blanksAtStart] + } + uString += ":" + + println(metaString) + println(uString) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXMLOneNote.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXMLOneNote.kt new file mode 100644 index 0000000..f95e21f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXMLOneNote.kt @@ -0,0 +1,6 @@ +package mg.dot.feufaro.musicXML + +class SolfaXMLOneNote { + var voiceNumber: Int = 0 + var note: String = "" +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXMLTuo.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXMLTuo.kt new file mode 100644 index 0000000..bd051dd --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/SolfaXMLTuo.kt @@ -0,0 +1,51 @@ +package mg.dot.feufaro.musicXML + +data class SolfaXMLTuo ( + var timeLapse: Int = 0, + var duration: Int = 0, + var markers: MutableList = mutableListOf(), + var oneNote: MutableList = mutableListOf(), + val lyrics: MutableList = mutableListOf() +) { + fun addMarker(marker: String) { + markers += marker + } + fun setTime(newTimeLapse: Int?, newDuration: Int?): SolfaXMLTuo { + if (newTimeLapse != null) timeLapse = newTimeLapse + if (newDuration != null) duration = newDuration + return this + } + fun setNote(voiceNumber: Int, note: String) { + while (oneNote.size <= voiceNumber) { + if (oneNote.isEmpty()) { + oneNote.add("") + } else { + oneNote.add(note) + } + } + oneNote[voiceNumber] = note + } + fun clone( + timeLapse: Int = this.timeLapse, + duration: Int = this.duration, + markers: MutableList = this.markers.toMutableList(), + oneNote: MutableList = this.oneNote.toMutableList(), + newNote: String? = null + ): SolfaXMLTuo { + val cloned = SolfaXMLTuo(timeLapse, duration, markers, oneNote) + if (newNote != null) { + cloned.oneNote.forEachIndexed { itVoice, itNote -> + cloned.setNote(itVoice, newNote) + } + } + return cloned + } + fun addLyrics(mxLyrics: MXLyric) { + val number = mxLyrics.number ?: -1 + val text = mxLyrics.text ?: "" + while(lyrics.size <= number) { + lyrics.add(lyrics.size, "") + } + lyrics[number] = text + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/XSLTransformation.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/XSLTransformation.kt new file mode 100644 index 0000000..74953d2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/musicXML/XSLTransformation.kt @@ -0,0 +1,83 @@ +package mg.dot.feufaro.musicXML + +import java.io.File +import javax.xml.transform.TransformerFactory +import javax.xml.transform.stream.StreamSource +import javax.xml.transform.stream.StreamResult +import java.io.StringReader +import java.io.StringWriter + +/** + * Crée un fichier temporaire avec le nom de fichier suggéré. + * L'emplacement exact est le répertoire temporaire du système. + * + * @param filename Le nom de fichier suggéré (ex: "working.music.xml"). + * @return Le [File] temporaire créé. + */ +fun createTempFileWithFixedName(filename: String): File { + val tempDir = System.getProperty("java.io.tmpdir") // Répertoire temporaire système + val tempFile = File(tempDir, filename) + + // S'assurer que le fichier est nouveau ou vide s'il existe déjà + if (tempFile.exists()) { + tempFile.delete() // Supprimer l'ancien fichier s'il existe + } + tempFile.createNewFile() // Créer le fichier vide + + return tempFile +} + +/** + * Performs an XSLT transformation on XML and XSLT content provided as strings. + * The transformed XML content is returned as a String. + * + * @param xmlInputContent The XML input content as a String. + * @param xsltContent The XSLT stylesheet content as a String. + * @param outputFilename The name for the temporary output file. Defaults to "working.music.xml". + * @return The transformed XML content as a [String]. + * @throws Exception If an error occurs during transformation. + */ +fun performXsltTransformation( + xmlInputContent: String, + xsltContent: String, + outputFilename: String = "working.music.xml" // Default filename for the temp file +): String { + // Create a temporary file to store the transformed XML. + // This is useful if the transformer writes to a file, or if you need to debug. + // However, the primary return will be a String. + val xmlOutputFile = createTempFileWithFixedName(outputFilename) + + try { + val factory = TransformerFactory.newInstance() + + // Create StreamSource from StringReaders for in-memory transformation + val xmlSource = StreamSource(StringReader(xmlInputContent)) + val xsltSource = StreamSource(StringReader(xsltContent)) + + // Create a StringWriter to capture the transformed XML output + val resultWriter = StringWriter() + val streamResult = StreamResult(resultWriter) + + val transformer = factory.newTransformer(xsltSource) + + // Perform the transformation + transformer.transform(xmlSource, streamResult) + + // Get the transformed XML content from the StringWriter + val transformedContent = resultWriter.toString() + + println("Transformation XSLT successful. Transformed content returned as String.") + // Optionally, write the transformed content to the temporary file for debugging or persistence + if (outputFilename != "") { + xmlOutputFile.writeText(transformedContent) + println("Also saved to temporary file: ${xmlOutputFile.absolutePath}") + } + + return transformedContent + + } catch (e: Exception) { + System.err.println("Error during XSLT transformation: ${e.message}") + e.printStackTrace() + throw e // Re-throw the exception for calling error handling + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Transpose.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Transpose.kt index 97ae384..e021957 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Transpose.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Transpose.kt @@ -22,7 +22,7 @@ class Transpose { companion object { val noteToNumber = listOf("d", "di", "r", "ri", "m", "f", "fi", "s", "si", "l", "ta", "t") val keyToNumber = listOf("C", "Db", "D", "Eb", "E", "F", "Gb", "G", "Ab", "A", "Bb", "B" ) - fun transpose(note: String, fromKey: String, toKey: String): String { + fun transpose(note: String, fromKey: String, toKey: String, alter: Int = 0): String { val regexFound = Regex("^([drmfslt][ia]?)([¹²³₁₂₃]?)(.*)").find(note) if (regexFound == null) { return note @@ -31,7 +31,7 @@ class Transpose { val noteNum = noteToNumber.indexOf(noteNaked) val oldKey = keyToNumber.indexOf(fromKey) val newKey = keyToNumber.indexOf(toKey) - var newNoteNum = noteNum - newKey + oldKey + var newNoteNum = noteNum - newKey + oldKey + alter var suffix = "" val noteSize = noteToNumber.size if (newNoteNum < 0) { @@ -45,14 +45,19 @@ class Transpose { val newNote = noteToNumber[newNoteNum] + regexFound.groupValues[2] + suffix return newNote.replace("¹,", "") .replace("¹'", "²") - .replace("²,", "¹") - .replace("²'", "³") - .replace("³,", "²") + .replace("¹,", "") .replace("₁'", "") .replace("₁,", "₂") + .replace("²,", "¹") + .replace("²'", "³") .replace("₂'", "₁") .replace("₂,", "₃") + .replace("³,", "²") + .replace("³'", "⁴") .replace("₃'", "₂") + .replace("₃,", "₄") + .replace("₄'", "₃") + .replace("⁴,", "³") .replace(",", "₁") .replace("'", "¹") + regexFound.groupValues[3] }