Convert marker to List to allow multiple markers 1 grid & static marker list & fine supported
This commit is contained in:
parent
2ec69a8b13
commit
295ec5100e
9 changed files with 879 additions and 615 deletions
|
|
@ -291,6 +291,9 @@ actual class FMediaPlayer actual constructor(
|
|||
if (needsClockSync) {
|
||||
clockNano = System.nanoTime()
|
||||
clockTick = currentTickPos
|
||||
idx = events.indexOfFirst { it.tickAbsolute >= currentTickPos }
|
||||
.takeIf { it >= 0 } ?: events.size
|
||||
|
||||
needsClockSync = false
|
||||
}
|
||||
if (isHolding) {
|
||||
|
|
@ -304,7 +307,6 @@ actual class FMediaPlayer actual constructor(
|
|||
if (idx >= events.size) {
|
||||
val dc = getPendingDcStep()
|
||||
if (dc != null) {
|
||||
dc.alreadyDone = true
|
||||
allNotesOff()
|
||||
seekToGrid(dc.targetGrid)
|
||||
clockNano = System.nanoTime(); clockTick = currentTickPos
|
||||
|
|
@ -312,7 +314,10 @@ actual class FMediaPlayer actual constructor(
|
|||
.takeIf { it >= 0 } ?: events.size
|
||||
continue
|
||||
} else {
|
||||
isRunning = false; allNotesOff(); onFinished(); break
|
||||
isRunning = false
|
||||
allNotesOff()
|
||||
onFinished()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -335,6 +340,10 @@ actual class FMediaPlayer actual constructor(
|
|||
yield()
|
||||
}
|
||||
}
|
||||
if (needsClockSync) {
|
||||
continue
|
||||
}
|
||||
|
||||
currentTickPos = ev.tickAbsolute
|
||||
lastEventNano = System.nanoTime()
|
||||
|
||||
|
|
@ -357,7 +366,7 @@ actual class FMediaPlayer actual constructor(
|
|||
0xC0 -> send(0xC0 or ev.channel, ev.data1)
|
||||
0xFF -> { /* tempo ignoré */ }
|
||||
}
|
||||
val currentGrid = (ev.tickAbsolute / resolution).toLong()
|
||||
val currentGrid = (currentTickPos/* ev.tickAbsolute*/ / resolution).toLong()
|
||||
if (boundModel?.activeIndex?.value != currentGrid.toInt()) {
|
||||
boundModel?.updateActiveIndex(currentGrid)
|
||||
}
|
||||
|
|
@ -368,12 +377,15 @@ actual class FMediaPlayer actual constructor(
|
|||
|
||||
actual fun seekToGrid(gridIndex: Int) {
|
||||
currentTickPos = gridIndex.toLong() * resolution
|
||||
lastEventNano = System.nanoTime() // ← recaler l'horloge d'interpolation
|
||||
lastEventNano = System.nanoTime()
|
||||
|
||||
val lastDyn = navigationSteps
|
||||
.filter { it.dynamic != null && it.gridIndex <= gridIndex }
|
||||
.maxByOrNull { it.gridIndex }?.dynamic ?: Dynamic.MF
|
||||
currentDynamicFactor = if (lastDyn == Dynamic.MF) 1.0f else lastDyn.factor
|
||||
applyVoiceStates()
|
||||
|
||||
needsClockSync = true
|
||||
}
|
||||
private fun ticksToMs(ticks: Long) = (ticks * usPerTick / 1000.0).toLong()
|
||||
private fun msToTicks(ms: Long) = (ms * 1000.0 / usPerTick).toLong()
|
||||
|
|
@ -399,210 +411,248 @@ actual class FMediaPlayer actual constructor(
|
|||
val ritRegex = Regex("""rit\.?|ritard\.?|ritenuto\.?|ritardando""", RegexOption.IGNORE_CASE)
|
||||
val rallRegex = Regex("""rall\.?|rallent\.?|rallentando""", RegexOption.IGNORE_CASE)
|
||||
|
||||
metadataList.forEach { (timestamp, gridIndex, template, lastCallerMarker, marker, noteBefore, separat, note) ->
|
||||
val ci = gridIndex ?: 0
|
||||
metadataList.forEach { midiMarker ->
|
||||
val ci = midiMarker.gridIndex ?: 0
|
||||
val dsR = Regex("""D\.?S\.?""");
|
||||
val dcR = Regex("""D\.?C\.?""")
|
||||
val dcR = Regex("""D\.?C\.?""")
|
||||
val dsG = Regex("""D\.?S\.?_GROUP_PART""");
|
||||
val dcG = Regex("""D\.?C\.?_GROUP_PART""")
|
||||
val dcG = Regex("""D\.?C\.?_GROUP_PART""")
|
||||
val finReg = Regex("""(fine|fin|farany|end)""", RegexOption.IGNORE_CASE)
|
||||
val last_grid = sharedScreenModel.getTotalGridCount()
|
||||
val hairPins = sharedScreenModel.getHairPins()
|
||||
val markerList = midiMarker.marker
|
||||
|
||||
when {
|
||||
marker.contains("$") -> lastSegno = ci
|
||||
markerList.forEach { marker ->
|
||||
val mTrim = marker.trim()
|
||||
when {
|
||||
marker.contains("$") -> lastSegno = ci
|
||||
|
||||
marker.contains("\uD834\uDD10") -> {
|
||||
val beat = if(note.contains('•')) 2 else 1
|
||||
navigationSteps.add(NavigationStep(marker, ci, isHold=true, beatInDC=beat))
|
||||
}
|
||||
ritRegex.containsMatchIn(marker) -> {
|
||||
val endGrid = if(lastCallerMarker == 0) last_grid else lastCallerMarker
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = ci,
|
||||
isTempoChange = true,
|
||||
tempoType = "rit",
|
||||
endGridForTempo = endGrid,
|
||||
targetTempoMultiplier = 0.55f
|
||||
)
|
||||
)
|
||||
}
|
||||
rallRegex.containsMatchIn(marker) -> {
|
||||
val endGrid = last_grid
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = ci,
|
||||
isTempoChange = true,
|
||||
tempoType = "rall",
|
||||
endGridForTempo = endGrid,
|
||||
targetTempoMultiplier = 0.50f
|
||||
)
|
||||
)
|
||||
}
|
||||
dsG.matches(marker.trim()) -> {
|
||||
val target = if(lastSegno>0) lastSegno else 0
|
||||
val indx = if((last_grid-ci)<=0) ci-1 else ci
|
||||
navigationSteps.add(NavigationStep(marker, indx, targetGrid=target))
|
||||
}
|
||||
|
||||
dsR.matches(marker.trim()) || marker == "DSFin" -> {
|
||||
navigationSteps.add(NavigationStep(marker, ci,
|
||||
targetGrid = if(lastSegno>0) lastSegno else 0))
|
||||
}
|
||||
|
||||
dcG.matches(marker.trim()) -> {
|
||||
val indx = if((last_grid-ci)<=0) ci-1 else ci
|
||||
navigationSteps.add(NavigationStep(marker, indx, targetGrid=0))
|
||||
println("DC_GROUP créé à $indx → 0")
|
||||
}
|
||||
|
||||
dcR.matches(marker.trim()) && !marker.contains("DC_GROUP_PART") -> {
|
||||
val indx = if((last_grid-ci)<=0) ci else ci
|
||||
navigationSteps.add(NavigationStep(marker, indx, targetGrid=0))
|
||||
println("DC créé à $indx → 0")
|
||||
}
|
||||
|
||||
// ── Farany ────────────────────────────────
|
||||
marker.trim().equals("Farany_GROUP_PART", ignoreCase = true) -> {
|
||||
val hasDcAfter = metadataList.any { (_, gi, _, _, mk, _, _, _) ->
|
||||
(gi ?: 0) > ci &&
|
||||
(mk.contains(Regex("""D\.?C\.?""")) || mk.contains("DC_GROUP_PART"))
|
||||
marker.contains("\uD834\uDD10") -> {
|
||||
val beat = if(midiMarker.note.contains('•')) 2 else 1
|
||||
navigationSteps.add(NavigationStep(marker, ci, isHold=true, beatInDC=beat))
|
||||
}
|
||||
if (hasDcAfter) {
|
||||
ritRegex.containsMatchIn(marker) -> {
|
||||
val endGrid = if(midiMarker.lastCallerMarker == 0) last_grid else midiMarker.lastCallerMarker
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = ci,
|
||||
isTempoChange = true,
|
||||
tempoType = "rit",
|
||||
endGridForTempo = endGrid,
|
||||
targetTempoMultiplier = 0.55f
|
||||
)
|
||||
)
|
||||
}
|
||||
rallRegex.containsMatchIn(marker) -> {
|
||||
val endGrid = last_grid
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = ci,
|
||||
isTempoChange = true,
|
||||
tempoType = "rall",
|
||||
endGridForTempo = endGrid,
|
||||
targetTempoMultiplier = 0.50f
|
||||
)
|
||||
)
|
||||
}
|
||||
dsG.matches(mTrim) -> {
|
||||
val target = if(lastSegno>0) lastSegno else 0
|
||||
val indx = if((last_grid-ci)<=0) ci-1 else ci
|
||||
navigationSteps.add(NavigationStep(marker, indx, targetGrid=target))
|
||||
}
|
||||
|
||||
dsR.matches(mTrim) || marker == "DSFin" -> {
|
||||
navigationSteps.add(NavigationStep(marker, ci,
|
||||
targetGrid = if(lastSegno>0) lastSegno else 0))
|
||||
}
|
||||
|
||||
dcG.matches(mTrim) -> {
|
||||
val indx = if((last_grid-ci)<=0) ci-1 else ci
|
||||
navigationSteps.add(NavigationStep(marker, indx, targetGrid=0))
|
||||
println("DC_GROUP créé à $indx → 0")
|
||||
}
|
||||
|
||||
dcR.matches(mTrim) && !marker.contains("DC_GROUP_PART") -> {
|
||||
val indx = if((last_grid-ci)<=0) ci else ci
|
||||
navigationSteps.add(NavigationStep(marker, indx, targetGrid=0))
|
||||
println("DC créé à $indx → 0")
|
||||
}
|
||||
|
||||
// ── Farany ────────────────────────────────
|
||||
finReg.containsMatchIn(mTrim) -> {
|
||||
val hasDcAfter = metadataList.any { m ->
|
||||
val after = (m.gridIndex ?: 0) > ci
|
||||
|
||||
val hasMarker = m.marker.any { mk ->
|
||||
val value = mk.trim()
|
||||
val ok = dcG.matches(value) || dcR.matches(value)
|
||||
ok
|
||||
}
|
||||
after && hasMarker
|
||||
}
|
||||
if (hasDcAfter) {
|
||||
navigationSteps.add(NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = ci,
|
||||
targetGrid = last_grid,
|
||||
isFarany = true,
|
||||
faranyActive = false
|
||||
))
|
||||
println("Farany mémorisé à $ci")
|
||||
} else {
|
||||
val verse = sharedScreenModel.stanza.value
|
||||
val verses = sharedScreenModel.nbStanzas.value
|
||||
println("Farany ignoré à $ci i $verse == $verses")
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker="${marker}_STZ",
|
||||
gridIndex=ci,
|
||||
isFarany = true,
|
||||
faranyActive = if(verse == verses) false else true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extractDynamic(marker) != null && mTrim != "=" -> {
|
||||
val dyn = extractDynamic(marker) ?: return@forEach
|
||||
lastFactor = if(dyn==Dynamic.MF) 1.0f else dyn.factor
|
||||
navigationSteps.add(NavigationStep(marker=marker, gridIndex=ci, dynamic=dyn))
|
||||
}
|
||||
|
||||
mTrim=="<" || mTrim==">" ||
|
||||
mTrim.contains("cres", ignoreCase=true) ||
|
||||
mTrim.contains("dim", ignoreCase=true) -> {
|
||||
val sym = when {
|
||||
mTrim=="<" -> '<'; mTrim==">" -> '>'
|
||||
mTrim.contains("cres",ignoreCase=true) -> '<'; else -> '>'
|
||||
}
|
||||
val pair = hairPins.find{it.startGrid==ci} ?: return@forEach
|
||||
val explicitAfter = metadataList
|
||||
.filter{ m ->(m.gridIndex ?:0) >= pair.endGrid }
|
||||
.mapNotNull { m ->
|
||||
val dynamic = m.marker.firstNotNullOfOrNull { mk -> extractDynamic(mk) }
|
||||
if (dynamic != null) (m.gridIndex ?: 0) to dynamic else null
|
||||
}
|
||||
.minByOrNull { it.first }
|
||||
?.second
|
||||
|
||||
val from = lastFactor
|
||||
val to = when {
|
||||
explicitAfter!=null && sym=='<' && explicitAfter.factor>from -> explicitAfter.factor
|
||||
explicitAfter!=null && sym=='>' && explicitAfter.factor<from -> explicitAfter.factor
|
||||
else -> nextDynamic(from, sym)
|
||||
}
|
||||
lastFactor = to
|
||||
navigationSteps.add(NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = ci,
|
||||
targetGrid = last_grid,
|
||||
isFarany = true,
|
||||
faranyActive = false
|
||||
marker=mTrim, gridIndex=ci, hairPin=sym,
|
||||
hairPinEndGrid=pair.endGrid,
|
||||
hairPinFromFactor=from, hairPinToFactor=to
|
||||
))
|
||||
println("Farany mémorisé à $ci")
|
||||
} else {
|
||||
println("Farany ignoré à $ci")
|
||||
}
|
||||
}
|
||||
|
||||
extractDynamic(marker) != null && marker.trim() != "=" -> {
|
||||
val dyn = extractDynamic(marker) ?: return@forEach
|
||||
lastFactor = if(dyn==Dynamic.MF) 1.0f else dyn.factor
|
||||
navigationSteps.add(NavigationStep(marker=marker, gridIndex=ci, dynamic=dyn))
|
||||
}
|
||||
|
||||
marker.trim()=="<" || marker.trim()==">" ||
|
||||
marker.trim().contains("cres", ignoreCase=true) ||
|
||||
marker.trim().contains("dim", ignoreCase=true) -> {
|
||||
val sym = when {
|
||||
marker.trim()=="<" -> '<'; marker.trim()==">" -> '>'
|
||||
marker.trim().contains("cres",ignoreCase=true) -> '<'; else -> '>'
|
||||
}
|
||||
val pair = hairPins.find{it.startGrid==ci} ?: return@forEach
|
||||
val explicitAfter = metadataList
|
||||
.filter{(_,gi,_,_,mk,_,_,_)->(gi?:0)>=pair.endGrid && extractDynamic(mk)!=null}
|
||||
.minByOrNull{it.gridIndex?:0}?.let{m->extractDynamic(m.marker)}
|
||||
val from = lastFactor
|
||||
val to = when {
|
||||
explicitAfter!=null && sym=='<' && explicitAfter.factor>from -> explicitAfter.factor
|
||||
explicitAfter!=null && sym=='>' && explicitAfter.factor<from -> explicitAfter.factor
|
||||
else -> nextDynamic(from, sym)
|
||||
}
|
||||
lastFactor = to
|
||||
navigationSteps.add(NavigationStep(
|
||||
marker=marker.trim(), gridIndex=ci, hairPin=sym,
|
||||
hairPinEndGrid=pair.endGrid,
|
||||
hairPinFromFactor=from, hairPinToFactor=to
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startNavigationMonitor(sharedScreenModel: SharedScreenModel) {
|
||||
var lastProcessedIndex = -1
|
||||
var isJumping = false
|
||||
var pendingFin = false
|
||||
navigationJob?.cancel()
|
||||
|
||||
navigationJob = playerScope.launch(Dispatchers.Default) {
|
||||
sharedScreenModel.activeIndex.collect { currentIndex ->
|
||||
if (currentIndex <= lastProcessedIndex) {
|
||||
return@collect
|
||||
}
|
||||
//if (currentIndex <= lastProcessedIndex && lastProcessedIndex != -1) return@collect
|
||||
|
||||
lastProcessedIndex = currentIndex
|
||||
|
||||
if (!isRunning || currentIndex < 0) return@collect
|
||||
|
||||
val step = navigationSteps.find {
|
||||
val currentSteps = navigationSteps.filter {
|
||||
it.gridIndex == currentIndex && !it.alreadyDone
|
||||
}
|
||||
println("pas: ${currentIndex}")
|
||||
if (step != null) {
|
||||
}.sortedBy { step ->
|
||||
when {
|
||||
// ── Farany ────────────────────────────
|
||||
step.isFarany -> {
|
||||
if (step.faranyActive) {
|
||||
println("Farany activé → STOP à grille $currentIndex")
|
||||
step.isHold -> 0
|
||||
step.isTempoChange -> 1
|
||||
step.isFarany -> 1
|
||||
step.dynamic != null || step.hairPin != null -> 1
|
||||
else -> 3
|
||||
}
|
||||
}
|
||||
for(step in currentSteps) {
|
||||
when {
|
||||
// ── Point d'orgue ─────────────────────
|
||||
step.isHold -> {
|
||||
val beatMs = (60_000 / targetBpm).toLong()
|
||||
val holdDuration = if(step.beatInDC==2) beatMs/2 else beatMs*2
|
||||
|
||||
isHolding = true
|
||||
for (ch in 0 until 4) controlChange(ch, 64, 127)
|
||||
delay(holdDuration)
|
||||
for (ch in 0 until 4) controlChange(ch, 64, 0)
|
||||
isHolding = false
|
||||
println("POINT D'ORGUE grille $currentIndex | ${holdDuration}ms")
|
||||
}
|
||||
// Rit. / Rall. , ...
|
||||
step.isTempoChange -> {
|
||||
step.alreadyDone = true
|
||||
isHolding = false
|
||||
isRunning = false
|
||||
playJob?.cancel()
|
||||
allNotesOff()
|
||||
onFinished()
|
||||
} else {
|
||||
println("Farany 1er passage — DC pas encore vu → on continue ✅")
|
||||
val startBpm = currentPlaybackBpm
|
||||
val endBpm = targetBpm * step.targetTempoMultiplier
|
||||
val gridDistance = (step.endGridForTempo - step.gridIndex).coerceAtLeast(1)
|
||||
val beatDurationMs = (60_000L / targetBpm).toLong()
|
||||
val durationMs = (gridDistance * beatDurationMs).coerceIn(1000L, 8000L)
|
||||
|
||||
println("${step.tempoType.uppercase()} : Grille ${step.gridIndex} → ${step.endGridForTempo}")
|
||||
println(" $startBpm BPM → $endBpm BPM sur ${durationMs}ms")
|
||||
|
||||
applyTempoChange(
|
||||
startBpm = startBpm,
|
||||
endBpm = endBpm,
|
||||
durationMs = durationMs,
|
||||
tempoType = step.tempoType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Point d'orgue ─────────────────────
|
||||
step.isHold -> {
|
||||
val beatMs = (60_000 / targetBpm).toLong()
|
||||
val holdDuration = if(step.beatInDC==2) beatMs/2 else beatMs*2
|
||||
println("POINT D'ORGUE grille $currentIndex | ${holdDuration}ms")
|
||||
for (ch in 0 until 4) controlChange(ch, 64, 127)
|
||||
isHolding = true
|
||||
delay(holdDuration)
|
||||
isHolding = false
|
||||
for (ch in 0 until 4) controlChange(ch, 64, 0)
|
||||
step.alreadyDone = true
|
||||
}
|
||||
// Rit. / Rall. , ...
|
||||
step.isTempoChange -> {
|
||||
step.alreadyDone = true
|
||||
val startBpm = currentPlaybackBpm
|
||||
val endBpm = targetBpm * step.targetTempoMultiplier
|
||||
val gridDistance = (step.endGridForTempo - step.gridIndex).coerceAtLeast(1)
|
||||
val beatDurationMs = (60_000L / targetBpm).toLong()
|
||||
val durationMs = (gridDistance * beatDurationMs).coerceIn(1000L, 8000L)
|
||||
// ── Soufflet ──────────────────────────
|
||||
step.hairPin != null -> {
|
||||
step.alreadyDone = true
|
||||
val dist = (step.hairPinEndGrid - step.gridIndex).coerceAtLeast(1)
|
||||
val beatMs = (60_000L / targetBpm).toLong()
|
||||
applyCrescendo(step.hairPinFromFactor, step.hairPinToFactor,
|
||||
(dist * beatMs).coerceIn(200L, 5000L))
|
||||
}
|
||||
|
||||
println("${step.tempoType.uppercase()} : Grille ${step.gridIndex} → ${step.endGridForTempo}")
|
||||
println(" $startBpm BPM → $endBpm BPM sur ${durationMs}ms")
|
||||
|
||||
applyTempoChange(
|
||||
startBpm = startBpm,
|
||||
endBpm = endBpm,
|
||||
durationMs = durationMs,
|
||||
tempoType = step.tempoType
|
||||
)
|
||||
}
|
||||
|
||||
// ── Soufflet ──────────────────────────
|
||||
step.hairPin != null -> {
|
||||
step.alreadyDone = true
|
||||
val dist = (step.hairPinEndGrid - step.gridIndex).coerceAtLeast(1)
|
||||
val beatMs = (60_000L / targetBpm).toLong()
|
||||
applyCrescendo(step.hairPinFromFactor, step.hairPinToFactor,
|
||||
(dist * beatMs).coerceIn(200L, 5000L))
|
||||
}
|
||||
|
||||
// ── Dynamique ─────────────────────────
|
||||
step.dynamic != null -> {
|
||||
step.alreadyDone = true
|
||||
applyDynamic(step.dynamic)
|
||||
}
|
||||
// ── Dynamique ─────────────────────────
|
||||
step.dynamic != null -> {
|
||||
step.alreadyDone = true
|
||||
applyDynamic(step.dynamic)
|
||||
}
|
||||
|
||||
// ── Farany ─────────────────────────
|
||||
step.isFarany -> {
|
||||
println("C Farany ok $step")
|
||||
if (pendingFin || step.faranyActive) {
|
||||
println("Farany activé → STOP à grille $currentIndex")
|
||||
isRunning = false
|
||||
allNotesOff()
|
||||
onFinished()
|
||||
return@collect
|
||||
} else {
|
||||
println("Farany 1er passage — DC pas encore vu")
|
||||
pendingFin = true
|
||||
step.alreadyDone = true
|
||||
continue
|
||||
}
|
||||
}
|
||||
// ── DC / DS ───────────────────────────
|
||||
else -> {
|
||||
step.alreadyDone = true
|
||||
val beatMs = (60_000 / targetBpm).toLong()
|
||||
println("avant de sauter bpm=$targetBpm")
|
||||
|
||||
//lastProcessedIndex = -1
|
||||
for (ch in 0 until 4) controlChange(ch, 64, 127)
|
||||
isHolding = true
|
||||
delay(beatMs)
|
||||
|
|
@ -621,10 +671,9 @@ actual class FMediaPlayer actual constructor(
|
|||
}
|
||||
|
||||
startPlaybackLoop()
|
||||
println("DC/DS → grille ${step.targetGrid}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,27 +7,12 @@ import androidx.compose.foundation.layout.*
|
|||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AddCircleOutline
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowDown
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowUp
|
||||
import androidx.compose.material.icons.filled.RemoveCircleOutline
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.PlainTooltip
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TooltipBox
|
||||
import androidx.compose.material3.TooltipDefaults
|
||||
import androidx.compose.material3.rememberTooltipState
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -52,6 +37,13 @@ data class MarkerGroup(
|
|||
val markers: List<Marker>
|
||||
)
|
||||
|
||||
fun getAllMarker(): List<String> {
|
||||
return markerGroups
|
||||
.flatMap { group -> group.markers }
|
||||
.map { marker -> marker.abbr }
|
||||
.filter { it != "♩=" && it != "Dô dia" }
|
||||
.sortedByDescending { it.length }
|
||||
}
|
||||
private val markerGroups = listOf(
|
||||
MarkerGroup(
|
||||
title = "Nuances",
|
||||
|
|
@ -71,6 +63,8 @@ private val markerGroups = listOf(
|
|||
Marker("dim.", "Diminuendo"),
|
||||
Marker(">", "Decrescendo"),
|
||||
|
||||
Marker("poco", "Un peu (modifie la nuance suivante)"),
|
||||
Marker("organa", "Style ornementé"),
|
||||
Marker("sf", "Accent soudain"),
|
||||
Marker("sfz", "Sforzato"),
|
||||
Marker("fz", "Forzando"),
|
||||
|
|
@ -106,11 +100,11 @@ private val markerGroups = listOf(
|
|||
Marker("string.", "Presser le tempo"),
|
||||
Marker("♩=", "Changement tempo", canEditValue = true, defaultValue = "60"),
|
||||
Marker("allarg.", "Élargir le tempo"),
|
||||
Marker("a tempo", "Retour au tempo initial"),
|
||||
Marker("a_tempo", "Retour au tempo initial"),
|
||||
Marker("Tempo I", "Retour au premier tempo"),
|
||||
Marker("rubato", "Tempo libre"),
|
||||
Marker("meno mosso", "Moins rapide"),
|
||||
Marker("più mosso", "Plus rapide")
|
||||
Marker("meno_mosso", "Moins rapide"),
|
||||
Marker("più_mosso", "Plus rapide")
|
||||
)
|
||||
),
|
||||
|
||||
|
|
@ -118,16 +112,19 @@ private val markerGroups = listOf(
|
|||
title = "Reprises",
|
||||
markers = listOf(
|
||||
Marker("D.C.", "Da Capo"),
|
||||
Marker("D.C. Fin", "Retour au début jusqu'à Fine"),
|
||||
Marker("D.C. al Coda", "Retour au début puis aller à la Coda"),
|
||||
Marker("D.C.Fin", "Retour au début jusqu'à Fine"),
|
||||
Marker("D.C.alCoda", "Retour au début puis aller à la Coda"),
|
||||
Marker("D.S.", "Retour au Segno"),
|
||||
Marker("End", "Fin"),
|
||||
Marker("D.S. Fin", "Retour au Segno jusqu'à Fine"),
|
||||
Marker("D.S.Fin", "Retour au Segno jusqu'à Fine"),
|
||||
Marker("Farany", "Fin"),
|
||||
Marker("$", "Segno"),
|
||||
Marker("Fine", "Fin"),
|
||||
Marker("Fiverenana", "Réfrain"),
|
||||
Marker("Isan\'andininy", "Réfrain"),
|
||||
Marker("Fine", "Fin"),
|
||||
Marker("𝄌", "Coda"),
|
||||
Marker("To Coda", "Aller à la Coda"),
|
||||
Marker("To_Coda", "Aller à la Coda"),
|
||||
Marker("||:", "Début de reprise"),
|
||||
Marker(":||", "Fin de reprise"),
|
||||
Marker("Bis", "Rejouer")
|
||||
|
|
|
|||
|
|
@ -36,6 +36,61 @@ class PTemplate (val template: String, val separatorAfter: String, private val m
|
|||
.replace(Regex("^c:(.*)"), "Do dia $1")
|
||||
}
|
||||
}
|
||||
fun markerToList(): List<String> {
|
||||
if (markers.isEmpty()) {
|
||||
return emptyList()
|
||||
}
|
||||
val mString = markerToString()
|
||||
return parseMarkers(mString)
|
||||
}
|
||||
fun parseMarkers(rawText: String): List<String> {
|
||||
val tokens = mutableListOf<String>()
|
||||
if (rawText.isBlank()) return tokens
|
||||
|
||||
var remaining = rawText.replace(",", " ")
|
||||
.replace(Regex("""\b(DC)\b""", RegexOption.IGNORE_CASE), "D.C.")
|
||||
.replace(Regex("""\b(DS)\b""", RegexOption.IGNORE_CASE), "D.S.")
|
||||
.replace(Regex("""(D\.?C\.?)\s*(Fin)""", RegexOption.IGNORE_CASE), "D.C.Fin")
|
||||
.replace(Regex("""(D\.?S\.?)\s*(Fin)""", RegexOption.IGNORE_CASE), "D.S.Fin")
|
||||
.replace(Regex("""(D\.?C\.?)\s*(al\s*Coda)""", RegexOption.IGNORE_CASE), "D.C.alCoda")
|
||||
.replace(Regex("""(D\.?S\.?)\s*(al\s*Coda)""", RegexOption.IGNORE_CASE), "D.S.alCoda")
|
||||
|
||||
val allStaticMarkers = getAllMarker()
|
||||
val tempoRegex = Regex("""^(♩\s*=\s*\d+)""", RegexOption.IGNORE_CASE)
|
||||
val modulaRegex = Regex("""^(D(?:ô|o)?\s*dia\s*(?:C|Db|D|Eb|E|F|Gb|G|Ab|A|Bb|B))""", RegexOption.IGNORE_CASE)
|
||||
|
||||
// Lexer
|
||||
while (remaining.isNotEmpty()) {
|
||||
remaining = remaining.trimStart()
|
||||
if (remaining.isEmpty()) break
|
||||
|
||||
val tempoMatch = tempoRegex.find(remaining)
|
||||
if (tempoMatch != null) {
|
||||
tokens.add(tempoMatch.value)
|
||||
remaining = remaining.substring(tempoMatch.value.length)
|
||||
continue
|
||||
}
|
||||
|
||||
val modulaMatch = modulaRegex.find(remaining)
|
||||
if (modulaMatch != null) {
|
||||
tokens.add(modulaMatch.value)
|
||||
remaining = remaining.substring(modulaMatch.value.length)
|
||||
continue
|
||||
}
|
||||
|
||||
val matchedMarker = allStaticMarkers.find { remaining.startsWith(it, ignoreCase = true) }
|
||||
if (matchedMarker != null) {
|
||||
tokens.add(matchedMarker)
|
||||
remaining = remaining.substring(matchedMarker.length)
|
||||
} else {
|
||||
val unknownChunk = remaining.takeWhile { !it.isWhitespace() }
|
||||
tokens.add(unknownChunk)
|
||||
remaining = remaining.substring(unknownChunk.length)
|
||||
}
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
fun hasKeyChange(): String {
|
||||
var returnFun = ""
|
||||
markers.map {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package mg.dot.feufaro
|
||||
|
||||
import SharedScreenModel
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
|
|
@ -12,17 +13,16 @@ import androidx.compose.material.icons.Icons
|
|||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.*
|
||||
import androidx.compose.material.icons.sharp.Delete
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.layout.positionInParent
|
||||
import androidx.compose.ui.layout.positionInWindow
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
|
|
@ -48,6 +48,7 @@ fun TUODetailDialog(
|
|||
globalIndex: Int,
|
||||
isEditable: Boolean,
|
||||
canAdd: Boolean,
|
||||
sharedScreenModel: SharedScreenModel,
|
||||
onIndexChanged: (Int) -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
onSave: (TUOEditState) -> Unit
|
||||
|
|
@ -344,61 +345,80 @@ fun TUODetailDialog(
|
|||
)
|
||||
|
||||
}
|
||||
if(!templateFragment.isNullOrBlank() && templateFragment !="-") {
|
||||
|
||||
if (!templateFragment.isNullOrBlank() && templateFragment != "-") {
|
||||
val hasMarker = marker.isNotEmpty()
|
||||
val tooltipText = if (hasMarker) {
|
||||
"Enlever la marqueur"
|
||||
} else {
|
||||
"Ajouter une marqueur"
|
||||
}
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip(
|
||||
containerColor = Color.DarkGray,
|
||||
contentColor = Color.White
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
|
||||
// 1. Bouton "Remove" (affiché uniquement si hasMarker est true)
|
||||
if (hasMarker) {
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip(containerColor = Color.DarkGray, contentColor = Color.White) {
|
||||
Text(text = "Enlever le marqueur", fontSize = MaterialTheme.typography.titleSmall.fontSize)
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState()
|
||||
) {
|
||||
Text(text = tooltipText, fontSize = MaterialTheme.typography.titleSmall.fontSize)
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState()
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(30.dp)
|
||||
.onGloballyPositioned { coordinates ->
|
||||
markerButtonPos = coordinates.positionInParent()
|
||||
.let {
|
||||
IntOffset(
|
||||
it.x.toInt(),
|
||||
it.y.toInt()
|
||||
)
|
||||
}
|
||||
},
|
||||
onClick = {
|
||||
if(hasMarker) {
|
||||
val state = TUOEditState(
|
||||
tuoIndex = globalIndex,
|
||||
notesByVoice = notes.toMap(),
|
||||
originalNotes = originalNotes.toMap(),
|
||||
originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
|
||||
lyricsByStanza = mutableMapOf(),
|
||||
templateFragment = templateFragment,
|
||||
marker = "_",
|
||||
originalSep = initialSep,
|
||||
sep = newSep
|
||||
IconButton(
|
||||
modifier = Modifier.size(25.dp),
|
||||
onClick = {
|
||||
val state = TUOEditState(
|
||||
tuoIndex = globalIndex,
|
||||
notesByVoice = notes.toMap(),
|
||||
originalNotes = originalNotes.toMap(),
|
||||
originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
|
||||
lyricsByStanza = mutableMapOf(),
|
||||
templateFragment = templateFragment,
|
||||
marker = "_", // On remet le marqueur à vide
|
||||
originalSep = initialSep,
|
||||
sep = newSep
|
||||
)
|
||||
onSave(state)
|
||||
}
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Sharp.Delete,
|
||||
tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f),
|
||||
contentDescription = "Enlever"
|
||||
)
|
||||
onSave(state)
|
||||
}
|
||||
showMarkerPopup = true
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = if(hasMarker) Icons.Default.Remove else Icons.Default.Add,
|
||||
tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
TooltipBox(
|
||||
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
|
||||
tooltip = {
|
||||
PlainTooltip(containerColor = Color.DarkGray, contentColor = Color.White) {
|
||||
Text(
|
||||
text = "Ajouter un marqueur",
|
||||
fontSize = MaterialTheme.typography.titleSmall.fontSize
|
||||
)
|
||||
}
|
||||
},
|
||||
state = rememberTooltipState()
|
||||
) {
|
||||
IconButton(
|
||||
modifier = Modifier.size(25.dp)
|
||||
.onGloballyPositioned { coordinates ->
|
||||
markerButtonPos = coordinates.positionInParent().let {
|
||||
IntOffset(it.x.toInt(), it.y.toInt())
|
||||
}
|
||||
},
|
||||
onClick = { showMarkerPopup = true }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Edit,
|
||||
tint = MaterialTheme.colorScheme.secondary.copy(alpha = 1.5f),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
|
@ -595,8 +615,20 @@ fun TUODetailDialog(
|
|||
onDismiss = {
|
||||
showMarkerPopup = false
|
||||
},
|
||||
onMarkerSelected = { marker ->
|
||||
onMarkerSelected = { newMarker ->
|
||||
showMarkerPopup = false
|
||||
val cleanMarker = marker.trim().replace(Regex("""\s+"""), " ")
|
||||
val modulaRegex = Regex("""D(ô|o)?\s*dia\s*(C|Db|D|Eb|E|F|Gb|G|Ab|A|Bb|B)""", RegexOption.IGNORE_CASE)
|
||||
val formattedExisting = modulaRegex.replace(cleanMarker) { match ->
|
||||
"c:${match.groupValues[2]}"
|
||||
}
|
||||
val formattedMod = modulaRegex.replace(newMarker) { match -> "c:${match.groupValues[2]}"}
|
||||
|
||||
val markerToSave = if (TimeUnitObject._hasMarker) {
|
||||
"${formattedExisting.trim()} ${formattedMod.trim()} "
|
||||
} else {
|
||||
"${formattedMod.trim()} "
|
||||
}
|
||||
|
||||
val state = TUOEditState(
|
||||
tuoIndex = globalIndex,
|
||||
|
|
@ -605,7 +637,7 @@ fun TUODetailDialog(
|
|||
originalLyricsByStanza = originalLyricsByStz.toMutableMap(),
|
||||
lyricsByStanza = mutableMapOf(),
|
||||
templateFragment = templateFragment,
|
||||
marker = marker,
|
||||
marker = markerToSave,
|
||||
originalSep = initialSep,
|
||||
sep = newSep
|
||||
)
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import androidx.compose.ui.text.style.BaselineShift
|
|||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.*
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import feufaro.composeapp.generated.resources.Emmentaler
|
||||
import feufaro.composeapp.generated.resources.PTSerif_Bold
|
||||
import feufaro.composeapp.generated.resources.PT_Serif_Bold_Italic
|
||||
|
|
@ -646,29 +647,34 @@ fun LazyVerticalGridTUO(
|
|||
val currentStanza = viewModel.stanza
|
||||
|
||||
val tuoTimestamps by sharedScreenModel.tuoTimestamps.collectAsState()
|
||||
val activeRowIndex by sharedScreenModel.activeIndex.collectAsState()
|
||||
val activeRowIndex by sharedScreenModel.activeIndex.collectAsStateWithLifecycle()
|
||||
|
||||
val measures = tuoList.drop(1).chunked(gridColumnCount)
|
||||
// Avant column affichage:
|
||||
val metadataList = remember(tuoList) {
|
||||
tuoList.drop(1).mapIndexedNotNull { globalIndex, oneTUO ->
|
||||
val markerText = oneTUO.pTemplate.markerToString()
|
||||
val rawMarker = oneTUO.pTemplate.markerToString()
|
||||
val hairPin = oneTUO.hasHairPin()
|
||||
val finalMarker = when {
|
||||
val markerList = sharedScreenModel.parseMarkers(rawMarker)
|
||||
/*val finalMarker = when {
|
||||
hairPin != null && markerText.isBlank() -> hairPin.toString()
|
||||
hairPin != null && markerText.isNotBlank() -> "${markerText.trim()}$hairPin"
|
||||
markerText.isNotBlank() -> markerText
|
||||
else -> null
|
||||
}*/
|
||||
if (hairPin != null) {
|
||||
markerList.add(hairPin.toString())
|
||||
}
|
||||
if (finalMarker != null) {
|
||||
|
||||
if (markerList.isNotEmpty()) {
|
||||
val myTimestamp = sharedScreenModel.tuoTimestamps.value.getOrElse(globalIndex) { 0L }
|
||||
// println("MetaData[$globalIndex] marker='$finalMarker' hairpin=$hairPin")
|
||||
// println("MetaData[$globalIndex] marker='${markerList.joinToString(", ")}' hairpin=$hairPin")
|
||||
MidiMarkers(
|
||||
myTimestamp,
|
||||
globalIndex,
|
||||
oneTUO.pTemplate.template,
|
||||
oneTUO.pTemplate.lastCalledMarker,
|
||||
finalMarker,
|
||||
markerList,
|
||||
oneTUO.prevTUO?.pTemplate?.template ?: "",
|
||||
oneTUO.sep0,
|
||||
oneTUO.tuNotes.getOrNull(1).toString()
|
||||
|
|
@ -697,7 +703,6 @@ fun LazyVerticalGridTUO(
|
|||
var emmentaler = FontFamily(Font(Res.font.Emmentaler))
|
||||
var ptSerifBoldItalic = FontFamily(Font(Res.font.PT_Serif_Bold_Italic))
|
||||
var ptSerif = FontFamily(Font(Res.font.PTSerif_Bold))
|
||||
var markerFontFamily: FontFamily = FontFamily.Default
|
||||
|
||||
val textMeasurer = rememberTextMeasurer()
|
||||
val containerWidthDp = gridWidthDp / gridColumnCount
|
||||
|
|
@ -764,18 +769,17 @@ fun LazyVerticalGridTUO(
|
|||
if (TimeUnitObject._hasMarker) {
|
||||
val lineHeight = 20.sp
|
||||
val density = LocalDensity.current
|
||||
val lineHeightDp : Dp = with(density) {
|
||||
val lineHeightDp: Dp = with(density) {
|
||||
lineHeight.toDp()
|
||||
}
|
||||
var markerFontSize: Float = MaterialTheme.typography.titleMedium.fontSize.value
|
||||
var fontStyle = FontStyle.Normal
|
||||
var fontWeight = FontWeight.Normal
|
||||
val hairPinSymbol = tuo.hasHairPin()
|
||||
val yHeight = with(density) { lineHeightDp.toPx()}
|
||||
val yHeight = with(density) { lineHeightDp.toPx() }
|
||||
|
||||
if (tuo.isTriolet()) {
|
||||
Canvas(modifier = Modifier.fillMaxSize()) {
|
||||
val arcWidth = with(density) { size.width * 0.75f}
|
||||
val arcWidth = with(density) { size.width * 0.75f }
|
||||
drawArc(
|
||||
color = FEUFAROO_TRIOLET_COLOR,
|
||||
startAngle = 200f,
|
||||
|
|
@ -792,26 +796,26 @@ fun LazyVerticalGridTUO(
|
|||
// println("LastHairpin: ${TimeUnitObject.lastHairPinSymbol} ${TimeUnitObject.lastHairPinStart}")
|
||||
val hairPinStart = TimeUnitObject.lastHairPinStart
|
||||
val lastHairPinSymbol = TimeUnitObject.lastHairPinSymbol
|
||||
val hairPinStartLine: Int = (hairPinStart - 1) / gridColumnCount
|
||||
val hairPinStartLine: Int = (hairPinStart - 1) / gridColumnCount
|
||||
val hairPinEndLine: Int = (tuo.numBlock - 1) / gridColumnCount
|
||||
// if (hairPinStartLine == hairPinEndLine) {
|
||||
Canvas(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
val xStart = if (lastHairPinSymbol == '>') -size.width * (tuo.numBlock - hairPinStart) else size.width/2
|
||||
val xEnd = if (lastHairPinSymbol == '>') size.width/2 else -size.width * (tuo.numBlock - hairPinStart)
|
||||
Canvas(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
val xStart = if (lastHairPinSymbol == '>') -size.width * (tuo.numBlock - hairPinStart) else size.width / 2
|
||||
val xEnd = if (lastHairPinSymbol == '>') size.width / 2 else -size.width * (tuo.numBlock - hairPinStart)
|
||||
drawLine(
|
||||
Color.DarkGray,
|
||||
start = Offset(x=xStart, y=0f),
|
||||
end = Offset(xEnd, yHeight/2)
|
||||
start = Offset(x = xStart, y = 0f),
|
||||
end = Offset(xEnd, yHeight / 2)
|
||||
)
|
||||
drawLine(
|
||||
Color.DarkGray,
|
||||
start = Offset(xStart, yHeight),
|
||||
end = Offset(xEnd, yHeight/2)
|
||||
)
|
||||
}
|
||||
TimeUnitObject.endHairPin()
|
||||
drawLine(
|
||||
Color.DarkGray,
|
||||
start = Offset(xStart, yHeight),
|
||||
end = Offset(xEnd, yHeight / 2)
|
||||
)
|
||||
}
|
||||
TimeUnitObject.endHairPin()
|
||||
// }
|
||||
|
||||
}
|
||||
|
|
@ -821,36 +825,55 @@ fun LazyVerticalGridTUO(
|
|||
// @todo pTemplate.markerToString retourne les marqueurs comme une seule chaîne
|
||||
// problème si template = $QD:,-$QD
|
||||
tuo.pTemplate.resetCalledMarker()
|
||||
val text = tuo.pTemplate.markerToString()
|
||||
val musicForPtSerifBIRegex = Regex("""^(Largh\.|Grave|Largo|Lento|Adagio|And\.|Andantino|Mod\.|Moderato|Alleg\.|All\.|Viv\.|Vivacissimo|Presto|Prestiss\.|accel\.|rit\.|rall\.|riten\.|string\.|allarg\.|a tempo|Tempo I|rubato|meno mosso|più mosso|cres\.|<|decresc\.|dim\.|>|rfz|fp|pf|sub\.p|sub\.f)""", RegexOption.IGNORE_CASE)
|
||||
val musicForEmmentRegex = Regex("""\b(ppp|pp|mp|mf|fff|ff|p|f|sfz|sf|fz|rfz|fp|pf)\b""", RegexOption.IGNORE_CASE)
|
||||
|
||||
when {
|
||||
musicForPtSerifBIRegex.containsMatchIn(text) -> {
|
||||
markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value
|
||||
markerFontFamily = ptSerifBoldItalic
|
||||
}
|
||||
musicForEmmentRegex.containsMatchIn(text) -> {
|
||||
markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value+11
|
||||
markerFontFamily = emmentaler
|
||||
}
|
||||
else -> {
|
||||
markerFontFamily = ptSerif
|
||||
val musicForPtSerifBIRegex = Regex("""^(Largh\.|Grave|Largo|Lento|Adagio|And\.|Andantino|Mod\.|Moderato|Alleg\.|All\.|Viv\.|Vivacissimo|Presto|Prestiss\.|accel\.|rit\.|rall\.|riten\.|string\.|allarg\.|a tempo|Tempo I|rubato|meno mosso|più mosso|cres\.|<|decresc\.|dim\.|>|rfz|fp|pf|sub\.p|sub\.f)""",RegexOption.IGNORE_CASE)
|
||||
val musicForEmmentRegex = Regex("""^(ppp|pp|mp|mf|fff|ff|p|f|sfz|sf|fz|rfz|fp|pf)\b""",RegexOption.IGNORE_CASE)
|
||||
|
||||
val markerList = tuo.pTemplate.markerToList()
|
||||
Row(
|
||||
modifier = Modifier.wrapContentSize(unbounded = true, align = Alignment.CenterStart),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
markerList.forEach { marker ->
|
||||
Column {
|
||||
var markerFontFamily: FontFamily = FontFamily.Default
|
||||
var markerFontSize: Float = MaterialTheme.typography.titleMedium.fontSize.value
|
||||
|
||||
when {
|
||||
musicForPtSerifBIRegex.matches(marker) -> {
|
||||
markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value
|
||||
markerFontFamily = ptSerifBoldItalic
|
||||
}
|
||||
|
||||
musicForEmmentRegex.matches(marker) -> {
|
||||
markerFontSize = MaterialTheme.typography.titleMedium.fontSize.value + 11
|
||||
markerFontFamily = emmentaler
|
||||
}
|
||||
|
||||
else -> {
|
||||
markerFontFamily = ptSerif
|
||||
}
|
||||
}
|
||||
Text(
|
||||
text = marker,
|
||||
modifier = Modifier
|
||||
.wrapContentSize(unbounded = true, align = Alignment.CenterStart,),
|
||||
softWrap = false,
|
||||
maxLines = 1,
|
||||
fontStyle = fontStyle,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSecondary,
|
||||
fontSize = markerFontSize.sp,
|
||||
fontFamily = markerFontFamily
|
||||
)
|
||||
)
|
||||
if (marker != markerList.last()) {
|
||||
Spacer(modifier = Modifier.width(2.dp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Text(text = text,
|
||||
modifier = Modifier
|
||||
.wrapContentSize(unbounded = true, align = Alignment.CenterStart,),
|
||||
softWrap = false,
|
||||
maxLines = 1,
|
||||
fontStyle = fontStyle,
|
||||
fontWeight = FontWeight.Bold,
|
||||
style = MaterialTheme.typography.titleMedium.copy(
|
||||
color = MaterialTheme.colorScheme.onSecondary,
|
||||
fontSize = markerFontSize.sp,
|
||||
fontFamily = markerFontFamily
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1053,6 +1076,7 @@ fun LazyVerticalGridTUO(
|
|||
globalIndex = globalIndex,
|
||||
isEditable = canEdit,
|
||||
canAdd = canAdd,
|
||||
sharedScreenModel = sharedScreenModel,
|
||||
onDismiss = {
|
||||
showAddDialog = false
|
||||
showDetailDialog = false
|
||||
|
|
|
|||
|
|
@ -48,9 +48,6 @@ fun Settings(
|
|||
) {
|
||||
var isDynamicEnabled by remember { mutableStateOf(Dynamic.isGloballyEnabled) }
|
||||
var refreshTrigger by remember { mutableStateOf(0) }
|
||||
var expandedGeneral by remember { mutableStateOf(true) }
|
||||
var expandedPrint by remember { mutableStateOf(false) }
|
||||
var expandedAudio by remember { mutableStateOf(false) }
|
||||
val isFullScreenEnabled by sharedScreenModel.isFullScreen.collectAsState()
|
||||
val platform = getPlatform()
|
||||
val isAndroid = platform.name.startsWith("Android")
|
||||
|
|
@ -72,6 +69,16 @@ fun Settings(
|
|||
|
||||
var emmentaler = FontFamily(Font(Res.font.Emmentaler))
|
||||
val player = sharedScreenModel.mediaPlayer
|
||||
var expandedAudio by remember { mutableStateOf(player?.getCurrentPosition() != 0L) }
|
||||
var expandedGeneral by remember { mutableStateOf(true) }
|
||||
var expandedPrint by remember { mutableStateOf(false) }
|
||||
|
||||
fun openExclusive(target: String) {
|
||||
expandedGeneral = (target == "General")
|
||||
expandedPrint = (target == "Print")
|
||||
expandedAudio = (target == "Audio")
|
||||
}
|
||||
|
||||
val instruments = remember { player?.getAvalaibleInstruments() ?: emptyList() }
|
||||
var globalInstrumentProgram by remember {
|
||||
mutableStateOf(player?.getVoiceInstrument() ?: 1)
|
||||
|
|
@ -117,7 +124,9 @@ fun Settings(
|
|||
SettingsAccordion(
|
||||
title = "Paramètres Général",
|
||||
isExpanded = expandedGeneral,
|
||||
onToggle = { expandedGeneral = !expandedGeneral }
|
||||
onToggle = {
|
||||
openExclusive("General")
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
|
|
@ -327,7 +336,9 @@ fun Settings(
|
|||
SettingsAccordion(
|
||||
title = "Paramètres Impression",
|
||||
isExpanded = expandedPrint,
|
||||
onToggle = { expandedPrint = !expandedPrint }
|
||||
onToggle = {
|
||||
openExclusive("Print")
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp),
|
||||
|
|
@ -346,7 +357,9 @@ fun Settings(
|
|||
SettingsAccordion(
|
||||
title = "Paramètres Audio & Nuances",
|
||||
isExpanded = expandedAudio,
|
||||
onToggle = { expandedAudio = !expandedAudio }
|
||||
onToggle = {
|
||||
openExclusive("Audio")
|
||||
}
|
||||
) {
|
||||
Column(modifier = Modifier.padding(top = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ data class MidiMarkers(
|
|||
val gridIndex: Int? = 0,
|
||||
val template: String,
|
||||
val lastCallerMarker: Int,
|
||||
val marker: String,
|
||||
val marker: List<String>,
|
||||
val noteBefore: String,
|
||||
val separat: String,
|
||||
val note: String
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import mg.dot.feufaro.data.getCombinedList
|
|||
import mg.dot.feufaro.solfa.TimeUnitObject
|
||||
import mg.dot.feufaro.midi.FMediaPlayer
|
||||
import mg.dot.feufaro.solfa.TUOEditState
|
||||
import mg.dot.feufaro.solfa.getAllMarker
|
||||
import mg.dot.feufaro.viewmodel.MidiMarkers
|
||||
import java.io.File
|
||||
|
||||
|
|
@ -430,120 +431,170 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
|||
fun getTotalGridCount(): Int {
|
||||
return _tuoList.value.drop(1).size - 1
|
||||
}
|
||||
|
||||
fun parseMarkers(rawText: String): MutableList<String> {
|
||||
val tokens = mutableListOf<String>()
|
||||
if (rawText.isBlank()) return tokens
|
||||
|
||||
var remaining = rawText.replace(",", " ")
|
||||
.replace(Regex("""\b(DC)\b""", RegexOption.IGNORE_CASE), "D.C.")
|
||||
.replace(Regex("""\b(DS)\b""", RegexOption.IGNORE_CASE), "D.S.")
|
||||
.replace(Regex("""(D\.?C\.?)\s*(Fin)""", RegexOption.IGNORE_CASE), "D.C.Fin")
|
||||
.replace(Regex("""(D\.?S\.?)\s*(Fin)""", RegexOption.IGNORE_CASE), "D.S.Fin")
|
||||
.replace(Regex("""(D\.?C\.?)\s*(al\s*Coda)""", RegexOption.IGNORE_CASE), "D.C.alCoda")
|
||||
.replace(Regex("""(D\.?S\.?)\s*(al\s*Coda)""", RegexOption.IGNORE_CASE), "D.S.alCoda")
|
||||
|
||||
val allStaticMarkers = getAllMarker()
|
||||
|
||||
val tempoRegex = Regex("""^(♩\s*=\s*\d+)""", RegexOption.IGNORE_CASE)
|
||||
val modulaRegex = Regex("""^(D(?:ô|o)?\s*dia\s*(?:C|Db|D|Eb|E|F|Gb|G|Ab|A|Bb|B))""", RegexOption.IGNORE_CASE)
|
||||
|
||||
// Lexer
|
||||
while (remaining.isNotEmpty()) {
|
||||
remaining = remaining.trimStart()
|
||||
if (remaining.isEmpty()) break
|
||||
|
||||
val tempoMatch = tempoRegex.find(remaining)
|
||||
if (tempoMatch != null) {
|
||||
tokens.add(tempoMatch.value)
|
||||
remaining = remaining.substring(tempoMatch.value.length)
|
||||
continue
|
||||
}
|
||||
|
||||
val modulaMatch = modulaRegex.find(remaining)
|
||||
if (modulaMatch != null) {
|
||||
tokens.add(modulaMatch.value)
|
||||
remaining = remaining.substring(modulaMatch.value.length)
|
||||
continue
|
||||
}
|
||||
|
||||
val matchedMarker = allStaticMarkers.find { remaining.startsWith(it, ignoreCase = true) }
|
||||
if (matchedMarker != null) {
|
||||
tokens.add(matchedMarker)
|
||||
remaining = remaining.substring(matchedMarker.length)
|
||||
} else {
|
||||
val unknownChunk = remaining.takeWhile { !it.isWhitespace() }
|
||||
tokens.add(unknownChunk)
|
||||
remaining = remaining.substring(unknownChunk.length)
|
||||
}
|
||||
}
|
||||
|
||||
// println("Sortie: ${tokens.joinToString(" | ")}")
|
||||
return tokens
|
||||
}
|
||||
|
||||
fun updateAndFinalizeMidiData(rawList: List<MidiMarkers>) {
|
||||
// val timestamps = _tuoTimestamps.value
|
||||
val tuos = _tuoList.value.drop(1)
|
||||
|
||||
val finalizedList = rawList.map { marker ->
|
||||
var markerText = marker.marker
|
||||
val index = marker.gridIndex ?: 0 // On utilise l'index passé par l'UI
|
||||
val isNearEnd = index >= (tuos.size - 2)
|
||||
val originalMarker = marker.marker
|
||||
var currentMarker = marker
|
||||
|
||||
val isDC = markerText.contains(Regex("""D\.?C\.?"""))
|
||||
val isDS = markerText.contains(Regex("""D\.?S\.?"""))
|
||||
val isFarany = markerText.trim().contains(Regex("""^(fine|fin|farany|end)$""", RegexOption.IGNORE_CASE))
|
||||
val isRit = Regex("""rit\.?|ritard\.?|ritenuto\.?|ritardando""", RegexOption.IGNORE_CASE)
|
||||
var newGridIndex = marker.gridIndex ?: 0
|
||||
val newMarkerList = mutableListOf<String>()
|
||||
|
||||
var resultMarker: MidiMarkers = marker
|
||||
if(isFarany) {
|
||||
var forwardIndex = index
|
||||
while (forwardIndex < tuos.size) {
|
||||
val sep = tuos.getOrNull(forwardIndex)?.sep0 ?: ""
|
||||
if (sep == "/") {
|
||||
resultMarker = marker.copy(
|
||||
gridIndex = forwardIndex - 1,
|
||||
marker = "Farany_GROUP_PART"
|
||||
)
|
||||
println("Farany finalisé : grille $index → ${forwardIndex - 1}")
|
||||
break
|
||||
}
|
||||
forwardIndex++
|
||||
}
|
||||
} else if (isDC) {
|
||||
var forwardIndex = index
|
||||
while (forwardIndex < tuos.size) {
|
||||
val currentTuo = tuos.getOrNull(forwardIndex)
|
||||
val currentNote = currentTuo?.tuNotes?.getOrNull(1)?.toString() ?: ""
|
||||
val currentSep = currentTuo?.sep0 ?: ""
|
||||
originalMarker.forEach { markerText->
|
||||
val index = marker.gridIndex ?: 0 // On utilise l'index passé par l'UI
|
||||
val isNearEnd = index >= (tuos.size - 2)
|
||||
// println("MARKERS:> $markerText\n")
|
||||
|
||||
// println("Je suis sur $forwardIndex note $currentNote Sep $currentSep \t condition: ${(currentNote == "―")} || ${(currentSep == "/")}")
|
||||
// Tant que fin de mesure
|
||||
if (currentSep == "/") {
|
||||
resultMarker = marker.copy(
|
||||
gridIndex = forwardIndex-1,
|
||||
marker = "${markerText.trim()}_GROUP_PART"
|
||||
)
|
||||
break
|
||||
}
|
||||
forwardIndex++
|
||||
}
|
||||
val isDC = markerText.contains(Regex("""D\.?C\.?"""))
|
||||
val isDS = markerText.contains(Regex("""D\.?S\.?"""))
|
||||
val isFarany = markerText.trim().contains(Regex("""^(fine|fin|farany|end)$""", RegexOption.IGNORE_CASE))
|
||||
val isRit = Regex("""rit\.?|ritard\.?|ritenuto\.?|ritardando""", RegexOption.IGNORE_CASE)
|
||||
val isRall = Regex("""rall\.?|rallent\.?|rallentando""", RegexOption.IGNORE_CASE)
|
||||
|
||||
} else if (isDS && isNearEnd) {
|
||||
val currentNote = tuos.getOrNull(index)?.tuNotes?.getOrNull(1)?.toString() ?: ""
|
||||
val nextNote = tuos.getOrNull(index + 1)?.tuNotes?.getOrNull(1)?.toString() ?: ""
|
||||
|
||||
// Cas où le marker et la note suivante sont vides
|
||||
val isEmptySituation = currentNote.trim().isEmpty() && nextNote.trim().isEmpty()
|
||||
|
||||
if (isEmptySituation) {
|
||||
// vers arrière une note non vide
|
||||
var backwardIndex = index - 1
|
||||
while (backwardIndex >= 0) {
|
||||
val note = tuos.getOrNull(backwardIndex)?.tuNotes?.getOrNull(1)?.toString() ?: ""
|
||||
if (note.trim().isNotEmpty()) {
|
||||
val cleanText = if (markerText.trim() == "DSFin") "DS" else markerText.trim()
|
||||
|
||||
resultMarker = marker.copy(
|
||||
gridIndex = backwardIndex,
|
||||
marker = "${cleanText}_GROUP_PART"
|
||||
)
|
||||
break
|
||||
}
|
||||
backwardIndex--
|
||||
}
|
||||
} else {
|
||||
// vers avant le séparateur "/"
|
||||
if (isFarany) {
|
||||
var forwardIndex = index
|
||||
while (forwardIndex < tuos.size) {
|
||||
val sep = tuos.getOrNull(forwardIndex)?.sep0 ?: ""
|
||||
if (sep == "/") {
|
||||
val cleanText = if (markerText.trim() == "DSFin") "DS" else markerText.trim()
|
||||
|
||||
resultMarker = marker.copy(
|
||||
gridIndex = forwardIndex,
|
||||
marker = "${cleanText}_GROUP_PART"
|
||||
)
|
||||
if (tuos.getOrNull(forwardIndex)?.sep0 == "/") {
|
||||
newGridIndex = forwardIndex - 1
|
||||
newMarkerList.add("Farany_GROUP_PART")
|
||||
break
|
||||
}
|
||||
forwardIndex++
|
||||
}
|
||||
}
|
||||
} else if(isRit.containsMatchIn(markerText)) {
|
||||
var forwardIndex = index + 1
|
||||
var foundSeparator = false
|
||||
|
||||
while (forwardIndex < tuos.size) {
|
||||
val sep = tuos.getOrNull(forwardIndex)?.sep0 ?: ""
|
||||
if (sep == "/") {
|
||||
resultMarker = marker.copy(
|
||||
gridIndex = index,
|
||||
lastCallerMarker = forwardIndex - 1,
|
||||
marker = "Ritenuto"
|
||||
)
|
||||
println("Rit finalisé : grille $index → ${forwardIndex - 1}")
|
||||
foundSeparator = true
|
||||
break
|
||||
println("Farany finalisé : grille $index → ${forwardIndex - 1}")
|
||||
} else if (isDC) {
|
||||
var found = false
|
||||
var forwardIndex = index
|
||||
while (forwardIndex < tuos.size) {
|
||||
if (tuos.getOrNull(forwardIndex)?.sep0 == "/") {
|
||||
newGridIndex = forwardIndex - 1
|
||||
newMarkerList.add("${markerText.trim()}_GROUP_PART")
|
||||
println("DC gp finalisé grille $newGridIndex")
|
||||
found = true
|
||||
break
|
||||
}
|
||||
forwardIndex++
|
||||
}
|
||||
forwardIndex++
|
||||
if (!found) {
|
||||
newMarkerList.add(markerText)
|
||||
}
|
||||
} else if (isDS && isNearEnd) {
|
||||
val currentNote = tuos.getOrNull(index)?.tuNotes?.getOrNull(1)?.toString() ?: ""
|
||||
val nextNote = tuos.getOrNull(index + 1)?.tuNotes?.getOrNull(1)?.toString() ?: ""
|
||||
|
||||
if (currentNote.isBlank() && nextNote.isBlank()) {
|
||||
var backwardIndex = index - 1
|
||||
while (backwardIndex >= 0) {
|
||||
if (tuos.getOrNull(backwardIndex)?.tuNotes?.getOrNull(1)?.toString()?.isNotBlank() == true) {
|
||||
newGridIndex = backwardIndex
|
||||
newMarkerList.add("${if (markerText.trim() == "DSFin") "DS" else markerText.trim()}_GROUP_PART")
|
||||
println("DS finalisé grille $newGridIndex")
|
||||
break
|
||||
}
|
||||
backwardIndex--
|
||||
}
|
||||
} else {
|
||||
var forwardIndex = index
|
||||
while (forwardIndex < tuos.size) {
|
||||
if (tuos.getOrNull(forwardIndex)?.sep0 == "/") {
|
||||
newGridIndex = forwardIndex
|
||||
newMarkerList.add("${if (markerText.trim() == "DSFin") "DS" else markerText.trim()}_GROUP_PART")
|
||||
println("DS gp finalisé grille $newGridIndex")
|
||||
break
|
||||
}
|
||||
forwardIndex++
|
||||
}
|
||||
}
|
||||
} else if (isRit.containsMatchIn(markerText)) {
|
||||
var forwardIndex = index + 1
|
||||
while (forwardIndex < tuos.size) {
|
||||
if (tuos.getOrNull(forwardIndex)?.sep0 == "/") {
|
||||
currentMarker = currentMarker.copy(lastCallerMarker = forwardIndex - 1)
|
||||
newMarkerList.add("Ritenuto")
|
||||
break
|
||||
}
|
||||
forwardIndex++
|
||||
}
|
||||
println("Rit finalisé : grille $index → ${forwardIndex - 1}")
|
||||
|
||||
} else if (isRall.containsMatchIn(markerText)) {
|
||||
var forwardIndex = index + 1
|
||||
while (forwardIndex < tuos.size) {
|
||||
if (tuos.getOrNull(forwardIndex)?.sep0 == "/") {
|
||||
currentMarker = currentMarker.copy(lastCallerMarker = forwardIndex - 1)
|
||||
newMarkerList.add("Rallentando")
|
||||
break
|
||||
}
|
||||
forwardIndex++
|
||||
}
|
||||
println("Rall finalisé : grille $index → ${forwardIndex - 1}")
|
||||
|
||||
} else {
|
||||
newMarkerList.add(markerText)
|
||||
}
|
||||
} else {
|
||||
marker
|
||||
}
|
||||
resultMarker
|
||||
currentMarker.copy(
|
||||
gridIndex = newGridIndex,
|
||||
marker = newMarkerList
|
||||
)
|
||||
}
|
||||
|
||||
_midiMarkersList.value = finalizedList
|
||||
println("Markers finalisés et mis à jour : ${finalizedList.size}")
|
||||
println("Markers finalisés et mis à jour : ${finalizedList.size} ${finalizedList.joinToString(", ")}")
|
||||
}
|
||||
|
||||
private val _tuoEditState = MutableStateFlow<TUOEditState?>(null)
|
||||
|
|
@ -709,6 +760,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
|||
fun setStanza(theStanza: Int) {
|
||||
try {
|
||||
_stanza.value = theStanza
|
||||
_showMidiCtrl.value = false
|
||||
} catch (e: NumberFormatException) {
|
||||
_stanza.value = 0
|
||||
}
|
||||
|
|
@ -758,28 +810,33 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
|||
|
||||
fun getHairPins(): List<HairPinData> {
|
||||
val allMarkers = _midiMarkersList.value
|
||||
|
||||
fun isStartToken(s: String): Boolean {
|
||||
val m = s.trim()
|
||||
return m == "<" ||
|
||||
m == ">" ||
|
||||
m.contains("cres", ignoreCase = true) ||
|
||||
m.contains("dim", ignoreCase = true)
|
||||
}
|
||||
val starts = allMarkers
|
||||
.filter {
|
||||
val m = it.marker.trim()
|
||||
m == "<" ||
|
||||
m == ">" ||
|
||||
m.contains("cres", ignoreCase = true) ||
|
||||
m.contains("dim", ignoreCase = true)
|
||||
.filter { markerObj ->
|
||||
markerObj.marker.any { isStartToken(it) }
|
||||
}
|
||||
.sortedBy { it.gridIndex }
|
||||
val ends = allMarkers
|
||||
.filter { it.marker.trim().endsWith("=") }
|
||||
.filter { markerObjt ->
|
||||
markerObjt.marker.any { it.trim().endsWith("=") }
|
||||
}
|
||||
.sortedBy { it.gridIndex }
|
||||
.toMutableList()
|
||||
|
||||
// println("HairPins starts: ${starts.size} | ends: ${ends.size}")
|
||||
return starts.mapNotNull { start ->
|
||||
val startGrid = start.gridIndex ?: return@mapNotNull null
|
||||
val symbol = when {
|
||||
start.marker.trim() == "<" -> '<'
|
||||
start.marker.trim() == ">" -> '>'
|
||||
start.marker.contains("cres", ignoreCase = true) -> '<'
|
||||
start.marker.contains("dim", ignoreCase = true) -> '>'
|
||||
val startString = start.marker.firstOrNull { isStartToken(it) } ?: return@mapNotNull null
|
||||
val symbol = when {
|
||||
startString.contains("<") || startString.contains("cres", ignoreCase = true) -> '<'
|
||||
startString.contains(">") || startString.contains("dim", ignoreCase = true) -> '>'
|
||||
else -> return@mapNotNull null
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -255,214 +255,255 @@ actual class FMediaPlayer actual constructor(
|
|||
val ritRegex = Regex("""rit\.?|ritard\.?|ritenuto\.?|ritardando""", RegexOption.IGNORE_CASE)
|
||||
val rallRegex = Regex("""rall\.?|rallent\.?|rallentando""", RegexOption.IGNORE_CASE)
|
||||
|
||||
metadataList.forEach { (timestamp, gridIndex, template, lastCallerMarker, marker, noteBefore, separat, note) ->
|
||||
val currentIndex = gridIndex ?: 0
|
||||
metadataList.forEach { midiMarker ->
|
||||
val currentIndex = midiMarker.gridIndex ?: 0
|
||||
val markerList = midiMarker.marker
|
||||
val last_grid = sharedScreenModel.getTotalGridCount()
|
||||
val hairPins = sharedScreenModel.getHairPins()
|
||||
|
||||
val dsRegex = Regex("""D\.?S\.?""")
|
||||
val dcRegex = Regex("""D\.?C\.?""")
|
||||
val dsGPattern = Regex("""D\.?S\.?_GROUP_PART""")
|
||||
val dcGPattern = Regex("""D\.?C\.?_GROUP_PART""")
|
||||
val finReg = Regex("""(fine|fin|farany|end)""", RegexOption.IGNORE_CASE)
|
||||
|
||||
val last_grid = sharedScreenModel.getTotalGridCount()
|
||||
val hairPins = sharedScreenModel.getHairPins()
|
||||
when {
|
||||
// segno
|
||||
marker.contains("$") -> {
|
||||
lastSegno = currentIndex
|
||||
println("Cible ($) mémorisée au $lastSegno")
|
||||
}
|
||||
// Point d'orgue
|
||||
marker.contains("\uD834\uDD10") -> {
|
||||
val beat = if(note.contains('•')) 2 else 1 // demi-ton sur .) ou non
|
||||
navigationSteps.add(
|
||||
FMediaPlayer.NavigationStep(
|
||||
marker,
|
||||
currentIndex,
|
||||
isHold = true,
|
||||
beatInDC = beat
|
||||
)
|
||||
)
|
||||
println("Point d'orgue (\uD834\uDD10) mémorisée au grille n° $gridIndex")
|
||||
}
|
||||
// Rit ...
|
||||
ritRegex.containsMatchIn(marker) -> {
|
||||
val endGrid = if(lastCallerMarker == 0) last_grid else lastCallerMarker
|
||||
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = currentIndex,
|
||||
isTempoChange = true,
|
||||
tempoType = "rit",
|
||||
endGridForTempo = endGrid,
|
||||
targetTempoMultiplier = 0.55f // 55% du tempo initial
|
||||
)
|
||||
)
|
||||
println("Ritardando mémorisé à grille $currentIndex jusqu'à $endGrid")
|
||||
}
|
||||
rallRegex.containsMatchIn(marker) -> {
|
||||
val endGrid = last_grid
|
||||
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = currentIndex,
|
||||
isTempoChange = true,
|
||||
tempoType = "rall",
|
||||
endGridForTempo = endGrid,
|
||||
targetTempoMultiplier = 0.50f // 50% du tempo initial
|
||||
)
|
||||
)
|
||||
println("Rallentando mémorisé à grille $currentIndex jusqu'à $endGrid")
|
||||
}
|
||||
// DS
|
||||
dsGPattern.matches(marker.trim())-> {
|
||||
val target = if (lastSegno > 0) lastSegno else 0
|
||||
var indx = if((last_grid - currentIndex) <= 0) {
|
||||
currentIndex - 1
|
||||
} else currentIndex
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker,
|
||||
gridIndex = indx,
|
||||
targetGrid = target,
|
||||
)
|
||||
)
|
||||
println("Lien créé : $marker à grille n° $indx vers cible $target ........")
|
||||
}
|
||||
dsRegex.matches(marker.trim()) || marker == "DSFin" -> {
|
||||
val target = if (lastSegno > 0) lastSegno else 0
|
||||
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker,
|
||||
currentIndex,
|
||||
targetGrid = target
|
||||
))
|
||||
println("Lien DS créé : Saut immédiat à $gridIndex vers Segno $target")
|
||||
}
|
||||
|
||||
// DC
|
||||
dcGPattern.matches(marker.trim()) -> {
|
||||
val indx = if((last_grid - currentIndex) <= 0) {
|
||||
currentIndex - 1
|
||||
} else currentIndex
|
||||
// println("monn index est $indx car dernier est $last_grid et curr $currentIndex")
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker,
|
||||
indx,
|
||||
targetGrid = 0
|
||||
)
|
||||
)
|
||||
println("Lien créé : $marker à $indx vers cible 0 ")
|
||||
}
|
||||
(dcRegex.matches(marker.trim()) && !marker.contains("DC_GROUP_PART")) -> {
|
||||
println("dernier grille $last_grid")
|
||||
var indx = if((last_grid - currentIndex) <= 0) {
|
||||
currentIndex /*- 1*/
|
||||
} else currentIndex
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker,
|
||||
indx,
|
||||
targetGrid = 0
|
||||
)
|
||||
)
|
||||
println("Lien DC créé : $marker à $indx vers le début")
|
||||
}
|
||||
|
||||
// Farany
|
||||
marker.trim().equals("Farany_GROUP_PART", ignoreCase = true) -> {
|
||||
val hasDcAfter = metadataList.any { (_, gi, _, _, mk, _, _, _) ->
|
||||
(gi ?: 0) > currentIndex &&
|
||||
(dcGPattern.matches(mk.trim()) ||
|
||||
dcRegex.matches(mk.trim()))
|
||||
markerList.forEach { marker ->
|
||||
val mTrim = marker.trim()
|
||||
when {
|
||||
// segno
|
||||
marker.contains("$") -> {
|
||||
lastSegno = currentIndex
|
||||
println("Cible ($) mémorisée au $lastSegno")
|
||||
}
|
||||
if (hasDcAfter) {
|
||||
// Point d'orgue
|
||||
marker.contains("\uD834\uDD10") -> {
|
||||
val beat = if (midiMarker.note.contains('•')) 2 else 1 // demi-ton sur .) ou non
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = currentIndex,
|
||||
targetGrid = last_grid,
|
||||
isFin = true,
|
||||
finActive = false
|
||||
FMediaPlayer.NavigationStep(
|
||||
marker,
|
||||
currentIndex,
|
||||
isHold = true,
|
||||
beatInDC = beat
|
||||
)
|
||||
)
|
||||
println("Farany mémorisé à la grille $currentIndex")
|
||||
println("Point d'orgue (\uD834\uDD10) mémorisée au grille n° ${midiMarker.gridIndex}")
|
||||
}
|
||||
}
|
||||
// velocité
|
||||
extractDynamic(marker) != null && marker.trim() != "=" -> {
|
||||
val dyn = extractDynamic(marker) ?: return@forEach
|
||||
lastFactor = when (dyn) {
|
||||
Dynamic.MF -> 1.0f
|
||||
else -> dyn.factor
|
||||
}
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = currentIndex,
|
||||
dynamic = dyn
|
||||
// Rit ...
|
||||
ritRegex.containsMatchIn(marker) -> {
|
||||
val endGrid = if (midiMarker.lastCallerMarker == 0) last_grid else midiMarker.lastCallerMarker
|
||||
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = currentIndex,
|
||||
isTempoChange = true,
|
||||
tempoType = "rit",
|
||||
endGridForTempo = endGrid,
|
||||
targetTempoMultiplier = 0.55f // 55% du tempo initial
|
||||
)
|
||||
)
|
||||
)
|
||||
println("Dynamique '${dyn.label}' (vel=${dyn.velocity}) mémorisée à la grille $currentIndex")
|
||||
}
|
||||
// Soufflet
|
||||
marker.trim() == "<" || marker.trim() == ">" ||
|
||||
marker.trim().contains("cres", ignoreCase = true) ||
|
||||
marker.trim().contains("dim", ignoreCase = true) -> {
|
||||
val symbol = when {
|
||||
marker.trim() == "<" -> '<'
|
||||
marker.trim() == ">" -> '>'
|
||||
marker.trim().contains("cres", ignoreCase = true) -> '<'
|
||||
marker.trim().contains("dim", ignoreCase = true) -> '>'
|
||||
else -> return@forEach
|
||||
println("Ritardando mémorisé à grille $currentIndex jusqu'à $endGrid")
|
||||
}
|
||||
val pair = hairPins.find { it.startGrid == currentIndex } ?: return@forEach
|
||||
rallRegex.containsMatchIn(marker) -> {
|
||||
val endGrid = last_grid
|
||||
|
||||
val dynBefore = navigationSteps
|
||||
.filter { it.dynamic != null && it.gridIndex <= currentIndex }
|
||||
.maxByOrNull { it.gridIndex }?.dynamic ?: Dynamic.MF
|
||||
|
||||
val dynAfter = metadataList
|
||||
.filter { (_, gi, _, _, mk, _, _, _) ->
|
||||
(gi ?: 0) >= pair.endGrid && Dynamic.entries.any { d -> d.label == mk.trim() }
|
||||
}
|
||||
.minByOrNull { it.gridIndex ?: 0 }
|
||||
?.let { m -> Dynamic.entries.find { d -> d.label == m.marker.trim() } }
|
||||
|
||||
val explicitDynAfter = metadataList
|
||||
.filter { (_, gi, _, _, mk, _, _, _) ->
|
||||
(gi ?: 0) >= pair.endGrid &&
|
||||
extractDynamic(mk) != null
|
||||
}
|
||||
.minByOrNull { it.gridIndex ?: 0 }
|
||||
?.let { m -> extractDynamic(m.marker) }
|
||||
|
||||
val fromFactor = lastFactor
|
||||
val toFactor = when {
|
||||
explicitDynAfter != null && symbol == '<' && explicitDynAfter.factor > fromFactor -> {
|
||||
explicitDynAfter.factor
|
||||
}
|
||||
explicitDynAfter != null && symbol == '>' && explicitDynAfter.factor < fromFactor -> {
|
||||
explicitDynAfter.factor
|
||||
}
|
||||
else -> nextDynamic(fromFactor, symbol)
|
||||
}
|
||||
lastFactor = toFactor
|
||||
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker.trim(),
|
||||
gridIndex = currentIndex,
|
||||
hairPin = symbol,
|
||||
hairPinEndGrid = pair.endGrid,
|
||||
hairPinFromFactor = fromFactor,
|
||||
hairPinToFactor = toFactor
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = currentIndex,
|
||||
isTempoChange = true,
|
||||
tempoType = "rall",
|
||||
endGridForTempo = endGrid,
|
||||
targetTempoMultiplier = 0.50f // 50% du tempo initial
|
||||
)
|
||||
)
|
||||
)
|
||||
println("NavigationStep HairPin '$symbol' : $currentIndex→${pair.endGrid} | ${fromFactor})→${toFactor})")
|
||||
println("Rallentando mémorisé à grille $currentIndex jusqu'à $endGrid")
|
||||
}
|
||||
// DS
|
||||
dsGPattern.matches(mTrim) -> {
|
||||
val target = if (lastSegno > 0) lastSegno else 0
|
||||
var indx = if ((last_grid - currentIndex) <= 0) {
|
||||
currentIndex - 1
|
||||
} else currentIndex
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker,
|
||||
gridIndex = indx,
|
||||
targetGrid = target,
|
||||
)
|
||||
)
|
||||
println("Lien créé : $marker à grille n° $indx vers cible $target ........")
|
||||
}
|
||||
|
||||
dsRegex.matches(mTrim) || marker == "DSFin" -> {
|
||||
val target = if (lastSegno > 0) lastSegno else 0
|
||||
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker,
|
||||
currentIndex,
|
||||
targetGrid = target
|
||||
)
|
||||
)
|
||||
println("Lien DS créé : Saut immédiat à ${midiMarker.gridIndex} vers Segno $target")
|
||||
}
|
||||
|
||||
// DC
|
||||
dcGPattern.matches(mTrim) -> {
|
||||
val indx = if ((last_grid - currentIndex) <= 0) {
|
||||
currentIndex - 1
|
||||
} else currentIndex
|
||||
// println("monn index est $indx car dernier est $last_grid et curr $currentIndex")
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker,
|
||||
indx,
|
||||
targetGrid = 0
|
||||
)
|
||||
)
|
||||
println("Lien créé : $marker à $indx vers cible 0 ")
|
||||
}
|
||||
|
||||
(dcRegex.matches(mTrim) && !marker.contains("DC_GROUP_PART")) -> {
|
||||
println("dernier grille $last_grid")
|
||||
var indx = if ((last_grid - currentIndex) <= 0) {
|
||||
currentIndex /*- 1*/
|
||||
} else currentIndex
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker,
|
||||
indx,
|
||||
targetGrid = 0
|
||||
)
|
||||
)
|
||||
println("Lien DC créé : $marker à $indx vers le début")
|
||||
}
|
||||
|
||||
// Farany
|
||||
finReg.containsMatchIn(mTrim) -> {
|
||||
val hasDcAfter = metadataList.any { m ->
|
||||
val after = (m.gridIndex ?: 0) > currentIndex
|
||||
|
||||
println(
|
||||
"grid=${m.gridIndex}, current=$currentIndex, after=$after"
|
||||
)
|
||||
|
||||
val hasMarker = m.marker.any { mk ->
|
||||
val value = mk.trim()
|
||||
val ok = dcGPattern.matches(value) || dcRegex.matches(value)
|
||||
println("marker=$value -> $ok")
|
||||
ok
|
||||
}
|
||||
|
||||
println("after=$after, hasMarker=$hasMarker, result=${after && hasMarker}")
|
||||
|
||||
after && hasMarker
|
||||
}
|
||||
if (hasDcAfter) {
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = currentIndex,
|
||||
targetGrid = last_grid,
|
||||
isFin = true,
|
||||
finActive = false
|
||||
)
|
||||
)
|
||||
println("Farany mémorisé à la grille $currentIndex")
|
||||
} else {
|
||||
val verse = sharedScreenModel.stanza.value
|
||||
val verses = sharedScreenModel.nbStanzas.value
|
||||
println("$verse == $verses")
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker="${marker}_STZ",
|
||||
gridIndex = currentIndex,
|
||||
isFin = true,
|
||||
finActive = if(verse == verses) false else true
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
// velocité
|
||||
extractDynamic(marker) != null && mTrim != "=" -> {
|
||||
val dyn = extractDynamic(marker) ?: return@forEach
|
||||
lastFactor = when (dyn) {
|
||||
Dynamic.MF -> 1.0f
|
||||
else -> dyn.factor
|
||||
}
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = marker,
|
||||
gridIndex = currentIndex,
|
||||
dynamic = dyn
|
||||
)
|
||||
)
|
||||
println("Dynamique '${dyn.label}' (vel=${dyn.velocity}) mémorisée à la grille $currentIndex")
|
||||
}
|
||||
// Soufflet
|
||||
mTrim == "<" || mTrim == ">" ||
|
||||
mTrim.contains("cres", ignoreCase = true) ||
|
||||
mTrim.contains("dim", ignoreCase = true) -> {
|
||||
val symbol = when {
|
||||
mTrim == "<" -> '<'
|
||||
mTrim == ">" -> '>'
|
||||
mTrim.contains("cres", ignoreCase = true) -> '<'
|
||||
mTrim.contains("dim", ignoreCase = true) -> '>'
|
||||
else -> return@forEach
|
||||
}
|
||||
val pair = hairPins.find { it.startGrid == currentIndex } ?: return@forEach
|
||||
|
||||
val dynBefore = navigationSteps
|
||||
.filter { it.dynamic != null && it.gridIndex <= currentIndex }
|
||||
.maxByOrNull { it.gridIndex }?.dynamic ?: Dynamic.MF
|
||||
|
||||
val dynAfter = metadataList
|
||||
.filter { m -> (m.gridIndex ?: 0) >= pair.endGrid }
|
||||
.mapNotNull { m ->
|
||||
val label = m.marker.firstOrNull { mk -> Dynamic.entries.any { d -> d.label == mk.trim() } }
|
||||
val dynamic = label?.let { l -> Dynamic.entries.find { d -> d.label == l.trim() } }
|
||||
if (dynamic != null) (m.gridIndex ?: 0) to dynamic else null
|
||||
}
|
||||
.minByOrNull { it.first }
|
||||
?.second
|
||||
|
||||
|
||||
val explicitDynAfter = metadataList
|
||||
.filter { m -> (m.gridIndex ?: 0) >= pair.endGrid }
|
||||
.mapNotNull { m ->
|
||||
val dynamic = m.marker.firstNotNullOfOrNull { mk -> extractDynamic(mk) }
|
||||
if (dynamic != null) (m.gridIndex ?: 0) to dynamic else null
|
||||
}
|
||||
.minByOrNull { it.first }
|
||||
?.second
|
||||
|
||||
|
||||
val fromFactor = lastFactor
|
||||
val toFactor = when {
|
||||
explicitDynAfter != null && symbol == '<' && explicitDynAfter.factor > fromFactor -> {
|
||||
explicitDynAfter.factor
|
||||
}
|
||||
|
||||
explicitDynAfter != null && symbol == '>' && explicitDynAfter.factor < fromFactor -> {
|
||||
explicitDynAfter.factor
|
||||
}
|
||||
|
||||
else -> nextDynamic(fromFactor, symbol)
|
||||
}
|
||||
lastFactor = toFactor
|
||||
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = mTrim,
|
||||
gridIndex = currentIndex,
|
||||
hairPin = symbol,
|
||||
hairPinEndGrid = pair.endGrid,
|
||||
hairPinFromFactor = fromFactor,
|
||||
hairPinToFactor = toFactor
|
||||
)
|
||||
)
|
||||
println("NavigationStep HairPin '$symbol' : $currentIndex→${pair.endGrid} | ${fromFactor})→${toFactor})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -483,8 +524,6 @@ actual class FMediaPlayer actual constructor(
|
|||
navigationJob?.cancel()
|
||||
navigationJob = playerScope.launch(Dispatchers.Default) {
|
||||
sharedScreenModel.activeIndex.collect { currentIndex ->
|
||||
|
||||
val availableIndices = navigationSteps.map { it.gridIndex }
|
||||
// println("bpm:$targetBpm _ ${sequencer?.tempoInBPM}| Index en mémoire : $availableIndices")
|
||||
// println("i $currentIndex ")
|
||||
|
||||
|
|
@ -494,12 +533,10 @@ actual class FMediaPlayer actual constructor(
|
|||
}
|
||||
if (currentIndex < 0) return@collect
|
||||
|
||||
val step = navigationSteps.find {
|
||||
it.gridIndex == currentIndex &&
|
||||
!it.alreadyDone
|
||||
val currentSteps = navigationSteps.filter {
|
||||
it.gridIndex == currentIndex && !it.alreadyDone
|
||||
}
|
||||
|
||||
if (step != null) {
|
||||
currentSteps.forEach { step ->
|
||||
if (Math.abs(sequencer!!.tempoInBPM - targetBpm) > 0.1 && !isInTempoChange) {
|
||||
forceTempo(targetBpm.toDouble())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue