Implements split voices on Solfa ${S:d}d

This commit is contained in:
Hasinjato 2026-08-10 14:22:25 +03:00
parent 84b2094798
commit a883090d0f
4 changed files with 140 additions and 49 deletions

View file

@ -1,6 +1,6 @@
package mg.dot.feufaro.solfa package mg.dot.feufaro.solfa
class PNotes (var note: String, private var markers: MutableList<String> = mutableListOf("")) { class PNotes (var note: String, var noteSplit: MutableList<String> = mutableListOf(), private var markers: MutableList<String> = mutableListOf()) {
fun addMarker(newMarker: String) { fun addMarker(newMarker: String) {
markers.add(newMarker) markers.add(newMarker)
} }
@ -8,7 +8,20 @@ class PNotes (var note: String, private var markers: MutableList<String> = mutab
return markers.contains(marker) return markers.contains(marker)
} }
fun toNote(): Note { fun toNote(): Note {
val newNote = Note(note) val validSplits = noteSplit.filter { it.isNotBlank() }
val finalNoteStr = if (validSplits.isNotEmpty()) {
val formattedSplits = if (validSplits.size > 1) {
validSplits.joinToString("|")
} else {
validSplits.joinToString("")
}
"$note>{$formattedSplits}"
} else {
note
}
val newNote = Note(finalNoteStr)
newNote.setMarkers(markers) newNote.setMarkers(markers)
return newNote return newNote
} }
@ -16,4 +29,12 @@ class PNotes (var note: String, private var markers: MutableList<String> = mutab
fun appendNote(noteSuffix: Char) { fun appendNote(noteSuffix: Char) {
note += noteSuffix note += noteSuffix
} }
fun addSplitNote(splitNote: String) {
noteSplit.add(splitNote)
}
fun appendSplitNote(suffix: Char) {
if (noteSplit.isNotEmpty()) {
noteSplit[noteSplit.lastIndex] += suffix
}
}
} }

View file

@ -16,9 +16,15 @@ class POneVoiceNote {
oneVoiceNote.lastOrNull()?.addMarker(marker.toString()) oneVoiceNote.lastOrNull()?.addMarker(marker.toString())
} }
fun addItem(noteString: String = "") { fun addItem(noteString: String = "") {
val newNote: PNotes = PNotes(noteString, nextMarkers) val newNote: PNotes = PNotes(note = noteString, noteSplit = mutableListOf(), markers = nextMarkers)
oneVoiceNote.add(newNote) oneVoiceNote.add(newNote)
nextNote = "" nextNote = ""
nextMarkers = mutableListOf() nextMarkers = mutableListOf()
} }
fun appendLastSplitNote(splitNote: String) {
oneVoiceNote.lastOrNull()?.addSplitNote(splitNote)
}
fun appendLastSplitNoteSuffix(suffix: Char) {
oneVoiceNote.lastOrNull()?.appendSplitNote(suffix)
}
} }

View file

