Compare commits
No commits in common. "1a928ab4b08fa6bdbe61af1e154deff4677d74e8" and "b7d4fba9a9c7286fd24bf32191fd218c098b61f7" have entirely different histories.
1a928ab4b0
...
b7d4fba9a9
6 changed files with 215 additions and 338 deletions
|
|
@ -19,10 +19,15 @@ expect class FMediaPlayer(filename: String, onFinished: () -> Unit) {
|
||||||
fun setPointA()
|
fun setPointA()
|
||||||
fun setPointB()
|
fun setPointB()
|
||||||
fun clearLoop()
|
fun clearLoop()
|
||||||
fun setTempo(bpm: Float)
|
fun setTempo(factor: Float)
|
||||||
fun getCurrentBPM(): Float
|
fun getCurrentBPM(): Float
|
||||||
fun updateVoiceVolume(voiceIndex: Int, newVolume: Float)
|
fun updateVoiceVolume(voiceIndex: Int, newVolume: Float)
|
||||||
fun requestSync(sharedScreenModel: SharedScreenModel)
|
fun requestSync(sharedScreenModel: SharedScreenModel)
|
||||||
fun syncNavigationMonitor(sharedScreenModel: SharedScreenModel)
|
fun syncNavigationMonitor(sharedScreenModel: SharedScreenModel)
|
||||||
fun release()
|
fun release()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
expect fun MidiPlayer(filename: String, onFinished: () -> Unit)
|
||||||
|
|
||||||
|
expect fun StopMidi()*/
|
||||||
|
|
|
||||||
|
|
@ -593,34 +593,7 @@ fun LazyVerticalGridTUO(
|
||||||
-1
|
-1
|
||||||
}*/
|
}*/
|
||||||
val measures = tuoList.drop(1).chunked(gridColumnCount)
|
val measures = tuoList.drop(1).chunked(gridColumnCount)
|
||||||
// Avant column affichage:
|
val metadataList = mutableListOf<MidiMarkers>()
|
||||||
val metadataList = remember(tuoList) {
|
|
||||||
tuoList.drop(1).mapIndexedNotNull { globalIndex, oneTUO ->
|
|
||||||
val markerText = oneTUO.pTemplate.markerToString()
|
|
||||||
if (markerText.isNotEmpty()) {
|
|
||||||
val myTimestamp = sharedScreenModel.tuoTimestamps.value.getOrElse(globalIndex) { 0L }
|
|
||||||
MidiMarkers(
|
|
||||||
myTimestamp,
|
|
||||||
globalIndex,
|
|
||||||
oneTUO.pTemplate.template,
|
|
||||||
oneTUO.pTemplate.lastCalledMarker,
|
|
||||||
markerText,
|
|
||||||
oneTUO.prevTUO?.pTemplate?.template ?: "",
|
|
||||||
oneTUO.sep0,
|
|
||||||
oneTUO.tuNotes.getOrNull(1).toString()
|
|
||||||
)
|
|
||||||
|
|
||||||
} else null
|
|
||||||
}.distinctBy { it.gridIndex }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Envoyer les données au ViewModel une seule fois
|
|
||||||
LaunchedEffect(metadataList) {
|
|
||||||
if (metadataList.isNotEmpty()) {
|
|
||||||
println("Mise à jour MidiData avec ${metadataList.size}")
|
|
||||||
sharedScreenModel.updateAndFinalizeMidiData(metadataList)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
){
|
){
|
||||||
|
|
@ -726,6 +699,20 @@ fun LazyVerticalGridTUO(
|
||||||
|
|
||||||
val myTimestamp = sharedScreenModel.tuoTimestamps.value.getOrElse(globalIndex) { 0L }
|
val myTimestamp = sharedScreenModel.tuoTimestamps.value.getOrElse(globalIndex) { 0L }
|
||||||
|
|
||||||
|
if(oneTUO.pTemplate.markerToString() != "") {
|
||||||
|
val nott = oneTUO.tuNotes.getOrNull(1)
|
||||||
|
metadataList.add(
|
||||||
|
MidiMarkers(
|
||||||
|
myTimestamp,
|
||||||
|
oneTUO.pTemplate.template,
|
||||||
|
oneTUO.pTemplate.lastCalledMarker,
|
||||||
|
oneTUO.pTemplate.markerToString(),
|
||||||
|
oneTUO.prevTUO!!.pTemplate!!.template,
|
||||||
|
oneTUO.sep0,
|
||||||
|
oneTUO.tuNotes.getOrNull(1).toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
Box(modifier = Modifier.weight(1f)
|
Box(modifier = Modifier.weight(1f)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
|
|
@ -736,9 +723,8 @@ fun LazyVerticalGridTUO(
|
||||||
} ,
|
} ,
|
||||||
onDoubleClick = {
|
onDoubleClick = {
|
||||||
val m = sharedScreenModel.getFullMarkers()
|
val m = sharedScreenModel.getFullMarkers()
|
||||||
m.forEach { (timestamp, gridIndex, template, lastCallerMarker, marker, noteBefore, separat, note) ->
|
m.forEach { (timestamp, template, lastCallerMarker, marker, noteBefore, separat, note) ->
|
||||||
println("Allmarker $marker in $gridIndex on $timestamp ms")
|
println("Allmarker : $marker in $timestamp ms & note= $note & nBefore =$noteBefore & separateur = $separat")
|
||||||
// & note= $note & nBefore =$noteBefore & separateur = $separat
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)) {
|
)) {
|
||||||
|
|
|
||||||
|
|
@ -70,8 +70,9 @@ fun MidiControlPanel(
|
||||||
val labels = listOf("S", "A", "T", "B")
|
val labels = listOf("S", "A", "T", "B")
|
||||||
listOf("Soprano", "Alto", "Ténor", "Basse")
|
listOf("Soprano", "Alto", "Ténor", "Basse")
|
||||||
|
|
||||||
var tempo by remember { mutableStateOf(120.toFloat()) }
|
var tempo by remember { mutableStateOf(1.0f) }
|
||||||
var currentBpm by remember { mutableStateOf(mediaPlayer.getCurrentBPM()) }
|
var currentBpm by remember { mutableStateOf(mediaPlayer.getCurrentBPM()) }
|
||||||
|
val basseBpm = 120f
|
||||||
|
|
||||||
var isPianoSelected by remember { mutableStateOf(true) }
|
var isPianoSelected by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
|
@ -100,16 +101,23 @@ fun MidiControlPanel(
|
||||||
LaunchedEffect(tempo) {
|
LaunchedEffect(tempo) {
|
||||||
currentBpm = mediaPlayer.getCurrentBPM()
|
currentBpm = mediaPlayer.getCurrentBPM()
|
||||||
}
|
}
|
||||||
fun updateTempoToBpm(newBpm: Int) {
|
|
||||||
tempo = newBpm.toFloat()
|
|
||||||
mediaPlayer?.setTempo(tempo)
|
|
||||||
}
|
|
||||||
fun updateTempoByBpmStep(step: Int) {
|
fun updateTempoByBpmStep(step: Int) {
|
||||||
val currentBpm = mediaPlayer!!.getCurrentBPM()
|
val currentBpm = (basseBpm * tempo).toInt()
|
||||||
val nextBpm = currentBpm.toInt() + step
|
val newBpm = (currentBpm + step).coerceIn((basseBpm * 0.25f).toInt(), (basseBpm * 1.5f).toInt())
|
||||||
updateTempoToBpm(nextBpm)
|
val newFactor = newBpm.toFloat() / basseBpm
|
||||||
|
|
||||||
|
tempo = newFactor
|
||||||
|
mediaPlayer?.setTempo(newFactor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun updateTempoToBpm(targetBpm: Int) {
|
||||||
|
val clampedBpm = targetBpm.coerceIn((basseBpm * 0.25f).toInt(), (basseBpm * 1.5f).toInt())
|
||||||
|
val newFactor = clampedBpm.toFloat() / basseBpm
|
||||||
|
|
||||||
|
tempo = newFactor
|
||||||
|
mediaPlayer?.setTempo(newFactor)
|
||||||
|
// println("tempo : $tempo")
|
||||||
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
|
|
@ -244,7 +252,7 @@ fun MidiControlPanel(
|
||||||
.padding(vertical = 6.dp),
|
.padding(vertical = 6.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
if (tempo >= 40.0) { // limite 40BPM
|
if (tempo >= 0.3) { // limite 40BPM
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.background(Color(0XFF2C3130)),
|
modifier = Modifier.background(Color(0XFF2C3130)),
|
||||||
onClick = { updateTempoByBpmStep(-10) }) {
|
onClick = { updateTempoByBpmStep(-10) }) {
|
||||||
|
|
@ -257,7 +265,7 @@ fun MidiControlPanel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentBpmInt = (tempo).toInt()
|
val currentBpmInt = (basseBpm * tempo).toInt()
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(horizontal = 2.dp),
|
modifier = Modifier.padding(horizontal = 2.dp),
|
||||||
|
|
@ -292,7 +300,7 @@ fun MidiControlPanel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tempo <= 160) { // limite 160BPM
|
if (tempo <= 1.3) { // limite 156BPM
|
||||||
IconButton(
|
IconButton(
|
||||||
modifier = Modifier.background(Color(0XFF2C3130)),
|
modifier = Modifier.background(Color(0XFF2C3130)),
|
||||||
onClick = { updateTempoByBpmStep(10) }) {
|
onClick = { updateTempoByBpmStep(10) }) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package mg.dot.feufaro.viewmodel
|
||||||
|
|
||||||
data class MidiMarkers(
|
data class MidiMarkers(
|
||||||
val timestamp: Long,
|
val timestamp: Long,
|
||||||
val gridIndex: Int? = 0,
|
|
||||||
val template: String,
|
val template: String,
|
||||||
val lastCallerMarker: Int,
|
val lastCallerMarker: Int,
|
||||||
val marker: String,
|
val marker: String,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import cafe.adriel.voyager.core.model.ScreenModel
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
@ -142,15 +143,9 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
|
|
||||||
private val _midiMarkersList = MutableStateFlow<List<MidiMarkers>>(emptyList())
|
private val _midiMarkersList = MutableStateFlow<List<MidiMarkers>>(emptyList())
|
||||||
val midiMarkersList: StateFlow<List<MidiMarkers>> = _midiMarkersList
|
val midiMarkersList: StateFlow<List<MidiMarkers>> = _midiMarkersList
|
||||||
private val _activeIndex = MutableStateFlow(-1)
|
|
||||||
val activeIndex: StateFlow<Int> = _activeIndex.asStateFlow()
|
|
||||||
|
|
||||||
fun updateActiveIndex(currentPosMs: Long) {
|
fun updateMidiData(newList: List<MidiMarkers>) {
|
||||||
val currentPosMicros = currentPosMs * 1000
|
_midiMarkersList.value = newList
|
||||||
val index = _tuoTimestamps.value.indexOfLast { it <= currentPosMicros }
|
|
||||||
if (_activeIndex.value != index) {
|
|
||||||
_activeIndex.value = index.coerceAtLeast(0)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getFullMarkers(): List<MidiMarkers> {
|
fun getFullMarkers(): List<MidiMarkers> {
|
||||||
|
|
@ -170,90 +165,58 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
return _dsDone.value
|
return _dsDone.value
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTotalGridCount(): Int {
|
fun finalizeMarkers() {
|
||||||
return _tuoList.value.drop(1).size - 1
|
val timestamps = _tuoTimestamps.value
|
||||||
}
|
|
||||||
fun updateAndFinalizeMidiData(rawList: List<MidiMarkers>) {
|
|
||||||
// val timestamps = _tuoTimestamps.value
|
|
||||||
val tuos = _tuoList.value.drop(1)
|
val tuos = _tuoList.value.drop(1)
|
||||||
|
val newMtdList = mutableListOf<MidiMarkers>()
|
||||||
|
|
||||||
val finalizedList = rawList.map { marker ->
|
tuos.forEachIndexed { index, tuo ->
|
||||||
var markerText = marker.marker
|
val markerText = tuo.pTemplate.markerToString()
|
||||||
val index = marker.gridIndex ?: 0 // On utilise l'index passé par l'UI
|
if (markerText.isNotEmpty()) {
|
||||||
val isNearEnd = index >= (tuos.size - 2)
|
val ts = timestamps.getOrNull(index) ?: 0L
|
||||||
|
newMtdList.add(
|
||||||
|
MidiMarkers(
|
||||||
|
timestamp = ts,
|
||||||
|
template = tuo.pTemplate.template,
|
||||||
|
lastCallerMarker = tuo.pTemplate.lastCalledMarker,
|
||||||
|
marker = markerText,
|
||||||
|
noteBefore = tuo.prevTUO!!.pTemplate!!.template,
|
||||||
|
separat = tuo.sep0,
|
||||||
|
note = tuo.tuNotes.getOrNull(1).toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if(markerText.contains("DC")){ // marquer la fin de mesure après un D.C.
|
||||||
|
var internIn = index +1
|
||||||
|
while (internIn < tuos.size) {
|
||||||
|
val currentTuo = tuos[internIn]
|
||||||
|
val currentNote = currentTuo.tuNotes.getOrNull(1)?.toString() ?: ""
|
||||||
|
val currentSep = currentTuo.sep0 ?: ""
|
||||||
|
|
||||||
val isDC = markerText.contains(Regex("""D\.?C\.?"""))
|
// On se position sur la note apres les tiret "-" ou sur /
|
||||||
val isDS = markerText.contains(Regex("""D\.?S\.?"""))
|
if ((currentNote != "―" && currentNote.isNotBlank()) || (currentSep == "/")) {
|
||||||
|
val currentTs = timestamps.getOrNull(internIn) ?: 0L
|
||||||
var resultMarker: MidiMarkers = marker
|
println("Dernier '-' passé. Nouvelle note '$currentNote' trouvée à l'index $internIn (Position du saut)")
|
||||||
|
newMtdList.clear()
|
||||||
if (isDC) {
|
newMtdList.add(
|
||||||
var forwardIndex = index
|
MidiMarkers(
|
||||||
while (forwardIndex < tuos.size) {
|
timestamp = currentTs,
|
||||||
val currentTuo = tuos.getOrNull(forwardIndex)
|
template = currentTuo.pTemplate.template,
|
||||||
val currentNote = currentTuo?.tuNotes?.getOrNull(1)?.toString() ?: ""
|
lastCallerMarker = currentTuo.pTemplate.lastCalledMarker,
|
||||||
val currentSep = currentTuo?.sep0 ?: ""
|
marker = "DC_GROUP_PART",
|
||||||
|
noteBefore = currentTuo.prevTUO!!.pTemplate!!.template,
|
||||||
// println("Je suis sur $forwardIndex note $currentNote Sep $currentSep \t condition: ${(currentNote == "―")} || ${(currentSep == "/")}")
|
separat = currentTuo.sep0,
|
||||||
// Tant que fin de mesure
|
note = currentNote
|
||||||
if (currentSep == "/") {
|
)
|
||||||
resultMarker = marker.copy(
|
|
||||||
gridIndex = forwardIndex-1,
|
|
||||||
marker = "${markerText.trim()}_GROUP_PART"
|
|
||||||
)
|
)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
forwardIndex++
|
internIn++
|
||||||
}
|
|
||||||
|
|
||||||
} else if (isDS && isNearEnd) {
|
|
||||||
val currentNote = tuos.getOrNull(index)?.tuNotes?.getOrNull(1)?.toString() ?: ""
|
|
||||||
val nextNote = tuos.getOrNull(index + 1)?.tuNotes?.getOrNull(1)?.toString() ?: ""
|
|
||||||
|
|
||||||
// Cas où le marker et la note suivante sont vides
|
|
||||||
val isEmptySituation = currentNote.trim().isEmpty() && nextNote.trim().isEmpty()
|
|
||||||
|
|
||||||
if (isEmptySituation) {
|
|
||||||
// vers arrière une note non vide
|
|
||||||
var backwardIndex = index - 1
|
|
||||||
while (backwardIndex >= 0) {
|
|
||||||
val note = tuos.getOrNull(backwardIndex)?.tuNotes?.getOrNull(1)?.toString() ?: ""
|
|
||||||
if (note.trim().isNotEmpty()) {
|
|
||||||
val cleanText = if (markerText.trim() == "DSFin") "DS" else markerText.trim()
|
|
||||||
|
|
||||||
resultMarker = marker.copy(
|
|
||||||
gridIndex = backwardIndex,
|
|
||||||
marker = "${cleanText}_GROUP_PART"
|
|
||||||
)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
backwardIndex--
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// vers avant le séparateur "/"
|
|
||||||
var forwardIndex = index
|
|
||||||
while (forwardIndex < tuos.size) {
|
|
||||||
val sep = tuos.getOrNull(forwardIndex)?.sep0 ?: ""
|
|
||||||
if (sep == "/") {
|
|
||||||
val cleanText = if (markerText.trim() == "DSFin") "DS" else markerText.trim()
|
|
||||||
|
|
||||||
resultMarker = marker.copy(
|
|
||||||
gridIndex = forwardIndex,
|
|
||||||
marker = "${cleanText}_GROUP_PART"
|
|
||||||
)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
forwardIndex++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
marker
|
|
||||||
}
|
}
|
||||||
resultMarker
|
|
||||||
}
|
}
|
||||||
|
updateMidiData(newMtdList)
|
||||||
_midiMarkersList.value = finalizedList
|
println("Markers loaded >> ok")
|
||||||
println("Markers finalisés et mis à jour : ${finalizedList.size}")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadNewSong(newMidiFile: String) {
|
fun loadNewSong(newMidiFile: String) {
|
||||||
|
|
@ -281,7 +244,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
})
|
})
|
||||||
// synchro
|
// synchro
|
||||||
_mediaPlayer?.requestSync(this)
|
_mediaPlayer?.requestSync(this)
|
||||||
//finalizeMarkers()
|
finalizeMarkers()
|
||||||
_mediaPlayer?.syncNavigationMonitor(this)
|
_mediaPlayer?.syncNavigationMonitor(this)
|
||||||
} catch(e: Exception) {
|
} catch(e: Exception) {
|
||||||
println("Erreur d'ouverture de mediaPlayer / ")
|
println("Erreur d'ouverture de mediaPlayer / ")
|
||||||
|
|
@ -305,7 +268,7 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
|
||||||
player.seekTo(0)
|
player.seekTo(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// println("128: Status de isPlay ${_isPlay.value} \nisPos ${_isPos.value} \ncurrentPos ${_currentPos.value} \n volume ${_volumeLevel.value}")
|
println("128: Status de isPlay ${_isPlay.value} \nisPos ${_isPos.value} \ncurrentPos ${_currentPos.value} \n volume ${_volumeLevel.value}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fun stopMidi() {
|
fun stopMidi() {
|
||||||
|
|
|
||||||
|
|
@ -39,22 +39,20 @@ actual class FMediaPlayer actual constructor(
|
||||||
private var currentGlobalVolume: Float = 0.8f
|
private var currentGlobalVolume: Float = 0.8f
|
||||||
|
|
||||||
private var currentTempo: Float = 1.0f
|
private var currentTempo: Float = 1.0f
|
||||||
private var targetBpm: Float = 120f
|
|
||||||
private val playerScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
private val playerScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||||
private var abJob: Job? = null
|
private var abJob: Job? = null
|
||||||
|
|
||||||
private var navigationJob: Job? = null
|
private var navigationJob: Job? = null
|
||||||
private data class NavigationStep(
|
private data class NavigationStep(
|
||||||
val marker: String,
|
val triggerMs: Long, // DC | DS
|
||||||
val gridIndex: Int, // L'ancre absolue
|
val targetMs: Long, // farany, $ ,..
|
||||||
val targetGrid: Int = 0,
|
|
||||||
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
|
||||||
|
var sustainNedd: Boolean = true
|
||||||
)
|
)
|
||||||
private val navigationSteps = mutableListOf<NavigationStep>()
|
private val navigationSteps = mutableListOf<NavigationStep>()
|
||||||
|
|
||||||
private var boundModel: SharedScreenModel? = null
|
|
||||||
init {
|
init {
|
||||||
try {
|
try {
|
||||||
sequencer?.open()
|
sequencer?.open()
|
||||||
|
|
@ -70,8 +68,6 @@ actual class FMediaPlayer actual constructor(
|
||||||
if (file.exists()) {
|
if (file.exists()) {
|
||||||
val mySequence = MidiSystem.getSequence(file)
|
val mySequence = MidiSystem.getSequence(file)
|
||||||
sequencer?.sequence = mySequence
|
sequencer?.sequence = mySequence
|
||||||
stripTempoEvents(mySequence)
|
|
||||||
sequencer?.tempoFactor = 1.0f
|
|
||||||
loadVoiceVolumes()
|
loadVoiceVolumes()
|
||||||
applyVoiceStates()
|
applyVoiceStates()
|
||||||
sequencer?.addMetaEventListener { meta ->
|
sequencer?.addMetaEventListener { meta ->
|
||||||
|
|
@ -79,15 +75,6 @@ actual class FMediaPlayer actual constructor(
|
||||||
onFinished()
|
onFinished()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
sequencer?.addMetaEventListener { meta ->
|
|
||||||
if (meta.type == 0x51) { // 0x51 "Set Tempo"
|
|
||||||
if (sequencer?.tempoInBPM != targetBpm) {
|
|
||||||
println("Tempo MIDI détecté (120), forçage à $targetBpm")
|
|
||||||
applyBpm()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
} else {
|
} else {
|
||||||
// Créeons une fichier vide au 1er lancement de l'application, après MidiWriterKotlin l'écrasera
|
// Créeons une fichier vide au 1er lancement de l'application, après MidiWriterKotlin l'écrasera
|
||||||
val f0file = File("${getConfigDirectoryPath()}whawyd3.mid")
|
val f0file = File("${getConfigDirectoryPath()}whawyd3.mid")
|
||||||
|
|
@ -95,115 +82,45 @@ actual class FMediaPlayer actual constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stripTempoEvents(sequence: Sequence) {
|
|
||||||
for (track in sequence.tracks) {
|
|
||||||
val eventsToRemove = mutableListOf<javax.sound.midi.MidiEvent>()
|
|
||||||
for (i in 0 until track.size()) {
|
|
||||||
val event = track.get(i)
|
|
||||||
val message = event.message
|
|
||||||
if (message is MetaMessage && message.type == 0x51) { // 0x51 = Set Tempo
|
|
||||||
eventsToRemove.add(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
eventsToRemove.forEach { track.remove(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private fun applyBpm() {
|
|
||||||
sequencer?.tempoInBPM = targetBpm
|
|
||||||
sequencer?.tempoFactor = 1.0f
|
|
||||||
}
|
|
||||||
private fun forceTempo(bpm: Double) {
|
|
||||||
sequencer?.tempoInBPM = bpm.toFloat()
|
|
||||||
sequencer?.tempoFactor = 1.0f
|
|
||||||
}
|
|
||||||
private fun resetNavigationFlags() {
|
private fun resetNavigationFlags() {
|
||||||
navigationSteps.forEach { it.alreadyDone = false }
|
navigationSteps.forEach { it.alreadyDone = false }
|
||||||
}
|
}
|
||||||
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 holdPos: Long = 0L
|
||||||
|
|
||||||
metadataList.forEach { (timestamp, gridIndex, template, lastCallerMarker, marker, noteBefore, separat, note) ->
|
val currentBpm = sequencer?.tempoInBPM ?: 120f
|
||||||
val currentIndex = gridIndex ?: 0
|
val beatDurationMs = (60_000 / currentBpm).toLong()
|
||||||
|
|
||||||
val dsRegex = Regex("""D\.?S\.?""")
|
metadataList.forEach { (timestamp, template, lastCallerMarker, marker, noteBefore, separat, note) ->
|
||||||
val dcRegex = Regex("""D\.?C\.?""")
|
val timeMs = (timestamp / 1000)
|
||||||
val dsGPattern = Regex("""D\.?S\.?_GROUP_PART""")
|
|
||||||
val dcGPattern = Regex("""D\.?C\.?_GROUP_PART""")
|
|
||||||
|
|
||||||
val last_grid = sharedScreenModel.getTotalGridCount()
|
|
||||||
when {
|
when {
|
||||||
marker.contains("$") -> {
|
|
||||||
lastSegno = currentIndex
|
|
||||||
println("Cible ($) mémorisée au $lastSegno")
|
|
||||||
}
|
|
||||||
marker.contains("\uD834\uDD10") -> {
|
marker.contains("\uD834\uDD10") -> {
|
||||||
|
holdPos = timeMs
|
||||||
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(FMediaPlayer.NavigationStep(timeMs, -1L, isHold = true, beatInDC = beat))
|
||||||
FMediaPlayer.NavigationStep(
|
println("Point d'orgue (\uD834\uDD10) mémorisée à $holdPos ms")
|
||||||
marker,
|
|
||||||
currentIndex,
|
|
||||||
isHold = true,
|
|
||||||
beatInDC = beat
|
|
||||||
)
|
|
||||||
)
|
|
||||||
println("Point d'orgue (\uD834\uDD10) mémorisée au grille n° $gridIndex")
|
|
||||||
}
|
}
|
||||||
/*// DS
|
// Déclencheurs DC
|
||||||
dsGPattern.matches(marker.trim())-> {
|
marker == "DC_GROUP_PART" -> {
|
||||||
val target = if (lastSegno > 0) lastSegno else 0
|
var needSustaine = false
|
||||||
var indx = if((last_grid - currentIndex) <= 0) {
|
var trigger = timeMs - (beatDurationMs / 2)
|
||||||
currentIndex -1
|
|
||||||
} else currentIndex
|
|
||||||
navigationSteps.add(
|
|
||||||
NavigationStep(
|
|
||||||
marker,
|
|
||||||
gridIndex = indx,
|
|
||||||
targetGrid = target,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
println("Lien créé : $marker à grille n° $indx vers cible $target ........")
|
|
||||||
}
|
|
||||||
dsRegex.matches(marker.trim()) || marker == "DSFin" -> {
|
|
||||||
val target = if (lastSegno > 0) lastSegno else 0
|
|
||||||
|
|
||||||
navigationSteps.add(
|
navigationSteps.add(NavigationStep(trigger, targetMs = 0L, sustainNedd = needSustaine))
|
||||||
NavigationStep(
|
println("Lien créé : $marker à $trigger ms vers cible 0 ms Avec Sustaine? $needSustaine")
|
||||||
marker,
|
|
||||||
currentIndex,
|
|
||||||
targetGrid = target
|
|
||||||
))
|
|
||||||
println("Lien DS créé : Saut immédiat à $gridIndex vers Segno $target")
|
|
||||||
}*/
|
|
||||||
|
|
||||||
// DC
|
|
||||||
dcGPattern.matches(marker.trim()) -> {
|
|
||||||
var indx = if((last_grid - currentIndex) <= 0) {
|
|
||||||
currentIndex - 1
|
|
||||||
} else currentIndex
|
|
||||||
navigationSteps.add(
|
|
||||||
NavigationStep(
|
|
||||||
marker,
|
|
||||||
indx,
|
|
||||||
targetGrid = 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
println("Lien créé : $marker à $indx vers cible 0 ")
|
|
||||||
}
|
}
|
||||||
(dcRegex.matches(marker.trim()) && !marker.contains("DC_GROUP_PART")) -> {
|
/* Un Dc se place sur une note '-'*/
|
||||||
println("dernier grille $last_grid")
|
marker == "DC" -> {
|
||||||
var indx = if((last_grid - currentIndex) <= 0) {
|
var needSustaine = false
|
||||||
currentIndex /*- 1*/
|
val trigger = timeMs
|
||||||
} else currentIndex
|
// Si le marker si place près de la fin de la partition
|
||||||
navigationSteps.add(
|
if((getDuration() - timeMs) <= beatDurationMs) {
|
||||||
NavigationStep(
|
println("Fin très proche")
|
||||||
marker,
|
needSustaine = true
|
||||||
indx,
|
}
|
||||||
targetGrid = 0
|
navigationSteps.add(NavigationStep(trigger, targetMs = 0, sustainNedd = needSustaine))
|
||||||
)
|
println("Lien créé : $marker à $trigger ms vers le début")
|
||||||
)
|
|
||||||
println("Lien DC créé : $marker à $indx vers le début")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -212,101 +129,90 @@ actual class FMediaPlayer actual constructor(
|
||||||
var dcDone = sharedScreenModel.getDcDone()
|
var dcDone = sharedScreenModel.getDcDone()
|
||||||
val dsDone = sharedScreenModel.getDsDone()
|
val dsDone = sharedScreenModel.getDsDone()
|
||||||
|
|
||||||
|
// la durée d'un temps (noire)
|
||||||
|
val currentBpm = sequencer?.tempoInBPM ?: 120f
|
||||||
|
val beatDurationMs = (60_000 / currentBpm).toLong()
|
||||||
|
|
||||||
|
val remainingTime = getDuration() - getCurrentPosition()
|
||||||
|
|
||||||
navigationJob?.cancel()
|
navigationJob?.cancel()
|
||||||
navigationJob = playerScope.launch(Dispatchers.Default) {
|
navigationJob = playerScope.launch {
|
||||||
sharedScreenModel.activeIndex.collect { currentIndex ->
|
|
||||||
|
|
||||||
val availableIndices = navigationSteps.map { it.gridIndex }
|
|
||||||
println("bpm:$targetBpm _ ${sequencer?.tempoInBPM}")
|
|
||||||
println("MONITOR : Reçu Index $currentIndex | Index en mémoire : $availableIndices ")
|
|
||||||
|
|
||||||
if (sequencer?.isRunning == true) {
|
|
||||||
if (Math.abs(sequencer!!.tempoInBPM - targetBpm) > 0.1) {
|
|
||||||
forceTempo(targetBpm.toDouble())
|
|
||||||
}
|
|
||||||
if (currentIndex < 0) return@collect
|
|
||||||
|
|
||||||
val step = navigationSteps.find {
|
|
||||||
it.gridIndex == currentIndex &&
|
|
||||||
!it.alreadyDone
|
|
||||||
}
|
|
||||||
|
|
||||||
if (step != null) {
|
|
||||||
// Point d'orgue
|
|
||||||
if (step.isHold) {
|
|
||||||
val currentBpm = sequencer?.tempoInBPM ?: targetBpm
|
|
||||||
val beatMs = (60_000 / currentBpm).toLong()
|
|
||||||
val holdDuration = if (step.beatInDC == 2) beatMs / 2 else beatMs * 2
|
|
||||||
|
|
||||||
println("POINT D'ORGUE sur Grille $currentIndex | Durée: ${holdDuration}ms")
|
|
||||||
|
|
||||||
synthetizer?.channels?.forEach { it?.controlChange(64, 127) }
|
|
||||||
val previousFactor = sequencer?.tempoFactor ?: 1.0f
|
|
||||||
sequencer?.tempoFactor = 0.0001f
|
|
||||||
|
|
||||||
delay(holdDuration)
|
|
||||||
|
|
||||||
sequencer?.tempoFactor = previousFactor
|
|
||||||
synthetizer?.channels?.forEach { it?.controlChange(64, 0) }
|
|
||||||
} else {
|
|
||||||
// $ dc ds ...
|
|
||||||
step.alreadyDone = true
|
|
||||||
|
|
||||||
val currentBpm = targetBpm
|
|
||||||
val beatDuration = (60_000 / currentBpm).toLong()
|
|
||||||
println("avant de sauter bpm=$targetBpm")
|
|
||||||
|
|
||||||
synthetizer?.channels?.forEach { it?.controlChange(64, 127) }
|
|
||||||
sequencer?.tempoFactor = 0.0001f
|
|
||||||
delay(beatDuration)
|
|
||||||
|
|
||||||
// println("et là je saut vers ${step.targetGrid}")
|
|
||||||
seekToGrid(step.targetGrid)
|
|
||||||
synthetizer?.channels?.forEach { it?.controlChange(64, 0) }
|
|
||||||
sequencer?.tempoFactor = 1f
|
|
||||||
if (sequencer?.isRunning == false) {
|
|
||||||
sequencer?.tempoInBPM = targetBpm
|
|
||||||
sequencer?.start()
|
|
||||||
}
|
|
||||||
// println("Après de sauter bpm=$targetBpm")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private var syncJob: Job? = null
|
|
||||||
|
|
||||||
private fun startSyncLoop(sharedScreenModel: SharedScreenModel) {
|
|
||||||
syncJob?.cancel()
|
|
||||||
syncJob = playerScope.launch(Dispatchers.Default) {
|
|
||||||
while (isActive) {
|
while (isActive) {
|
||||||
if (sequencer?.isRunning == true) {
|
if (sequencer?.isRunning == true) {
|
||||||
val posMs = (sequencer?.microsecondPosition ?: 0L) / 1000
|
val currentPos = getCurrentPosition()
|
||||||
sharedScreenModel.updateActiveIndex(posMs)
|
val duration = getDuration()
|
||||||
}
|
val measure = sharedScreenModel.measure.value
|
||||||
delay(20)
|
val firstVal = measure.substringBefore("/")
|
||||||
}
|
val mesureNum = firstVal.toIntOrNull() ?: 4
|
||||||
}
|
|
||||||
}
|
|
||||||
private fun seekToGrid(gridIndex: Int) {
|
|
||||||
val resolution = sequencer?.sequence?.resolution?.toDouble() ?: 480.0
|
|
||||||
val tick = gridIndex.toDouble() * resolution
|
|
||||||
|
|
||||||
val bpm = sequencer?.tempoInBPM ?: targetBpm
|
// On cherche si on est sur un point de saut
|
||||||
val usPerTick = (60_000_000.0 / bpm) / resolution
|
val step = navigationSteps.find {
|
||||||
val targetMicros = (tick * usPerTick).toLong()
|
currentPos >= it.triggerMs &&
|
||||||
|
currentPos < it.triggerMs + 800 &&
|
||||||
|
!it.alreadyDone
|
||||||
|
}
|
||||||
|
if (step != null) {
|
||||||
|
if (currentPos < step.triggerMs + 800) {
|
||||||
|
step.alreadyDone = true
|
||||||
|
val timeToFinish = duration - currentPos
|
||||||
|
val isCloseToEnd = timeToFinish < (beatDurationMs * 1.5).toLong()
|
||||||
|
// println("time to finish = $timeToFinish et isClo $isCloseToEnd")
|
||||||
|
|
||||||
sequencer?.microsecondPosition = targetMicros
|
// point d'orgue
|
||||||
|
if (step.isHold) {
|
||||||
|
synthetizer?.channels?.forEach { it?.controlChange(64, 127) }
|
||||||
|
sequencer?.tempoFactor = 0.0001f
|
||||||
|
|
||||||
|
if(step.beatInDC == 1){
|
||||||
|
delay(beatDurationMs * 2)
|
||||||
|
} else { //démi-ton sur le point d'orgue
|
||||||
|
delay(beatDurationMs)
|
||||||
|
}
|
||||||
|
println("Est-ce un demi-ton ? ${step.beatInDC} Le delay /2 est : ${(beatDurationMs / step.beatInDC) * 2} et pour 1 ${beatDurationMs * 2}")
|
||||||
|
synthetizer?.channels?.forEach { it?.controlChange(64, 0) }
|
||||||
|
sequencer?.tempoFactor = currentTempo
|
||||||
|
step.alreadyDone=false
|
||||||
|
} else {
|
||||||
|
step.alreadyDone = true
|
||||||
|
|
||||||
|
// Sustain
|
||||||
|
if(step.sustainNedd) {
|
||||||
|
synthetizer?.channels?.forEach { it?.controlChange(64, 127) }
|
||||||
|
sequencer?.tempoFactor = 0.0001f
|
||||||
|
|
||||||
|
delay(beatDurationMs * (mesureNum/2))
|
||||||
|
|
||||||
|
synthetizer?.channels?.forEach {
|
||||||
|
it?.controlChange(64, 0)
|
||||||
|
it?.controlChange(123, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(50) // Silence de respiration
|
||||||
|
|
||||||
|
sequencer?.tempoFactor = currentTempo
|
||||||
|
setVolume(currentGlobalVolume)
|
||||||
|
}
|
||||||
|
seekTo(step.targetMs)
|
||||||
|
|
||||||
|
if (sequencer?.isRunning == false) sequencer?.start()
|
||||||
|
|
||||||
|
// delay(500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delay(300)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun play(){
|
actual fun play(){
|
||||||
if (sequencer?.isOpen == true){
|
if (!sequencer!!.isOpen){
|
||||||
applyBpm()
|
sequencer!!.open()
|
||||||
|
}
|
||||||
sequencer?.start()
|
sequencer?.start()
|
||||||
println("La sequence vient d etre lancé ${sequencer?.isRunning}")
|
println("La sequence vient d etre lancé ${sequencer?.isRunning}")
|
||||||
// sequencer!!.open()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
actual fun pause(){
|
actual fun pause(){
|
||||||
sequencer?.stop()
|
sequencer?.stop()
|
||||||
|
|
@ -319,16 +225,14 @@ actual class FMediaPlayer actual constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun syncNavigationMonitor(sharedScreenModel: SharedScreenModel) {
|
actual fun syncNavigationMonitor(sharedScreenModel: SharedScreenModel) {
|
||||||
this.boundModel = sharedScreenModel
|
|
||||||
prepareNavigation(sharedScreenModel)
|
prepareNavigation(sharedScreenModel)
|
||||||
startNavigationMonitor(sharedScreenModel)
|
startNavigationMonitor(sharedScreenModel)
|
||||||
startSyncLoop(sharedScreenModel)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun syncTuoWithMidi(sequence: Sequence, sharedScreenModel: SharedScreenModel) {
|
fun syncTuoWithMidi(sequence: Sequence, sharedScreenModel: SharedScreenModel) {
|
||||||
val timestamps = mutableListOf<Long>()
|
val timestamps = mutableListOf<Long>()
|
||||||
val resolution = sequence.resolution.toDouble()
|
val resolution = sequence.resolution.toDouble()
|
||||||
val bpm = targetBpm/*sequencer?.tempoInBPM?.toDouble() ?: 120.0*/
|
val bpm = sequencer?.tempoInBPM?.toDouble() ?: 120.0
|
||||||
val usPerTick = (60_000_000.0 / bpm) / resolution
|
val usPerTick = (60_000_000.0 / bpm) / resolution
|
||||||
|
|
||||||
// détecter par 60 ticks càd par un temp (noir)
|
// détecter par 60 ticks càd par un temp (noir)
|
||||||
|
|
@ -378,7 +282,6 @@ actual class FMediaPlayer actual constructor(
|
||||||
}
|
}
|
||||||
actual fun seekTo(position: Long) {
|
actual fun seekTo(position: Long) {
|
||||||
sequencer?.microsecondPosition = position * 1000
|
sequencer?.microsecondPosition = position * 1000
|
||||||
applyBpm()
|
|
||||||
}
|
}
|
||||||
actual fun release() {
|
actual fun release() {
|
||||||
sequencer?.close()
|
sequencer?.close()
|
||||||
|
|
@ -412,7 +315,7 @@ actual class FMediaPlayer actual constructor(
|
||||||
} catch (e: Exception){
|
} catch (e: Exception){
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
// println("la volume $level")
|
println("la volume $level")
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun setPointA() {
|
actual fun setPointA() {
|
||||||
|
|
@ -472,7 +375,7 @@ actual class FMediaPlayer actual constructor(
|
||||||
channels[i].controlChange(123, 0)
|
channels[i].controlChange(123, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// println("SATB $i Volumes: ${voiceVolumes[i]}")
|
println("SATB $i Volumes: ${voiceVolumes[i]}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) { e.printStackTrace() }
|
} catch (e: Exception) { e.printStackTrace() }
|
||||||
|
|
@ -490,16 +393,29 @@ actual class FMediaPlayer actual constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
actual fun getCurrentBPM(): Float {
|
actual fun getCurrentBPM(): Float {
|
||||||
return targetBpm
|
val currentFactor = sequencer?.tempoFactor ?: 1.0f
|
||||||
|
val currentBPM = (120f * currentFactor)
|
||||||
|
return currentBPM
|
||||||
}
|
}
|
||||||
|
|
||||||
actual fun setTempo(bpm: Float){
|
actual fun setTempo(factor: Float){
|
||||||
this.targetBpm = bpm
|
currentTempo = factor
|
||||||
sequencer?.tempoInBPM = bpm
|
sequencer?.tempoFactor = factor
|
||||||
boundModel?.let { modele ->
|
}
|
||||||
prepareNavigation(modele)
|
fun getTempo(): Float = currentTempo
|
||||||
|
private val MS8PER_NOTE = 500L
|
||||||
|
|
||||||
|
fun seekToNote(index: Int) {
|
||||||
|
try {
|
||||||
|
sequencer?.let { sequencer ->
|
||||||
|
if (sequencer.isOpen) {
|
||||||
|
val targetPos = index * MS8PER_NOTE * 1000
|
||||||
|
sequencer.microsecondPosition = targetPos
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
println("Tempo réglé à : $bpm BPM")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveVoicesVolumes() {
|
private fun saveVoicesVolumes() {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue