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 com.russhwolf.settings.Settings
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import mg.dot.feufaro.provideSettings
|
import mg.dot.feufaro.provideSettings
|
||||||
|
import mg.dot.feufaro.solfa.Transpose
|
||||||
import org.billthefarmer.mididriver.MidiDriver
|
import org.billthefarmer.mididriver.MidiDriver
|
||||||
import org.koin.core.component.KoinComponent
|
import org.koin.core.component.KoinComponent
|
||||||
import org.koin.core.component.inject
|
import org.koin.core.component.inject
|
||||||
|
|
@ -132,6 +133,7 @@ actual class FMediaPlayer actual constructor(
|
||||||
private var currentTickPos: Long = 0L
|
private var currentTickPos: Long = 0L
|
||||||
private var lastEventNano: Long = System.nanoTime()
|
private var lastEventNano: Long = System.nanoTime()
|
||||||
private var targetBpm: Float = 120f
|
private var targetBpm: Float = 120f
|
||||||
|
private var initialBpm: Float = 120f
|
||||||
|
|
||||||
@Volatile private var currentPlaybackBpm: Float = 120f
|
@Volatile private var currentPlaybackBpm: Float = 120f
|
||||||
@Volatile private var isInTempoChange: Boolean = false
|
@Volatile private var isInTempoChange: Boolean = false
|
||||||
|
|
@ -140,6 +142,7 @@ 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 currentDynamicFactor: Float = Dynamic.MF.factor
|
private var currentDynamicFactor: Float = Dynamic.MF.factor
|
||||||
|
private var transpositionSemitones: Int = 0
|
||||||
|
|
||||||
private var pointA: Long = -1L
|
private var pointA: Long = -1L
|
||||||
private var pointB: Long = -1L
|
private var pointB: Long = -1L
|
||||||
|
|
@ -171,7 +174,9 @@ actual class FMediaPlayer actual constructor(
|
||||||
val isTempoChange: Boolean = false,
|
val isTempoChange: Boolean = false,
|
||||||
val tempoType: String = "",
|
val tempoType: String = "",
|
||||||
val endGridForTempo: Int = -1,
|
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 navigationSteps = mutableListOf<NavigationStep>()
|
||||||
private val prefs: Settings = provideSettings()
|
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 {
|
private fun extractDynamic(marker: String) = Dynamic.entries.firstOrNull {
|
||||||
it.label == marker.trim().removeSuffix("=").trim()
|
it.label == marker.trim().removeSuffix("=").trim()
|
||||||
|
|
@ -345,6 +356,7 @@ actual class FMediaPlayer actual constructor(
|
||||||
} else {
|
} else {
|
||||||
isRunning = false
|
isRunning = false
|
||||||
allNotesOff()
|
allNotesOff()
|
||||||
|
resetTempoToNormal()
|
||||||
onFinished()
|
onFinished()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -386,9 +398,15 @@ actual class FMediaPlayer actual constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
when (ev.type) {
|
when (ev.type) {
|
||||||
0x80 -> send(0x80 or ev.channel, ev.data1, ev.data2)
|
0x80 -> {
|
||||||
0x90 -> send(0x90 or ev.channel, ev.data1,
|
val transposedNote = (ev.data1 + transpositionSemitones).coerceIn(0, 127)
|
||||||
(ev.data2 * currentDynamicFactor).toInt().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)
|
0xB0 -> if (ev.data1 != 7 && ev.data1 != 11)
|
||||||
send(0xB0 or ev.channel, ev.data1, ev.data2)
|
send(0xB0 or ev.channel, ev.data1, ev.data2)
|
||||||
0xC0 -> send(0xC0 or ev.channel, ev.data1)
|
0xC0 -> send(0xC0 or ev.channel, ev.data1)
|
||||||
|
|
@ -419,10 +437,21 @@ actual class FMediaPlayer actual constructor(
|
||||||
currentTickPos = gridIndex.toLong() * resolution
|
currentTickPos = gridIndex.toLong() * resolution
|
||||||
lastEventNano = System.nanoTime()
|
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
|
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
|
||||||
|
navigationSteps.forEach { step ->
|
||||||
|
if (step.gridIndex > gridIndex) {
|
||||||
|
step.alreadyDone = false
|
||||||
|
}
|
||||||
|
}
|
||||||
applyVoiceStates()
|
applyVoiceStates()
|
||||||
|
|
||||||
needsClockSync = true
|
needsClockSync = true
|
||||||
|
|
@ -456,6 +485,8 @@ actual class FMediaPlayer actual constructor(
|
||||||
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 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 ->
|
metadataList.forEach { midiMarker ->
|
||||||
val ci = midiMarker.gridIndex ?: 0
|
val ci = midiMarker.gridIndex ?: 0
|
||||||
val last_grid = sharedScreenModel.getTotalGridCount()
|
val last_grid = sharedScreenModel.getTotalGridCount()
|
||||||
|
|
@ -475,6 +506,37 @@ actual class FMediaPlayer actual constructor(
|
||||||
|
|
||||||
val isDsFin = hasDs && hasFin
|
val isDsFin = hasDs && hasFin
|
||||||
val isDcFin = hasDc && 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 {
|
when {
|
||||||
isDsFin || isDcFin -> {
|
isDsFin || isDcFin -> {
|
||||||
val target = if (lastSegno > 0) lastSegno else 0
|
val target = if (lastSegno > 0) lastSegno else 0
|
||||||
|
|
@ -625,6 +687,22 @@ actual class FMediaPlayer actual constructor(
|
||||||
}
|
}
|
||||||
for(step in currentSteps) {
|
for(step in currentSteps) {
|
||||||
when {
|
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 ─────────────────────
|
// ── Point d'orgue ─────────────────────
|
||||||
step.isHold -> {
|
step.isHold -> {
|
||||||
val beatMs = (60_000 / targetBpm).toLong()
|
val beatMs = (60_000 / targetBpm).toLong()
|
||||||
|
|
@ -839,6 +917,7 @@ actual class FMediaPlayer actual constructor(
|
||||||
resetNavigationFlags(); navigationSteps.clear(); clearLoop()
|
resetNavigationFlags(); navigationSteps.clear(); clearLoop()
|
||||||
currentDynamicFactor = Dynamic.MF.factor
|
currentDynamicFactor = Dynamic.MF.factor
|
||||||
resetTempoToNormal()
|
resetTempoToNormal()
|
||||||
|
resetTempoToInitial()
|
||||||
}
|
}
|
||||||
actual fun release() {
|
actual fun release() {
|
||||||
stop(); midiDriver.stop(); playerScope.cancel()
|
stop(); midiDriver.stop(); playerScope.cancel()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue