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
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) {
markers.add(newMarker)
}
@ -8,7 +8,20 @@ class PNotes (var note: String, private var markers: MutableList<String> = mutab
return markers.contains(marker)
}
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)
return newNote
}
@ -16,4 +29,12 @@ class PNotes (var note: String, private var markers: MutableList<String> = mutab
fun appendNote(noteSuffix: Char) {
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())
}
fun addItem(noteString: String = "") {
val newNote: PNotes = PNotes(noteString, nextMarkers)
val newNote: PNotes = PNotes(note = noteString, noteSplit = mutableListOf(), markers = nextMarkers)
oneVoiceNote.add(newNote)
nextNote = ""
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.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: ews-47.txt DS/$ + [12][3]
//@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_NOTE_TOADD = Regex("([drmfsltz][ia]?|[―])([₄₃₂₁¹²³⁴',]*)>([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 nextNIndex: Int = -1
var nextLIndex: Int = -1
@ -1806,11 +1813,19 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
private fun preloadN(noteLine: String) {
val voiceNumber = noteLine.substring(0, 1).toInt()
val parsedNoteLine = parseNoteSplit(noteLine)
val templateComments = mutableListOf<String>()
val templateStripped = REGEX_TEMPLATE_COMMENT.replace(templateString) { matchResult ->
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 {
val delim = "/:!|"
val reDelim = "[$delim](?=[$delim])".toRegex()
@ -1818,9 +1833,9 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
return insertedZ.toCharArray()
}
val templateCharArray = insertZToCharArray(templateStripped)
val noteLine = noteLine.replace(REGEX_TEMPLATE_COMMENT, "")
val cleanNoteLine = noteLineWithoutSplits.replace(REGEX_TEMPLATE_COMMENT, "")
.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 nTimes = iteration.toInt()
charIterated.repeat(nTimes)
@ -1830,6 +1845,10 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
var inAnchor = false
var anchorNote = ' '
lineRepeated.toCharArray().forEach {
if (it == '§') {
noteChar2 += "§"
return@forEach
}
if (inAnchor) {
if (anchor.isEmpty()) {
anchorNote = it
@ -1877,16 +1896,34 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
'd', 'r', 'm', 'f', 's', 'l', 't',
-> {
if (firstInLoop) {
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
}
if (replacement == '(') {
result += replacement
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) {
result += replacement
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(
'd', 'r', 'm', 'f', 's', 'l', 't', '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)
}
var commentNumber = -1
var inSplit = false
val lineChar = lineRepeated.toCharArray()
lineChar.forEach {
when (it) {
'|', ':', '!', '/' -> {
if (lastIt == '|' || lastIt == ':' || lastIt == '!' || lastIt == '/') {
'|', ':', '!', '/', '&' -> {
if (it == '|' && lastIt == '[') {
// le séparateur split
} else if (lastIt == '|' || lastIt == ':' || lastIt == '!' || lastIt == '/') {
} else {
midiDuration += nextDuration
nextDuration = 60
}
}
// blocs de split <[...]
'<' -> { }
'{' -> {
inSplit = true
}
'}' -> {
inSplit = false
}
'.' -> {
if (lastIt == ';') {
pushMidi()
@ -2106,8 +2155,12 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
}
'\'', ',' -> {
if(!inSplit ){
newN.appendLastNote(it)
lastNoteString += it
} else {
newN.appendLastSplitNoteSuffix(it)
}
}
'(', '[' -> newN.addNextMarker(it)
@ -2135,6 +2188,9 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
if (it == 'T') {
noteString += "a"
}
if(inSplit) {
newN.appendLastSplitNote(noteString)
} else {
newN.addItem(noteString)
when (noteString) {
"-" -> {
@ -2153,15 +2209,17 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
lastNoteString = "z"
}
"'", "(", ")", "[", "]", "," -> {}
"'", "(", ")", "{", "}", "," -> {}
else -> {
pushMidi()
lastNoteString = noteString
}
}
}
}
'@' -> {
if(!inSplit) {
if (lastNoteString == "z") {
midiDuration += nextDuration
nextDuration = 0
@ -2170,6 +2228,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
}
lastNoteString = "z"
}
}
'i', 'a' ->
lastNoteString += it

View file

@ -471,8 +471,13 @@ fun TimeUnitComposable(
){
val annotatedText = remember(multiLineText, MaterialTheme.colorScheme.primary) {
buildAnnotatedString {
multiLineText.split(">").mapIndexed { index, text ->
if (index %2 == 0) {
val lines = multiLineText.lines()
lines.forEachIndexed { lineIndex, line ->
if (lineIndex > 0) {
append("\n")
}
line.split(">").mapIndexed { index, text ->
if (index % 2 == 0) {
append(text)
} else {
withStyle(
@ -485,7 +490,7 @@ fun TimeUnitComposable(
append(text+" ")
}
}
}
}
}
}