@ -20,7 +20,6 @@ import java.util.prefs.Preferences
import kotlin.getValue import kotlin.getValue
import kotlin.math.min import kotlin.math.min
//@todo: split voices (ffpm19/ews22) ${S:mfs} in N4:, idem ffpm-212
//@todo: ffpm-164.txt O10:s3:mflswss // alternative notes for stanza 3 only //@todo: ffpm-164.txt O10:s3:mflswss // alternative notes for stanza 3 only
//@todo: ews-47.txt DS/$ + [12][3] //@todo: ews-47.txt DS/$ + [12][3]
//@todo: ffpm-99-2.txt triolet.mid + triolet (m--)-- //@todo: ffpm-99-2.txt triolet.mid + triolet (m--)--
@ -67,8 +66,16 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
val REGEX_STRIP_DC = Regex("\\$\\{D[^:]*:[^\\}]*\\}") val REGEX_STRIP_DC = Regex("\\$\\{D[^:]*:[^\\}]*\\}")
val REGEX_NOTE_TOADD = Regex("([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)>([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)") val REGEX_NOTE_TOADD = Regex("([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)>([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)")
val REGEX_NOTE_TODROP = Regex("([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)<") val REGEX_NOTE_TODROP = Regex("([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)<")
val REGEX_NOTE_SPLIT = Regex("\\$\\{[Ss]:([^\\}]+)\\}([drmfsltDRFSTz][,']*)")
} }
fun parseNoteSplit(input: String): String {
return REGEX_NOTE_SPLIT.replace(input) { matchResult ->
val splitValues = matchResult.groupValues[1].chunked(1).joinToString("")
val mainNote = matchResult.groupValues[2]
"$mainNote<{$splitValues}"
}
}
var nextTIndex: Int = -1 var nextTIndex: Int = -1
var nextNIndex: Int = -1 var nextNIndex: Int = -1
var nextLIndex: Int = -1 var nextLIndex: Int = -1
@ -1806,11 +1813,19 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
private fun preloadN(noteLine: String) { private fun preloadN(noteLine: String) {
val voiceNumber = noteLine.substring(0, 1).toInt() val voiceNumber = noteLine.substring(0, 1).toInt()
val parsedNoteLine = parseNoteSplit(noteLine)
val templateComments = mutableListOf<String>() val templateComments = mutableListOf<String>()
val templateStripped = REGEX_TEMPLATE_COMMENT.replace(templateString) { matchResult -> val templateStripped = REGEX_TEMPLATE_COMMENT.replace(templateString) { matchResult ->
templateComments.add(matchResult.value) templateComments.add(matchResult.value)
"&" "&"
} }
val splitBlocks = mutableListOf<String>()
val noteLineWithoutSplits = parsedNoteLine
.replace(REGEX_TEMPLATE_COMMENT, "")
.replace(Regex("<\\s*\\{[^\\}]*\\}")) { matchResult ->
splitBlocks.add(matchResult.value)
"§"
}
fun insertZToCharArray(input: String): CharArray { fun insertZToCharArray(input: String): CharArray {
val delim = "/:!|" val delim = "/:!|"
val reDelim = "[$delim](?=[$delim])".toRegex() val reDelim = "[$delim](?=[$delim])".toRegex()
@ -1818,9 +1833,9 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
return insertedZ.toCharArray() return insertedZ.toCharArray()
} }
val templateCharArray = insertZToCharArray(templateStripped) val templateCharArray = insertZToCharArray(templateStripped)
val noteLine = noteLine.replace(REGEX_TEMPLATE_COMMENT, "") val cleanNoteLine = noteLineWithoutSplits.replace(REGEX_TEMPLATE_COMMENT, "")
.replace(" ", "") .replace(" ", "")
val lineRepeated = REGEX_REPETITION.replace(noteLine.substring(1)) { matchResult -> val lineRepeated = REGEX_REPETITION.replace(cleanNoteLine.substring(1)) { matchResult ->
val (charIterated, iteration) = matchResult.destructured val (charIterated, iteration) = matchResult.destructured
val nTimes = iteration.toInt() val nTimes = iteration.toInt()
charIterated.repeat(nTimes) charIterated.repeat(nTimes)
@ -1830,6 +1845,10 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
var inAnchor = false var inAnchor = false
var anchorNote = ' ' var anchorNote = ' '
lineRepeated.toCharArray().forEach { lineRepeated.toCharArray().forEach {
if (it == '§') {
noteChar2 += "§"
return@forEach
}
if (inAnchor) { if (inAnchor) {
if (anchor.isEmpty()) { if (anchor.isEmpty()) {
anchorNote = it anchorNote = it
@ -1877,16 +1896,34 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
'd', 'r', 'm', 'f', 's', 'l', 't', 'd', 'r', 'm', 'f', 's', 'l', 't',
-> { -> {
if (firstInLoop) { if (firstInLoop) {
replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd' val nextChar = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd'
if (nextChar == '§') {
result += splitBlocks.removeAt(0)
replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd'
} else {
replacement = nextChar
}
firstInLoop = false firstInLoop = false
} }
if (replacement == '(') { if (replacement == '(') {
result += replacement result += replacement
replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd' val nextChar = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd'
if (nextChar == '§') {
result += splitBlocks.removeAt(0)
replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd'
} else {
replacement = nextChar
}
} }
for (repetition in 0 until 6) { for (repetition in 0 until 6) {
result += replacement result += replacement
replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd' val nextChar = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd'
if (nextChar == '§') {
result += splitBlocks.removeAt(0)
replacement = if (noteCharIterator.hasNext()) noteCharIterator.next() else 'd'
break
}
replacement = nextChar
if (replacement in setOf( if (replacement in setOf(
'd', 'r', 'm', 'f', 's', 'l', 't', 'z', '(', 'd', 'r', 'm', 'f', 's', 'l', 't', 'z', '(',
'D', 'R', 'M', 'F', 'S', 'L', 'T', '-', 'w', 'Z' 'D', 'R', 'M', 'F', 'S', 'L', 'T', '-', 'w', 'Z'
@ -2067,17 +2104,29 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
charIterated.repeat(nTimes) charIterated.repeat(nTimes)
} }
var commentNumber = -1 var commentNumber = -1
var inSplit = false
val lineChar = lineRepeated.toCharArray() val lineChar = lineRepeated.toCharArray()
lineChar.forEach { lineChar.forEach {
when (it) { when (it) {
'|', ':', '!', '/' -> { '|', ':', '!', '/', '&' -> {
if (lastIt == '|' || lastIt == ':' || lastIt == '!' || lastIt == '/') { if (it == '|' && lastIt == '[') {
// le séparateur split
} else if (lastIt == '|' || lastIt == ':' || lastIt == '!' || lastIt == '/') {
} else { } else {
midiDuration += nextDuration midiDuration += nextDuration
nextDuration = 60 nextDuration = 60
} }
} }
// blocs de split <[...]
'<' -> { }
'{' -> {
inSplit = true
}
'}' -> {
inSplit = false
}
'.' -> { '.' -> {
if (lastIt == ';') { if (lastIt == ';') {
pushMidi() pushMidi()
@ -2106,8 +2155,12 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
} }
'\'', ',' -> { '\'', ',' -> {
newN.appendLastNote(it) if(!inSplit ){
lastNoteString += it newN.appendLastNote(it)
lastNoteString += it
} else {
newN.appendLastSplitNoteSuffix(it)
}
} }
'(', '[' -> newN.addNextMarker(it) '(', '[' -> newN.addNextMarker(it)
@ -2135,40 +2188,46 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
if (it == 'T') { if (it == 'T') {
noteString += "a" noteString += "a"
} }
newN.addItem(noteString) if(inSplit) {
when (noteString) { newN.appendLastSplitNote(noteString)
"-" -> { } else {
midiDuration += nextDuration newN.addItem(noteString)
nextDuration = 0 when (noteString) {
} "-" -> {
"z", "@" -> {
if (lastNoteString == "z") {
midiDuration += nextDuration midiDuration += nextDuration
nextDuration = 0 nextDuration = 0
} else {
pushMidi()
//noteString =
} }
lastNoteString = "z"
}
"'", "(", ")", "[", "]", "," -> {} "z", "@" -> {
else -> { if (lastNoteString == "z") {
pushMidi() midiDuration += nextDuration
lastNoteString = noteString nextDuration = 0
} else {
pushMidi()
//noteString =
}
lastNoteString = "z"
}
"'", "(", ")", "{", "}", "," -> {}
else -> {
pushMidi()
lastNoteString = noteString
}
} }
} }
} }
'@' -> { '@' -> {
if (lastNoteString == "z") { if(!inSplit) {
midiDuration += nextDuration if (lastNoteString == "z") {
nextDuration = 0 midiDuration += nextDuration
} else { nextDuration = 0
pushMidi() } else {
pushMidi()
}
lastNoteString = "z"
} }
lastNoteString = "z"
} }
'i', 'a' -> 'i', 'a' ->

View file

@ -471,21 +471,26 @@ fun TimeUnitComposable(
){ ){
val annotatedText = remember(multiLineText, MaterialTheme.colorScheme.primary) { val annotatedText = remember(multiLineText, MaterialTheme.colorScheme.primary) {
buildAnnotatedString { buildAnnotatedString {
multiLineText.split(">").mapIndexed { index, text -> val lines = multiLineText.lines()
if (index %2 == 0) { lines.forEachIndexed { lineIndex, line ->
append(text) if (lineIndex > 0) {
} else { append("\n")
withStyle( }
style = SpanStyle( line.split(">").mapIndexed { index, text ->
fontSize = 10.sp, if (index % 2 == 0) {
baselineShift = BaselineShift.Superscript, append(text)
//fontStyle = FontStyle.Italic, } else {
/*color = MaterialTheme.colorScheme.primary*/ withStyle(
)) { style = SpanStyle(
fontSize = 10.sp,
baselineShift = BaselineShift.Superscript,
//fontStyle = FontStyle.Italic,
/*color = MaterialTheme.colorScheme.primary*/
)) {
append(text+" ") append(text+" ")
} }
}
} }
} }
} }
} }