Midi generation enhancements

This commit is contained in:
dotmg 2025-11-28 07:02:42 +01:00
parent a35d7c38ff
commit 3867dbc855
6 changed files with 109 additions and 27 deletions

View file

@ -1,5 +1,6 @@
package mg.dot.feufaro.midi package mg.dot.feufaro.midi
import mg.dot.feufaro.solfa.ParseULine
import mg.dot.feufaro.solfa.Transpose import mg.dot.feufaro.solfa.Transpose
@ -10,7 +11,10 @@ data class MidiPitch (
var alter: String = "", var alter: String = "",
var duration: Int = 0, var duration: Int = 0,
var markers: List<String> = listOf(), var markers: List<String> = listOf(),
var tick : Int = 0 var tick : Int = 0,
var metaBytes: String = "",
var metaByteSize: Int = 0,
var metaType : Int = -1
) { ) {
var velocity: Int = 96 var velocity: Int = 96
companion object { companion object {
@ -18,18 +22,25 @@ data class MidiPitch (
var curVoiceNumber: Int = 0 var curVoiceNumber: Int = 0
var nextTick : MutableList<Int> = mutableListOf() var nextTick : MutableList<Int> = mutableListOf()
} }
fun setMeta(type: Int, tickMeta: Int, nb: Int, data0: Int, data1: Int = 0, data2: Int = 0, data3: Int = 0, data4: Int = 0) {
metaBytes = ""+ data0.toChar() + data1.toChar() + data2.toChar() + data3.toChar() + data4.toChar()
tick = tickMeta
metaByteSize = nb
metaType = type
}
fun setNote(note: String, theDuration: Int) { fun setNote(note: String, theDuration: Int) {
val midiPitch = Transpose.transposeToMidi(note, key, "C") val midiPitch = Transpose.transposeToMidi(note, key, "C")
if (midiPitch < 0) { velocity = if (midiPitch < 0) {
velocity = 0 0
} else { } else {
velocity = 99 99
} }
pitch = midiPitch.toString() pitch = midiPitch.toString()
duration = theDuration duration = theDuration
tick = nextTick[voiceNumber] tick = nextTick.getOrNull(voiceNumber) ?: 0
nextTick[voiceNumber] = theDuration + tick nextTick[voiceNumber] = theDuration + tick
voiceNumber = curVoiceNumber voiceNumber = curVoiceNumber
metaType = -1
} }
fun setMarker(theMarkers : MutableList<String>) { fun setMarker(theMarkers : MutableList<String>) {
if (theMarkers.isNotEmpty()) { if (theMarkers.isNotEmpty()) {
@ -46,6 +57,27 @@ data class MidiPitch (
} }
fun initKey(theKey: String) { fun initKey(theKey: String) {
key = theKey key = theKey
val armature = Transpose.toArmature(key)
val tickMeta = nextTick.getOrNull(0) ?: 0
setMeta(0x59, tickMeta, 2, armature)
}
fun initMeasure(theMeasure: String): Boolean {
val numerator = theMeasure.replace(Regex("/.*"), "").toIntOrNull()
val denominator = theMeasure.replace(Regex("^[^/]*/(\\d+).*$"), "$1").toIntOrNull() ?: 4
val data1 = when (denominator) {
1 -> 0
2 -> 1
4 -> 2
8 -> 3
16 -> 4
else -> 5
}
val blankDuration = (4 * (numerator ?: 4) - ParseULine.blankDuration) * 15
if (numerator != null) {
setMeta(0x58, blankDuration, 4, numerator, data1, 60, 20)
return true
}
return false
} }
fun reset() { fun reset() {
tick = 0 tick = 0

View file

@ -14,13 +14,18 @@ class MidiWriterKotlin (private val fileRepository: FileRepository) {
private val noteOn = ShortMessage() private val noteOn = ShortMessage()
private val noteOff = ShortMessage() private val noteOff = ShortMessage()
private val lastPitch : MutableList<Int> = mutableListOf() private val lastPitch : MutableList<Int> = mutableListOf()
private val useChord : Boolean = true
fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long) { fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long) {
var channel: Int = voiceNumber - 1
if (useChord) {
channel = channel / 2
}
var note = note var note = note
if (voiceNumber == 3 || voiceNumber == 4) { if (voiceNumber == 3 || voiceNumber == 4) {
note -= 12 note -= 12
} }
if (lastPitch.size > voiceNumber && lastPitch[voiceNumber] > 0) { if (lastPitch.size > voiceNumber && lastPitch[voiceNumber] > 0) {
noteOff.setMessage(ShortMessage.NOTE_OFF, voiceNumber - 1, lastPitch[voiceNumber], 0) noteOff.setMessage(ShortMessage.NOTE_OFF, channel, lastPitch[voiceNumber], 0)
val n2 = noteOff.clone() as MidiMessage val n2 = noteOff.clone() as MidiMessage
track.add(MidiEvent(n2, tick)) track.add(MidiEvent(n2, tick))
} }
@ -29,7 +34,7 @@ class MidiWriterKotlin (private val fileRepository: FileRepository) {
note = 40 note = 40
velocity = 0 velocity = 0
} }
noteOn.setMessage(ShortMessage.NOTE_ON, voiceNumber - 1, note, velocity) noteOn.setMessage(ShortMessage.NOTE_ON, channel, note, velocity)
val n1: MidiMessage = noteOn.clone() as MidiMessage val n1: MidiMessage = noteOn.clone() as MidiMessage
track.add(MidiEvent(n1, tick)) track.add(MidiEvent(n1, tick))
while(lastPitch.size <= voiceNumber) { while(lastPitch.size <= voiceNumber) {
@ -45,12 +50,22 @@ class MidiWriterKotlin (private val fileRepository: FileRepository) {
out.close() out.close()
} }
} }
fun addMetaMessage(type: Int, tick: Int, nbData: Int, metaByteString: String) {
val byteArray = metaByteString.toByteArray()
val metaMessage = MetaMessage(type, byteArray, nbData)
track.add(MidiEvent(metaMessage, tick.toLong()))
}
fun process(pitches: List<MidiPitch>) { fun process(pitches: List<MidiPitch>) {
val lastTick = 0 val lastTick = 0
nextTick.clear() nextTick.clear()
// addMetaMessage(0x59, 4, 2, 2,0)
tick = 0 tick = 0
pitches.forEach { pitches.forEach {
if (it.metaType > 0) {
addMetaMessage(it.metaType, it.tick, it.metaByteSize, it.metaBytes)
} else if (it.pitch != "") {
addNote(it.voiceNumber, it.pitch.toInt(), 100, it.tick.toLong()) addNote(it.voiceNumber, it.pitch.toInt(), 100, it.tick.toLong())
} }
} }
} }
}

View file

@ -80,7 +80,7 @@ class MusicXML(private val fileRepository: FileRepository) {
} }
} }
suspend fun load() { suspend fun load() {
val xmlContent = fileRepository.readFileContent("assets://31.xml") val xmlContent = fileRepository.readFileContent("assets://ews-89.xml")
val xslContent = fileRepository.readFileContent("assets://timepart.xsl") val xslContent = fileRepository.readFileContent("assets://timepart.xsl")
xmlString = performXsltTransformation(xmlContent, xslContent) xmlString = performXsltTransformation(xmlContent, xslContent)
val solfaXML = SolfaXML() val solfaXML = SolfaXML()
@ -215,7 +215,7 @@ class MusicXML(private val fileRepository: FileRepository) {
init { init {
val parseScope = CoroutineScope(Dispatchers.Default) val parseScope = CoroutineScope(Dispatchers.Default)
parseScope.launch { parseScope.launch {
if (true) if (false)
load() load()
} }
} }

View file

@ -15,6 +15,7 @@ class ParseULine (var line: String, var measure: Int) {
'y' to listOf(".,D:", ".-,:D,", ":,D.", ":-,.D,"), 'y' to listOf(".,D:", ".-,:D,", ":,D.", ":-,.D,"),
't' to listOf("DD", "DD", "DD", "DD") 't' to listOf("DD", "DD", "DD", "DD")
) )
var blankDuration: Int = 0
} }
fun parsed() : String { fun parsed() : String {
@ -28,6 +29,7 @@ class ParseULine (var line: String, var measure: Int) {
"O" -> 24 "O" -> 24
else -> capturedChar.toInt() else -> capturedChar.toInt()
} }
blankDuration = cursorX
line = matchResult.groups[2]!!.value line = matchResult.groups[2]!!.value
parsedString = ":" parsedString = ":"
if (capturedChar == "0") { if (capturedChar == "0") {

View file

@ -166,9 +166,9 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}") println("Erreur parseScope Solfa:150 : ${e.message} iter: ${TimeUnitObject.nbBlock}")
} }
val pitches = pitches.sortedWith( compareBy({it.tick}, {it.voiceNumber})) val pitches = pitches.sortedWith( compareBy({it.tick}, {it.voiceNumber}))
val k = MidiWriterKotlin(fileRepository) val midiWriter = MidiWriterKotlin(fileRepository)
k.process(pitches) midiWriter.process(pitches)
k.save("whawyd3.mid") midiWriter.save("whawyd3.mid")
} }
} }
@ -363,12 +363,51 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
sharedScreenModel.setSongRhythm(meta["r"] ?: "") sharedScreenModel.setSongRhythm(meta["r"] ?: "")
} }
private fun loadN(voiceNumber: Int, line: String, templateComments: MutableList<String>) { private fun loadN(voiceNumber: Int, line: String, templateComments: MutableList<String>) {
val midiPitch = MidiPitch(voiceNumber)
midiPitch.currentVoiceNumber(voiceNumber)
midiPitch.initKey(meta["C"] ?: "C")
var midiDuration = 0 var midiDuration = 0
var nextDuration = 0 var nextDuration = 0
var lastNoteString = "" var lastNoteString = ""
val midiPitch = MidiPitch(voiceNumber)
var nextComment: MutableList<String> = mutableListOf()
fun pushMidi(typeBlock: String = "note") {
if (typeBlock == "measure") {
val z = midiPitch.copy()
if (z.tick != 0) {
val tickMeta = midiPitch.tick
val numerator = midiPitch.metaBytes.toByteArray()[0].toInt()
val denominatorPower = midiPitch.metaBytes.toByteArray()[1].toInt()
val denominator = 1 shl denominatorPower
z.tick = 0
val newNumerator = tickMeta / 15 / denominator
z.metaBytes = "" + newNumerator.toChar() + denominatorPower.toChar() + 60.toChar() + 20.toChar()
pitches.add(z)
}
return;
}
if (midiDuration > 0 && typeBlock == "note") {
midiPitch.setNote(lastNoteString, midiDuration)
pitches.add(midiPitch.copy())
midiPitch.setMarker(nextComment)
} else if (typeBlock == "meta") {
pitches.add(midiPitch.copy())
}
midiPitch.metaBytes = ""
midiPitch.metaType = -1
midiPitch.duration = 0
lastNoteString = ""
midiDuration = 0
nextComment = mutableListOf()
}
midiPitch.currentVoiceNumber(voiceNumber)
if (voiceNumber == 1) {
midiPitch.initKey(meta["C"] ?: "C")
pushMidi("meta")
if (midiPitch.initMeasure(meta["m"] ?: "4/4")) {
pushMidi("measure")
pushMidi("meta")
}
}
var lastIt = ' ' var lastIt = ' '
val newN = POneVoiceNote() val newN = POneVoiceNote()
val lineRepeated = REGEX_REPETITION.replace(line) { matchResult -> val lineRepeated = REGEX_REPETITION.replace(line) { matchResult ->
@ -377,17 +416,6 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
charIterated.repeat(nTimes) charIterated.repeat(nTimes)
} }
var commentNumber = -1 var commentNumber = -1
var nextComment: MutableList<String> = mutableListOf()
fun pushMidi() {
if (midiDuration > 0) {
midiPitch.setNote(lastNoteString, midiDuration)
pitches.add(midiPitch.copy())
midiPitch.setMarker(nextComment)
}
lastNoteString = ""
midiDuration = 0
nextComment = mutableListOf()
}
val lineChar = lineRepeated.toCharArray() val lineChar = lineRepeated.toCharArray()
lineChar.forEach { lineChar.forEach {
when (it) { when (it) {

View file

@ -24,6 +24,7 @@ class Transpose {
val octaveSigns = listOf("", "", "", "", "", "¹", "²", "³", "") val octaveSigns = listOf("", "", "", "", "", "¹", "²", "³", "")
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" )
val keyToArmature = listOf(0, -7, 2, -3, 4, -1, -6, 1, -4, 3, -2, 5 )
fun compareNotes(note1: String, note2: String): Int { fun compareNotes(note1: String, note2: String): Int {
val found1 = REGEX_ONE_NOTE.find(note1) val found1 = REGEX_ONE_NOTE.find(note1)
val found2 = REGEX_ONE_NOTE.find(note2) val found2 = REGEX_ONE_NOTE.find(note2)
@ -46,6 +47,10 @@ class Transpose {
} }
} }
} }
fun toArmature(note: String): Int {
val index = keyToNumber.indexOf(note)
return keyToArmature.getOrNull(index) ?: 0
}
fun transposeMidi(note: String, fromKey: String) : Pair<String, Int> { fun transposeMidi(note: String, fromKey: String) : Pair<String, Int> {
return Pair(note, 4) return Pair(note, 4)
} }