toSolfa(), first take

This commit is contained in:
dotmg 2025-07-17 19:19:58 +02:00
parent f831ec2d1c
commit 694292c0cd
19 changed files with 708 additions and 20 deletions

View file

@ -52,7 +52,8 @@ fun App() {
} }
} }
val musicXML = MusicXML(fileRepository) val musicXML: MusicXML = koinInject()
} }

View file

@ -5,6 +5,7 @@ import mg.dot.feufaro.CommonFileRepository
import mg.dot.feufaro.FileRepository import mg.dot.feufaro.FileRepository
import mg.dot.feufaro.DisplayConfigManager // Importez DisplayConfigManager import mg.dot.feufaro.DisplayConfigManager // Importez DisplayConfigManager
import mg.dot.feufaro.musicXML.MusicXML import mg.dot.feufaro.musicXML.MusicXML
import mg.dot.feufaro.musicXML.SolfaXML
import mg.dot.feufaro.solfa.Solfa import mg.dot.feufaro.solfa.Solfa
import mg.dot.feufaro.solfa.TimeUnitObject import mg.dot.feufaro.solfa.TimeUnitObject
import mg.dot.feufaro.viewmodel.SolfaScreenModel import mg.dot.feufaro.viewmodel.SolfaScreenModel
@ -19,4 +20,5 @@ val commonModule = module {
single { Solfa(get(), get()) } single { Solfa(get(), get()) }
single { SolfaScreenModel(get()) } single { SolfaScreenModel(get()) }
single { MusicXML(get()) }
} }

View file

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

View file

@ -17,5 +17,5 @@ data class MXDirection (
@Serializable @Serializable
@XmlElement @XmlElement
@XmlSerialName("direction-type", "", "") @XmlSerialName("direction-type", "", "")
var directionType: MXDirectionType? = null var directionType: List<MXDirectionType> = listOf()
) )

View file

@ -11,4 +11,16 @@ data class MXDirectionType (
@XmlElement @XmlElement
@XmlSerialName("words", "", "") @XmlSerialName("words", "", "")
var words: String? = null, var words: String? = null,
@Serializable
@XmlElement
var segno: String? = null,
@Serializable
@XmlElement
var wedge: MXWedge? = null,
@Serializable
@XmlElement
var dynamics: List<MXDynamics> = listOf(),
@Serializable
@XmlElement
var bracket: MXBracket? = null
) )

View file

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

View file

@ -53,5 +53,32 @@ data class MXNote (
@Serializable @Serializable
@XmlElement @XmlElement
@XmlSerialName("accidental", "", "") @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,
) )

View file

@ -1,6 +1,7 @@
package mg.dot.feufaro.musicXML package mg.dot.feufaro.musicXML
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import mg.dot.feufaro.solfa.Transpose
import nl.adaptivity.xmlutil.serialization.XmlElement import nl.adaptivity.xmlutil.serialization.XmlElement
import nl.adaptivity.xmlutil.serialization.XmlSerialName import nl.adaptivity.xmlutil.serialization.XmlSerialName
@ -18,5 +19,31 @@ data class MXPitch (
@Serializable @Serializable
@XmlElement @XmlElement
@XmlSerialName("alter", "", "") @XmlSerialName("alter", "", "")
var alter: Int? = null var alter: Int? = null,
) @Serializable
@XmlElement
var unpitched: String? = null,
) {
companion object {
val stepToSolfa: Map<String, String> = 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)
}
}

View file

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

View file

@ -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<String>? = null
)

View file

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

View file

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

View file

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

View file

