Compare commits
No commits in common. "b3d7429b593f88930daa2f869d344576cf1b6855" and "e0b98c57589423744133f013058a0135b24ddab0" have entirely different histories.
b3d7429b59
...
e0b98c5758
3 changed files with 18 additions and 153 deletions
|
|
@ -4,7 +4,6 @@
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||||
android:maxSdkVersion="28" />
|
android:maxSdkVersion="28" />
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
|
||||||
<application
|
<application
|
||||||
android:name=".AndroidApp"
|
android:name=".AndroidApp"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
|
|
||||||
|
|
@ -66,11 +66,11 @@ class AndroidFileRepository(private val context: Context) : FileRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Exwternal files */
|
/*override fun getFileName(shortName: String): String {
|
||||||
override fun getFileName(shortName: String): String {
|
|
||||||
val folder = context.getExternalFilesDir(null)
|
val folder = context.getExternalFilesDir(null)
|
||||||
return "$folder/$shortName"
|
return "$folder/$shortName"
|
||||||
}
|
}*/
|
||||||
|
// Dans androidMain / AndroidFileRepository.kt
|
||||||
/*override suspend fun readFileBytes(filePath: String): ByteArray = withContext(Dispatchers.IO) {
|
/*override suspend fun readFileBytes(filePath: String): ByteArray = withContext(Dispatchers.IO) {
|
||||||
val folder = context.getExternalFilesDir(null)
|
val folder = context.getExternalFilesDir(null)
|
||||||
val file = File(folder, filePath)
|
val file = File(folder, filePath)
|
||||||
|
|
@ -95,10 +95,9 @@ class AndroidFileRepository(private val context: Context) : FileRepository {
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Internal files
|
override fun getFileName(shortName: String): String {
|
||||||
/*override fun getFileName(shortName: String): String {
|
|
||||||
return File(context.filesDir, shortName).absolutePath
|
return File(context.filesDir, shortName).absolutePath
|
||||||
}*/
|
}
|
||||||
|
|
||||||
override fun pickSavePath(defaultName: String): String? {
|
override fun pickSavePath(defaultName: String): String? {
|
||||||
val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
val downloadDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ actual class FMediaPlayer actual constructor(
|
||||||
private val midiDriver: MidiDriver = MidiDriver.getInstance()
|
private val midiDriver: MidiDriver = MidiDriver.getInstance()
|
||||||
private var sequence: MidiSequence? = null
|
private var sequence: MidiSequence? = null
|
||||||
private var resolution: Int = 480
|
private var resolution: Int = 480
|
||||||
@Volatile private var usPerTick: Double = (60_000_000.0 / 120.0) / 480.0
|
private var usPerTick: Double = 500_000.0 / 480.0
|
||||||
|
|
||||||
@Volatile private var isRunning = false
|
@Volatile private var isRunning = false
|
||||||
@Volatile private var isHolding = false
|
@Volatile private var isHolding = false
|
||||||
|
|
@ -119,10 +119,6 @@ actual class FMediaPlayer actual constructor(
|
||||||
private var lastEventNano: Long = System.nanoTime()
|
private var lastEventNano: Long = System.nanoTime()
|
||||||
private var targetBpm: Float = 120f
|
private var targetBpm: Float = 120f
|
||||||
|
|
||||||
@Volatile private var currentPlaybackBpm: Float = 120f
|
|
||||||
@Volatile private var isInTempoChange: Boolean = false
|
|
||||||
@Volatile private var needsClockSync = false
|
|
||||||
private var tempoChangeJob: Job? = null
|
|
||||||
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
|
||||||
|
|
@ -152,11 +148,6 @@ actual class FMediaPlayer actual constructor(
|
||||||
val hairPinToFactor: Float = 1.0f,
|
val hairPinToFactor: Float = 1.0f,
|
||||||
val isFarany: Boolean = false,
|
val isFarany: Boolean = false,
|
||||||
var faranyActive: Boolean = false,
|
var faranyActive: Boolean = false,
|
||||||
// rit & rall
|
|
||||||
val isTempoChange: Boolean = false,
|
|
||||||
val tempoType: String = "",
|
|
||||||
val endGridForTempo: Int = -1,
|
|
||||||
val targetTempoMultiplier: Float = 0.6f
|
|
||||||
)
|
)
|
||||||
private val navigationSteps = mutableListOf<NavigationStep>()
|
private val navigationSteps = mutableListOf<NavigationStep>()
|
||||||
|
|
||||||
|
|
@ -215,49 +206,6 @@ actual class FMediaPlayer actual constructor(
|
||||||
currentDynamicFactor = toFactor; applyVoiceStates()
|
currentDynamicFactor = toFactor; applyVoiceStates()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyTempoChange(
|
|
||||||
startBpm: Float,
|
|
||||||
endBpm: Float,
|
|
||||||
durationMs: Long,
|
|
||||||
tempoType: String
|
|
||||||
) {
|
|
||||||
tempoChangeJob?.cancel()
|
|
||||||
isInTempoChange = true
|
|
||||||
tempoChangeJob = playerScope.launch {
|
|
||||||
val steps = 50
|
|
||||||
val stepMs = (durationMs / steps).coerceAtLeast(20L)
|
|
||||||
val startTime = System.currentTimeMillis()
|
|
||||||
|
|
||||||
for (i in 1..steps) {
|
|
||||||
val elapsed = System.currentTimeMillis() - startTime
|
|
||||||
val progress = (elapsed.toFloat() / durationMs).coerceIn(0f, 1f)
|
|
||||||
val smooth = progress * progress * (3f - 2f * progress) // ease-in-out
|
|
||||||
currentPlaybackBpm = startBpm + (endBpm - startBpm) * smooth
|
|
||||||
usPerTick = (60_000_000.0 / currentPlaybackBpm) / resolution
|
|
||||||
needsClockSync = true
|
|
||||||
delay(stepMs)
|
|
||||||
if (!isActive) break
|
|
||||||
}
|
|
||||||
|
|
||||||
currentPlaybackBpm = endBpm
|
|
||||||
usPerTick = (60_000_000.0 / currentPlaybackBpm) / resolution
|
|
||||||
isInTempoChange = false
|
|
||||||
println("$tempoType terminé → $endBpm BPM")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun resetTempoToNormal() {
|
|
||||||
if (kotlin.math.abs(currentPlaybackBpm - targetBpm) > 0.5f) {
|
|
||||||
applyTempoChange(
|
|
||||||
startBpm = currentPlaybackBpm,
|
|
||||||
endBpm = targetBpm,
|
|
||||||
durationMs = 800L,
|
|
||||||
tempoType = "Retour tempo"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
||||||
}
|
}
|
||||||
|
|
@ -281,14 +229,8 @@ actual class FMediaPlayer actual constructor(
|
||||||
var clockTick = currentTickPos
|
var clockTick = currentTickPos
|
||||||
|
|
||||||
while (isActive && isRunning) {
|
while (isActive && isRunning) {
|
||||||
if (needsClockSync) {
|
|
||||||
clockNano = System.nanoTime()
|
|
||||||
clockTick = currentTickPos
|
|
||||||
needsClockSync = false
|
|
||||||
}
|
|
||||||
if (isHolding) {
|
if (isHolding) {
|
||||||
yield()
|
delay(10)
|
||||||
// delay(10)
|
|
||||||
clockNano = System.nanoTime()
|
clockNano = System.nanoTime()
|
||||||
clockTick = currentTickPos
|
clockTick = currentTickPos
|
||||||
continue
|
continue
|
||||||
|
|
@ -310,24 +252,10 @@ actual class FMediaPlayer actual constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
val ev = events[idx]
|
val ev = events[idx]
|
||||||
// val waitNano = (clockNano + ((ev.tickAbsolute - clockTick) * usPerTick * 1000)
|
val waitNano = (clockNano + ((ev.tickAbsolute - clockTick) * usPerTick * 1000)
|
||||||
// .toLong()) - System.nanoTime()
|
.toLong()) - System.nanoTime()
|
||||||
|
if (waitNano > 0) delay((waitNano / 1_000_000).coerceAtLeast(0L))
|
||||||
|
|
||||||
val targetNano = clockNano + ((ev.tickAbsolute - clockTick) * usPerTick * 1000).toLong()
|
|
||||||
val now = System.nanoTime()
|
|
||||||
val waitNano = targetNano - now
|
|
||||||
|
|
||||||
// if (waitNano > 0) delay((waitNano / 1_000_000).coerceAtLeast(0L))
|
|
||||||
if (waitNano > 0) {
|
|
||||||
// Thread.sleep est beaucoup plus précis que delay() pour les micro-délais MIDI
|
|
||||||
val ms = waitNano / 1_000_000
|
|
||||||
val ns = (waitNano % 1_000_000).toInt()
|
|
||||||
try {
|
|
||||||
Thread.sleep(ms, ns)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
yield()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
currentTickPos = ev.tickAbsolute
|
currentTickPos = ev.tickAbsolute
|
||||||
lastEventNano = System.nanoTime()
|
lastEventNano = System.nanoTime()
|
||||||
|
|
||||||
|
|
@ -350,10 +278,6 @@ 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()
|
|
||||||
if (boundModel?.activeIndex?.value != currentGrid.toInt()) {
|
|
||||||
boundModel?.updateActiveIndex(currentGrid)
|
|
||||||
}
|
|
||||||
idx++
|
idx++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -387,17 +311,12 @@ actual class FMediaPlayer actual constructor(
|
||||||
private fun prepareNavigation(sharedScreenModel: SharedScreenModel) {
|
private fun prepareNavigation(sharedScreenModel: SharedScreenModel) {
|
||||||
val metadataList = sharedScreenModel.getFullMarkers()
|
val metadataList = sharedScreenModel.getFullMarkers()
|
||||||
navigationSteps.clear()
|
navigationSteps.clear()
|
||||||
var lastSegno=0;
|
var lastSegno=0; var lastFactor=Dynamic.MF.factor
|
||||||
var lastFactor=Dynamic.MF.factor
|
|
||||||
val ritRegex = Regex("""rit\.?|ritard\.?|ritenuto\.?|ritardando""", RegexOption.IGNORE_CASE)
|
|
||||||
val rallRegex = Regex("""rall\.?|rallent\.?|rallentando""", RegexOption.IGNORE_CASE)
|
|
||||||
|
|
||||||
metadataList.forEach { (timestamp, gridIndex, template, lastCallerMarker, marker, noteBefore, separat, note) ->
|
metadataList.forEach { (_, gridIndex, _, _, marker, _, _, note) ->
|
||||||
val ci = gridIndex ?: 0
|
val ci = 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 dcG = Regex("""D\.?C\.?_GROUP_PART""")
|
||||||
val dsG = Regex("""D\.?S\.?_GROUP_PART""");
|
|
||||||
val dcG = Regex("""D\.?C\.?_GROUP_PART""")
|
|
||||||
val last_grid = sharedScreenModel.getTotalGridCount()
|
val last_grid = sharedScreenModel.getTotalGridCount()
|
||||||
val hairPins = sharedScreenModel.getHairPins()
|
val hairPins = sharedScreenModel.getHairPins()
|
||||||
|
|
||||||
|
|
@ -408,32 +327,7 @@ actual class FMediaPlayer actual constructor(
|
||||||
val beat = if(note.contains('•')) 2 else 1
|
val beat = if(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()) -> {
|
dsG.matches(marker.trim()) -> {
|
||||||
val target = if(lastSegno>0) lastSegno else 0
|
val target = if(lastSegno>0) lastSegno else 0
|
||||||
val indx = if((last_grid-ci)<=0) ci-1 else ci
|
val indx = if((last_grid-ci)<=0) ci-1 else ci
|
||||||
|
|
@ -512,21 +406,15 @@ actual class FMediaPlayer actual constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startNavigationMonitor(sharedScreenModel: SharedScreenModel) {
|
private fun startNavigationMonitor(sharedScreenModel: SharedScreenModel) {
|
||||||
var lastProcessedIndex = -1
|
|
||||||
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) {
|
|
||||||
return@collect
|
|
||||||
}
|
|
||||||
lastProcessedIndex = currentIndex
|
|
||||||
if (!isRunning || currentIndex < 0) return@collect
|
if (!isRunning || currentIndex < 0) return@collect
|
||||||
|
|
||||||
val step = navigationSteps.find {
|
val step = navigationSteps.find {
|
||||||
it.gridIndex == currentIndex && !it.alreadyDone
|
it.gridIndex == currentIndex && !it.alreadyDone
|
||||||
}
|
} ?: return@collect
|
||||||
println("pas: ${currentIndex}")
|
|
||||||
if (step != null) {
|
|
||||||
when {
|
when {
|
||||||
// ── Farany ────────────────────────────
|
// ── Farany ────────────────────────────
|
||||||
step.isFarany -> {
|
step.isFarany -> {
|
||||||
|
|
@ -555,25 +443,6 @@ actual class FMediaPlayer actual constructor(
|
||||||
for (ch in 0 until 4) controlChange(ch, 64, 0)
|
for (ch in 0 until 4) controlChange(ch, 64, 0)
|
||||||
step.alreadyDone = true
|
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}")
|
|
||||||
println(" $startBpm BPM → $endBpm BPM sur ${durationMs}ms")
|
|
||||||
|
|
||||||
applyTempoChange(
|
|
||||||
startBpm = startBpm,
|
|
||||||
endBpm = endBpm,
|
|
||||||
durationMs = durationMs,
|
|
||||||
tempoType = step.tempoType
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Soufflet ──────────────────────────
|
// ── Soufflet ──────────────────────────
|
||||||
step.hairPin != null -> {
|
step.hairPin != null -> {
|
||||||
|
|
@ -620,7 +489,6 @@ actual class FMediaPlayer actual constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private fun startSyncLoop(sharedScreenModel: SharedScreenModel) {
|
private fun startSyncLoop(sharedScreenModel: SharedScreenModel) {
|
||||||
syncJob?.cancel()
|
syncJob?.cancel()
|
||||||
|
|
@ -654,7 +522,6 @@ actual class FMediaPlayer actual constructor(
|
||||||
allNotesOff(); currentTickPos = 0L
|
allNotesOff(); currentTickPos = 0L
|
||||||
resetNavigationFlags(); navigationSteps.clear(); clearLoop()
|
resetNavigationFlags(); navigationSteps.clear(); clearLoop()
|
||||||
currentDynamicFactor = Dynamic.MF.factor
|
currentDynamicFactor = Dynamic.MF.factor
|
||||||
resetTempoToNormal()
|
|
||||||
}
|
}
|
||||||
actual fun release() {
|
actual fun release() {
|
||||||
stop(); midiDriver.stop(); playerScope.cancel()
|
stop(); midiDriver.stop(); playerScope.cancel()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue