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