@ -7,7 +7,9 @@ import kotlinx.serialization.decodeFromString
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
import mg.dot.feufaro.FileRepository import mg.dot.feufaro.FileRepository
import nl.adaptivity.xmlutil.serialization.XML 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) { class MusicXML(private val fileRepository: FileRepository) {
val module = SerializersModule { val module = SerializersModule {
@ -18,33 +20,193 @@ class MusicXML(private val fileRepository: FileRepository) {
ignoreUnknownChildren() ignoreUnknownChildren()
} }
} }
var dollarMarkers: MutableList<String> = mutableListOf()
var xmlString = "" 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 { try {
val xmlObject = xml.decodeFromString<MXScorePartwise>(xmlString) val xmlObject = xml.decodeFromString<MXScorePartwise>(xmlString)
var keyFifths = 0 var keyFifths = 6
var measureLength = 0
var timeIndex = 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 -> xmlObject.part.forEach { partNode ->
numPart ++
maxTimePointer = 0
var lastTimeCursor = 0
measureLength = 0
partNode.measure.forEach { measureNode -> partNode.measure.forEach { measureNode ->
if (measureNode.backup?.duration != null) { /*if (measureNode.backup?.duration != null) {
val backupDuration = measureNode.backup!!.duration!! val backupDuration = measureNode.backup!!.duration!!
timeIndex -= backupDuration timeIndex -= backupDuration
}*/
timePointer = maxTimePointer
measureLength = max(measureLength, timePointer - lastTimeCursor)
if (lastTimeCursor == 0 && measureLength != 0) {
firstMeasureLength = measureLength
} }
lastTimeCursor = timePointer
if (measureNode.attributes?.key?.fifths != null) { if (measureNode.attributes?.key?.fifths != null) {
keyFifths = measureNode.attributes!!.key!!.fifths!! keyFifths = measureNode.attributes!!.key!!.fifths!!
} }
val keySign = measureNode.attributes?.clef?.sign solfaXML.setKeyFifths(keyFifths)
val nominator = measureNode.attributes?.time?.beats //val keySign = measureNode.attributes?.clef?.sign
val numerator = measureNode.attributes?.time?.beats
val denominator = measureNode.attributes?.time?.beatType val denominator = measureNode.attributes?.time?.beatType
solfaXML.setMeasure(numerator, denominator)
measureNode.note.forEach { noteNode -> measureNode.note.forEach { noteNode ->
val voiceNumber = noteNode.voice // avec un xlst, il faut changer les <backup><duration> en <note><backup-duration>1</...
val duration = noteNode.duration // val stem = noteNode.stem Stem up ou down ou double n'est pas suffisant pour distinguer voix 1 / voix 2
val chord = noteNode.chord if (noteNode.direction != null) {
processDirection(noteNode.direction!!)
return@forEach
}
if (noteNode.backupDuration != null) {
timePointer -= noteNode.backupDuration!!
return@forEach
}
// et <forward><duration> en <note><forward-duration>1</...
if (noteNode.forwardDuration != null) {
timePointer += noteNode.forwardDuration!!
return@forEach
}
if ((noteNode.grace != null) || (noteNode.cue != null)) {
// On va ignorer les grace et les cue notes.
return@forEach
}
val duration = noteNode.duration // 8 === type==half
val voiceNumber = 2 - ((noteNode.voice ?: 1) % 2)
noteNode.lyric.forEach { } // todo number/syllabic/text
val chord = noteNode.chord // !null = accord (same note)
if (chord == null) {
voiceAlter = 0
} else {
timePointer -= lastChordDuration
voiceAlter ++
}
lastChordDuration = duration ?: 0
val beam = if (noteNode.beam?.number == 1) noteNode.beam else null // ligature (begin/continue/end pour ( et )
val timeModification = noteNode.timeModification
if (timeModification != null) {
if (timeModification.actualNotes == 3 && timeModification.normalNotes == 2) {
if (inTupletCount == 0) {
inTupletCount += timeModification.actualNotes
solfaXML.setTuplet(true)
}
inTupletCount--
}
}
val nextNote = when {
noteNode.unpitched != null -> "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) { } catch(e: Exception) {
println("${e.message}") println("${e.message}")
} }

View file

@ -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<String, String> = mutableMapOf()
var tuo: MutableList<SolfaXMLTuo> = mutableListOf()
var currentKey = "C"
var curBeam: MXBeam? = null
var curMarkers: MutableList<String> = 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<String> = 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)
}
}

View file

@ -0,0 +1,6 @@
package mg.dot.feufaro.musicXML
class SolfaXMLOneNote {
var voiceNumber: Int = 0
var note: String = ""
}

View file

@ -0,0 +1,51 @@
package mg.dot.feufaro.musicXML
data class SolfaXMLTuo (
var timeLapse: Int = 0,
var duration: Int = 0,
var markers: MutableList<String> = mutableListOf(),
var oneNote: MutableList<String> = mutableListOf(),
val lyrics: MutableList<String> = 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<String> = this.markers.toMutableList(),
oneNote: MutableList<String> = 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
}
}

View file

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

View file

@ -22,7 +22,7 @@ class Transpose {
companion object { companion object {
val noteToNumber = listOf("d", "di", "r", "ri", "m", "f", "fi", "s", "si", "l", "ta", "t") 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" ) 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) val regexFound = Regex("^([drmfslt][ia]?)([¹²³₁₂₃]?)(.*)").find(note)
if (regexFound == null) { if (regexFound == null) {
return note return note
@ -31,7 +31,7 @@ class Transpose {
val noteNum = noteToNumber.indexOf(noteNaked) val noteNum = noteToNumber.indexOf(noteNaked)
val oldKey = keyToNumber.indexOf(fromKey) val oldKey = keyToNumber.indexOf(fromKey)
val newKey = keyToNumber.indexOf(toKey) val newKey = keyToNumber.indexOf(toKey)
var newNoteNum = noteNum - newKey + oldKey var newNoteNum = noteNum - newKey + oldKey + alter
var suffix = "" var suffix = ""
val noteSize = noteToNumber.size val noteSize = noteToNumber.size
if (newNoteNum < 0) { if (newNoteNum < 0) {
@ -45,14 +45,19 @@ class Transpose {
val newNote = noteToNumber[newNoteNum] + regexFound.groupValues[2] + suffix val newNote = noteToNumber[newNoteNum] + regexFound.groupValues[2] + suffix
return newNote.replace("¹,", "") return newNote.replace("¹,", "")
.replace("¹'", "²") .replace("¹'", "²")
.replace("²,", "¹") .replace("¹,", "")
.replace("²'", "³")
.replace("³,", "²")
.replace("₁'", "") .replace("₁'", "")
.replace("₁,", "") .replace("₁,", "")
.replace("²,", "¹")
.replace("²'", "³")
.replace("₂'", "") .replace("₂'", "")
.replace("₂,", "") .replace("₂,", "")
.replace("³,", "²")
.replace("³'", "")
.replace("₃'", "") .replace("₃'", "")
.replace("₃,", "")
.replace("₄'", "")
.replace("⁴,", "³")
.replace(",", "") .replace(",", "")
.replace("'", "¹") + regexFound.groupValues[3] .replace("'", "¹") + regexFound.groupValues[3]
} }