Implement velocity {ppp,pp,p,mp,mf,f,ff,fff}

This commit is contained in:
hasinarak3@gmail.com 2026-03-11 14:47:30 +03:00
parent 0b0f0e48a6
commit 44495aa5cd
2 changed files with 127 additions and 35 deletions

View file

@ -0,0 +1,17 @@
package mg.dot.feufaro.midi
enum class Dynamic(val velocity: Int, val label: String, val factor: Float) {
PPP(16, "ppp", 16f / 80f),
PP (33, "pp", 33f / 80f),
P (49, "p", 49f / 80f),
MP (64, "mp", 64f / 80f),
MF (80, "mf", 1.00f),
F (96, "f", 96f / 80f),
FF (112, "ff", 112f / 80f),
FFF(126, "fff", 126f / 80f);
companion object {
fun fromVelocity(v: Int): Dynamic =
entries.minByOrNull { kotlin.math.abs(it.velocity - v) } ?: MF
}
}

View file

@ -37,6 +37,8 @@ actual class FMediaPlayer actual constructor(
private val voiceVolumes = FloatArray(4) { 127f }
private var currentGlobalVolume: Float = 0.8f
private var currentDynamicVelocity: Int = Dynamic.MF.velocity
private var currentDynamicFactor: Float = Dynamic.MF.factor
private var currentTempo: Float = 1.0f
private var targetBpm: Float = 120f
@ -51,6 +53,7 @@ actual class FMediaPlayer actual constructor(
val isHold: Boolean = false, // point d'orgue
var alreadyDone: Boolean = false, // done
var beatInDC: Int = 1, // temps note sur le marker
val dynamic: Dynamic?= null, // velocité
)
private val navigationSteps = mutableListOf<NavigationStep>()
@ -119,6 +122,36 @@ actual class FMediaPlayer actual constructor(
private fun resetNavigationFlags() {
navigationSteps.forEach { it.alreadyDone = false }
}
private var dynamicJob: Job? = null
private fun applyDynamicSmooth(targetFactor: Float, durationMs: Long = 300L) {
dynamicJob?.cancel()
dynamicJob = playerScope.launch {
val startFactor = currentDynamicFactor
val steps = 30
val stepMs = durationMs / steps
for (i in 1..steps) {
val progress = i.toFloat() / steps
val smooth = progress * progress * (3f - 2f * progress)
currentDynamicFactor = startFactor + (targetFactor - startFactor) * smooth
applyVoiceStates()
delay(stepMs)
}
currentDynamicFactor = targetFactor
applyVoiceStates()
}
}
private fun applyDynamic(dynamic: Dynamic) {
val targetFactor = when (dynamic) {
Dynamic.MF -> 1.0f
else -> dynamic.factor
}
println("applyDynamic → ${dynamic.label} | ${currentDynamicFactor}$targetFactor")
applyDynamicSmooth(targetFactor, durationMs = 300L)
}
private fun prepareNavigation(sharedScreenModel: SharedScreenModel) {
val metadataList = sharedScreenModel.getFullMarkers()
navigationSteps.clear()
@ -134,10 +167,12 @@ actual class FMediaPlayer actual constructor(
val last_grid = sharedScreenModel.getTotalGridCount()
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(
@ -205,6 +240,18 @@ actual class FMediaPlayer actual constructor(
)
println("Lien DC créé : $marker à $indx vers le début")
}
// velocité
Dynamic.entries.find { it.label == marker.trim() } != null -> {
val dyn = Dynamic.entries.first { it.label == marker.trim() }
navigationSteps.add(
NavigationStep(
marker = marker,
gridIndex = currentIndex,
dynamic = dyn
)
)
println("Dynamique '${dyn.label}' (vel=${dyn.velocity}) mémorisée à la grille $currentIndex")
}
}
}
}
@ -233,7 +280,8 @@ actual class FMediaPlayer actual constructor(
if (step != null) {
// Point d'orgue
if (step.isHold) {
when {
step.isHold -> {
val currentBpm = sequencer?.tempoInBPM ?: targetBpm
val beatMs = (60_000 / currentBpm).toLong()
val holdDuration = if (step.beatInDC == 2) beatMs / 2 else beatMs * 2
@ -248,7 +296,13 @@ actual class FMediaPlayer actual constructor(
sequencer?.tempoFactor = previousFactor
synthetizer?.channels?.forEach { it?.controlChange(64, 0) }
} else {
}
//velocity
step.dynamic != null -> {
applyDynamic(step.dynamic)
}
else -> {
// $ dc ds ...
step.alreadyDone = true
@ -275,6 +329,7 @@ actual class FMediaPlayer actual constructor(
}
}
}
}
private var syncJob: Job? = null
private fun startSyncLoop(sharedScreenModel: SharedScreenModel) {
@ -296,6 +351,13 @@ actual class FMediaPlayer actual constructor(
sequencer?.tickPosition = tick
applyBpm()
// Voir les velocity avant
val lastDynamic = navigationSteps
.filter { it.dynamic != null && it.gridIndex <= gridIndex }
.maxByOrNull { it.gridIndex }
?.dynamic ?: Dynamic.MF
currentDynamicFactor = lastDynamic.factor
applyVoiceStates()
}
actual fun play(){
@ -367,6 +429,7 @@ actual class FMediaPlayer actual constructor(
resetNavigationFlags()
navigationSteps.clear()
clearLoop()
currentDynamicFactor = Dynamic.MF.factor
// release()
}
actual fun getDuration(): Long {
@ -387,7 +450,8 @@ actual class FMediaPlayer actual constructor(
actual fun setVolume(level: Float) {
try {
this.currentGlobalVolume = level
val volumeInt = (level * 127).toInt().coerceIn(0, 127)
applyVoiceStates()
/*val volumeInt = (level * 127).toInt().coerceIn(0, 127)
synthetizer?.channels?.forEachIndexed { index, channel ->
if (index < 4) {
@ -396,7 +460,7 @@ actual class FMediaPlayer actual constructor(
} else {
channel?.controlChange(7, volumeInt)
}
}
}*/
val mixer = AudioSystem.getMixer(null)
val lines = mixer.sourceLines
for (line in lines) {
@ -462,14 +526,25 @@ actual class FMediaPlayer actual constructor(
synthetizer?.channels?.let { channels ->
for (i in 0 until 4) {
if (i < channels.size) {
val targetVolume = voiceVolumes[i].toInt()
val userVol = voiceVolumes[i] / 127f
val globalVol = currentGlobalVolume
val finalVol = (127f * userVol * globalVol * currentDynamicFactor).toInt().coerceIn(0, 127)
println("Voice[$i] user=${voiceVolumes[i]} global=$currentGlobalVolume dynFactor=$currentDynamicFactor → finalVol=$finalVol")
channels[i].controlChange(7, finalVol)
channels[i].controlChange(11, finalVol)
if (finalVol == 0) channels[i].controlChange(123, 0)
/*val targetVolume = voiceVolumes[i].toInt()
channels[i].controlChange(7, targetVolume)
channels[i].controlChange(11, targetVolume)
if (targetVolume == 0) {
channels[i].controlChange(123, 0)
}
}*/
}
// println("SATB $i Volumes: ${voiceVolumes[i]}")
}