toSolfa(), first take
This commit is contained in:
parent
f831ec2d1c
commit
694292c0cd
19 changed files with 708 additions and 20 deletions
|
|
@ -52,7 +52,8 @@ fun App() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val musicXML = MusicXML(fileRepository)
|
val musicXML: MusicXML = koinInject()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()) }
|
||||||
}
|
}
|
||||||
|
|
@ -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 = ""
|
||||||
|
)
|
||||||
|
|
@ -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()
|
||||||
)
|
)
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
||||||
|
)
|
||||||
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
@ -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?,
|
||||||
|
)
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
@ -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}")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package mg.dot.feufaro.musicXML
|
||||||
|
|
||||||
|
class SolfaXMLOneNote {
|
||||||
|
var voiceNumber: Int = 0
|
||||||
|
var note: String = ""
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue