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 val voiceVolumes = FloatArray(4) { 127f }
private var currentGlobalVolume: Float = 0.8f 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 currentTempo: Float = 1.0f
private var targetBpm: Float = 120f private var targetBpm: Float = 120f
@ -51,6 +53,7 @@ actual class FMediaPlayer actual constructor(
val isHold: Boolean = false, // point d'orgue val isHold: Boolean = false, // point d'orgue
var alreadyDone: Boolean = false, // done var alreadyDone: Boolean = false, // done
var beatInDC: Int = 1, // temps note sur le marker var beatInDC: Int = 1, // temps note sur le marker
val dynamic: Dynamic?= null, // velocité
) )
private val navigationSteps = mutableListOf<NavigationStep>() private val navigationSteps = mutableListOf<NavigationStep>()
@ -119,6 +122,36 @@ actual class FMediaPlayer actual constructor(
private fun resetNavigationFlags() { private fun resetNavigationFlags() {
navigationSteps.forEach { it.alreadyDone = false } 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) { private fun prepareNavigation(sharedScreenModel: SharedScreenModel) {
val metadataList = sharedScreenModel.getFullMarkers() val metadataList = sharedScreenModel.getFullMarkers()
navigationSteps.clear() navigationSteps.clear()
@ -134,10 +167,12 @@ actual class FMediaPlayer actual constructor(
val last_grid = sharedScreenModel.getTotalGridCount() val last_grid = sharedScreenModel.getTotalGridCount()
when { when {
// 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") -> { marker.contains("\uD834\uDD10") -> {
val beat = if(note.contains('•')) 2 else 1 // demi-ton sur .) ou non val beat = if(note.contains('•')) 2 else 1 // demi-ton sur .) ou non
navigationSteps.add( navigationSteps.add(
@ -205,6 +240,18 @@ actual class FMediaPlayer actual constructor(
) )
println("Lien DC créé : $marker à $indx vers le début") 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) { if (step != null) {
// Point d'orgue // Point d'orgue
if (step.isHold) { when {
step.isHold -> {
val currentBpm = sequencer?.tempoInBPM ?: targetBpm val currentBpm = sequencer?.tempoInBPM ?: targetBpm
val beatMs = (60_000 / currentBpm).toLong() val beatMs = (60_000 / currentBpm).toLong()
val holdDuration = if (step.beatInDC == 2) beatMs / 2 else beatMs * 2 val holdDuration = if (step.beatInDC == 2) beatMs / 2 else beatMs * 2
@ -248,7 +296,13 @@ actual class FMediaPlayer actual constructor(
sequencer?.tempoFactor = previousFactor sequencer?.tempoFactor = previousFactor
synthetizer?.channels?.forEach { it?.controlChange(64, 0) } synthetizer?.channels?.forEach { it?.controlChange(64, 0) }
} else { }
//velocity
step.dynamic != null -> {
applyDynamic(step.dynamic)
}
else -> {
// $ dc ds ... // $ dc ds ...
step.alreadyDone = true step.alreadyDone = true
@ -260,7 +314,7 @@ actual class FMediaPlayer actual constructor(
sequencer?.tempoFactor = 0.0001f sequencer?.tempoFactor = 0.0001f
delay(beatDuration) delay(beatDuration)
// println("et là je saut vers ${step.targetGrid}") // println("et là je saut vers ${step.targetGrid}")
seekToGrid(step.targetGrid) seekToGrid(step.targetGrid)
synthetizer?.channels?.forEach { it?.controlChange(64, 0) } synthetizer?.channels?.forEach { it?.controlChange(64, 0) }
sequencer?.tempoFactor = 1f sequencer?.tempoFactor = 1f
@ -268,7 +322,8 @@ actual class FMediaPlayer actual constructor(
sequencer?.tempoInBPM = targetBpm sequencer?.tempoInBPM = targetBpm
sequencer?.start() sequencer?.start()
} }
// println("Après de sauter bpm=$targetBpm") // println("Après de sauter bpm=$targetBpm")
}
} }
} }
} }
@ -296,6 +351,13 @@ actual class FMediaPlayer actual constructor(
sequencer?.tickPosition = tick sequencer?.tickPosition = tick
applyBpm() 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(){ actual fun play(){
@ -367,6 +429,7 @@ actual class FMediaPlayer actual constructor(
resetNavigationFlags() resetNavigationFlags()
navigationSteps.clear() navigationSteps.clear()
clearLoop() clearLoop()
currentDynamicFactor = Dynamic.MF.factor
// release() // release()
} }
actual fun getDuration(): Long { actual fun getDuration(): Long {
@ -387,7 +450,8 @@ actual class FMediaPlayer actual constructor(
actual fun setVolume(level: Float) { actual fun setVolume(level: Float) {
try { try {
this.currentGlobalVolume = level 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 -> synthetizer?.channels?.forEachIndexed { index, channel ->
if (index < 4) { if (index < 4) {
@ -396,7 +460,7 @@ actual class FMediaPlayer actual constructor(
} else { } else {
channel?.controlChange(7, volumeInt) channel?.controlChange(7, volumeInt)
} }
} }*/
val mixer = AudioSystem.getMixer(null) val mixer = AudioSystem.getMixer(null)
val lines = mixer.sourceLines val lines = mixer.sourceLines
for (line in lines) { for (line in lines) {
@ -462,14 +526,25 @@ actual class FMediaPlayer actual constructor(
synthetizer?.channels?.let { channels -> synthetizer?.channels?.let { channels ->
for (i in 0 until 4) { for (i in 0 until 4) {
if (i < channels.size) { 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(7, targetVolume)
channels[i].controlChange(11, targetVolume) channels[i].controlChange(11, targetVolume)
if (targetVolume == 0) { if (targetVolume == 0) {
channels[i].controlChange(123, 0) channels[i].controlChange(123, 0)
} }*/
} }
// println("SATB $i Volumes: ${voiceVolumes[i]}") // println("SATB $i Volumes: ${voiceVolumes[i]}")
} }