Implements modulation&tempo change MidiPlayer - android
This commit is contained in:
parent
41fbb204bb
commit
4698bc6a2c
1 changed files with 83 additions and 4 deletions
|
|
@ -4,6 +4,7 @@ import SharedScreenModel
|
|||
import com.russhwolf.settings.Settings
|
||||
import kotlinx.coroutines.*
|
||||
import mg.dot.feufaro.provideSettings
|
||||
import mg.dot.feufaro.solfa.Transpose
|
||||
import org.billthefarmer.mididriver.MidiDriver
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
|
@ -132,6 +133,7 @@ actual class FMediaPlayer actual constructor(
|
|||
private var currentTickPos: Long = 0L
|
||||
private var lastEventNano: Long = System.nanoTime()
|
||||
private var targetBpm: Float = 120f
|
||||
private var initialBpm: Float = 120f
|
||||
|
||||
@Volatile private var currentPlaybackBpm: Float = 120f
|
||||
@Volatile private var isInTempoChange: Boolean = false
|
||||
|
|
@ -140,6 +142,7 @@ actual class FMediaPlayer actual constructor(
|
|||
private val voiceVolumes = FloatArray(4) { 127f }
|
||||
private var currentGlobalVolume: Float = 0.8f
|
||||
private var currentDynamicFactor: Float = Dynamic.MF.factor
|
||||
private var transpositionSemitones: Int = 0
|
||||
|
||||
private var pointA: Long = -1L
|
||||
private var pointB: Long = -1L
|
||||
|
|
@ -171,7 +174,9 @@ actual class FMediaPlayer actual constructor(
|
|||
val isTempoChange: Boolean = false,
|
||||
val tempoType: String = "",
|
||||
val endGridForTempo: Int = -1,
|
||||
val targetTempoMultiplier: Float = 0.6f
|
||||
val targetTempoMultiplier: Float = 0.6f,
|
||||
val newBpm: Float? = null,
|
||||
val newKey: String? = null
|
||||
)
|
||||
private val navigationSteps = mutableListOf<NavigationStep>()
|
||||
private val prefs: Settings = provideSettings()
|
||||
|
|
@ -283,6 +288,12 @@ actual class FMediaPlayer actual constructor(
|
|||
)
|
||||
}
|
||||
}
|
||||
private fun resetTempoToInitial() {
|
||||
tempoChangeJob?.cancel()
|
||||
isInTempoChange = false
|
||||
targetBpm = initialBpm
|
||||
boundModel?.setBpmFlow(initialBpm)
|
||||
}
|
||||
|
||||
private fun extractDynamic(marker: String) = Dynamic.entries.firstOrNull {
|
||||
it.label == marker.trim().removeSuffix("=").trim()
|
||||
|
|
@ -345,6 +356,7 @@ actual class FMediaPlayer actual constructor(
|
|||
} else {
|
||||
isRunning = false
|
||||
allNotesOff()
|
||||
resetTempoToNormal()
|
||||
onFinished()
|
||||
break
|
||||
}
|
||||
|
|
@ -386,9 +398,15 @@ actual class FMediaPlayer actual constructor(
|
|||
}
|
||||
|
||||
when (ev.type) {
|
||||
0x80 -> send(0x80 or ev.channel, ev.data1, ev.data2)
|
||||
0x90 -> send(0x90 or ev.channel, ev.data1,
|
||||
0x80 -> {
|
||||
val transposedNote = (ev.data1 + transpositionSemitones).coerceIn(0, 127)
|
||||
send(0x80 or ev.channel, transposedNote, ev.data2)
|
||||
}
|
||||
0x90 -> {
|
||||
val transposedNote = (ev.data1 + transpositionSemitones).coerceIn(0, 127)
|
||||
send(0x90 or ev.channel, transposedNote,
|
||||
(ev.data2 * currentDynamicFactor).toInt().coerceIn(0, 127))
|
||||
}
|
||||
0xB0 -> if (ev.data1 != 7 && ev.data1 != 11)
|
||||
send(0xB0 or ev.channel, ev.data1, ev.data2)
|
||||
0xC0 -> send(0xC0 or ev.channel, ev.data1)
|
||||
|
|
@ -419,10 +437,21 @@ actual class FMediaPlayer actual constructor(
|
|||
currentTickPos = gridIndex.toLong() * resolution
|
||||
lastEventNano = System.nanoTime()
|
||||
|
||||
val initialOrPreviousBpm = navigationSteps
|
||||
.filter { it.newBpm != null && it.gridIndex <= gridIndex }
|
||||
.maxByOrNull { it.gridIndex }?.newBpm ?: initialBpm
|
||||
|
||||
targetBpm = initialOrPreviousBpm
|
||||
sharedScreenModel.setBpmFlow(initialOrPreviousBpm)
|
||||
val lastDyn = navigationSteps
|
||||
.filter { it.dynamic != null && it.gridIndex <= gridIndex }
|
||||
.maxByOrNull { it.gridIndex }?.dynamic ?: Dynamic.MF
|
||||
currentDynamicFactor = if (lastDyn == Dynamic.MF) 1.0f else lastDyn.factor
|
||||
navigationSteps.forEach { step ->
|
||||
if (step.gridIndex > gridIndex) {
|
||||
step.alreadyDone = false
|
||||
}
|
||||
}
|
||||
applyVoiceStates()
|
||||
|
||||
needsClockSync = true
|
||||
|
|
@ -456,6 +485,8 @@ actual class FMediaPlayer actual constructor(
|
|||
val dcG = Regex("""D\.?C\.?_GROUP_PART""")*/
|
||||
val finReg = Regex("""(fine|fin|farany|end)""", 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)
|
||||
val tempoRegex = Regex("""^♩\s*=\s*(\d+)""", RegexOption.IGNORE_CASE)
|
||||
metadataList.forEach { midiMarker ->
|
||||
val ci = midiMarker.gridIndex ?: 0
|
||||
val last_grid = sharedScreenModel.getTotalGridCount()
|
||||
|
|
@ -475,6 +506,37 @@ actual class FMediaPlayer actual constructor(
|
|||
|
||||
val isDsFin = hasDs && hasFin
|
||||
val isDcFin = hasDc && hasFin
|
||||
|
||||
// Changement de Tempo ♩=
|
||||
val tempoMatch = tempoRegex.find(mTrim)
|
||||
if (tempoMatch != null) {
|
||||
val bpmVal = tempoMatch.groupValues[1].toFloatOrNull()
|
||||
if (bpmVal != null) {
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = mTrim,
|
||||
gridIndex = ci,
|
||||
newBpm = bpmVal
|
||||
)
|
||||
)
|
||||
println("Changement de BPM mémorisé à la grille $ci -> $bpmVal BPM")
|
||||
}
|
||||
}
|
||||
|
||||
// Modulation Do dia ..
|
||||
val modMatch = modulaRegex.find(mTrim)
|
||||
if (modMatch != null) {
|
||||
val targetKey = modMatch.groupValues[2]
|
||||
navigationSteps.add(
|
||||
NavigationStep(
|
||||
marker = mTrim,
|
||||
gridIndex = ci,
|
||||
newKey = targetKey
|
||||
)
|
||||
)
|
||||
println("Modulation mémorisée à la grille $ci -> $targetKey")
|
||||
}
|
||||
|
||||
when {
|
||||
isDsFin || isDcFin -> {
|
||||
val target = if (lastSegno > 0) lastSegno else 0
|
||||
|
|
@ -625,6 +687,22 @@ actual class FMediaPlayer actual constructor(
|
|||
}
|
||||
for(step in currentSteps) {
|
||||
when {
|
||||
//♩
|
||||
step.newBpm != null -> {
|
||||
step.alreadyDone = true
|
||||
println("Exécution changement de tempo : ${step.newBpm} BPM à la grille $currentIndex")
|
||||
targetBpm = step.newBpm
|
||||
sharedScreenModel.setBpmFlow(step.newBpm)
|
||||
}
|
||||
|
||||
// Modulation
|
||||
step.newKey != null -> {
|
||||
step.alreadyDone = true
|
||||
val baseKeyIdx = Transpose.keyToNumber.indexOf("C").coerceAtLeast(0)
|
||||
val targetKeyIdx = Transpose.keyToNumber.indexOf(step.newKey).coerceAtLeast(0)
|
||||
transpositionSemitones = targetKeyIdx - baseKeyIdx
|
||||
}
|
||||
|
||||
// ── Point d'orgue ─────────────────────
|
||||
step.isHold -> {
|
||||
val beatMs = (60_000 / targetBpm).toLong()
|
||||
|
|
@ -839,6 +917,7 @@ actual class FMediaPlayer actual constructor(
|
|||
resetNavigationFlags(); navigationSteps.clear(); clearLoop()
|
||||
currentDynamicFactor = Dynamic.MF.factor
|
||||
resetTempoToNormal()
|
||||
resetTempoToInitial()
|
||||
}
|
||||
actual fun release() {
|
||||
stop(); midiDriver.stop(); playerScope.cancel()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue