Implement DC (simple) & 𝄐

This commit is contained in:
hasinarak3@gmail.com 2026-03-04 15:25:02 +03:00
parent 0cd33c0e43
commit fa603dc71e
5 changed files with 222 additions and 8 deletions

View file

@ -2,6 +2,7 @@ package mg.dot.feufaro.midi
import SharedScreenModel import SharedScreenModel
import mg.dot.feufaro.FileRepository import mg.dot.feufaro.FileRepository
import mg.dot.feufaro.viewmodel.MidiMarkers
expect class FMediaPlayer(filename: String, onFinished: () -> Unit) { expect class FMediaPlayer(filename: String, onFinished: () -> Unit) {
fun play() fun play()
@ -22,6 +23,8 @@ expect class FMediaPlayer(filename: String, onFinished: () -> Unit) {
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 release()
} }
/* /*

View file

@ -700,12 +700,16 @@ fun LazyVerticalGridTUO(
val myTimestamp = sharedScreenModel.tuoTimestamps.value.getOrElse(globalIndex) { 0L } val myTimestamp = sharedScreenModel.tuoTimestamps.value.getOrElse(globalIndex) { 0L }
if(oneTUO.pTemplate.markerToString() != "") { if(oneTUO.pTemplate.markerToString() != "") {
val nott = oneTUO.tuNotes.getOrNull(1)
metadataList.add( metadataList.add(
MidiMarkers( MidiMarkers(
myTimestamp, myTimestamp,
oneTUO.pTemplate.template, oneTUO.pTemplate.template,
oneTUO.pTemplate.lastCalledMarker, oneTUO.pTemplate.lastCalledMarker,
oneTUO.pTemplate.markerToString() oneTUO.pTemplate.markerToString(),
oneTUO.prevTUO!!.pTemplate!!.template,
oneTUO.sep0,
oneTUO.tuNotes.getOrNull(1).toString()
) )
) )
} }
@ -719,8 +723,8 @@ fun LazyVerticalGridTUO(
} , } ,
onDoubleClick = { onDoubleClick = {
val m = sharedScreenModel.getFullMarkers() val m = sharedScreenModel.getFullMarkers()
m.forEach { (timestamp, template, lastCallerMarker, marker) -> m.forEach { (timestamp, template, lastCallerMarker, marker, noteBefore, separat, note) ->
println("Allmarker : $marker in $timestamp ms") println("Allmarker : $marker in $timestamp ms & note= $note & nBefore =$noteBefore & separateur = $separat")
} }
} }
)) { )) {

View file

@ -5,4 +5,7 @@ data class MidiMarkers(
val template: String, val template: String,
val lastCallerMarker: Int, val lastCallerMarker: Int,
val marker: String, val marker: String,
val noteBefore: String,
val separat: String,
val note: String
) )

View file

@ -124,6 +124,12 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
private val _canUpdPositionFromPartition = MutableStateFlow(false) private val _canUpdPositionFromPartition = MutableStateFlow(false)
val canUpdPositionFromPartition = _canUpdPositionFromPartition.asStateFlow() val canUpdPositionFromPartition = _canUpdPositionFromPartition.asStateFlow()
private val _dcDone = MutableStateFlow(false)
val dcDone = _dcDone.asStateFlow()
private val _dsDone = MutableStateFlow(false)
val dsDone = _dsDone.asStateFlow()
private val _tuoTimestamps = MutableStateFlow<List<Long>>(emptyList()) private val _tuoTimestamps = MutableStateFlow<List<Long>>(emptyList())
val tuoTimestamps: StateFlow<List<Long>> = _tuoTimestamps val tuoTimestamps: StateFlow<List<Long>> = _tuoTimestamps
@ -146,6 +152,19 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
return _midiMarkersList.value return _midiMarkersList.value
} }
fun setDcDone(state: Boolean) {
_dcDone.value = state
}
fun setDsDone(state: Boolean) {
_dsDone.value = state
}
fun getDcDone(): Boolean {
return _dcDone.value
}
fun getDsDone(): Boolean {
return _dsDone.value
}
fun finalizeMarkers() { fun finalizeMarkers() {
val timestamps = _tuoTimestamps.value val timestamps = _tuoTimestamps.value
val tuos = _tuoList.value.drop(1) val tuos = _tuoList.value.drop(1)
@ -160,9 +179,40 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
timestamp = ts, timestamp = ts,
template = tuo.pTemplate.template, template = tuo.pTemplate.template,
lastCallerMarker = tuo.pTemplate.lastCalledMarker, lastCallerMarker = tuo.pTemplate.lastCalledMarker,
marker = markerText 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 ?: ""
// On se position sur la note apres les tiret "-" ou sur /
if ((currentNote != "" && currentNote.isNotBlank()) || (currentSep == "/")) {
val currentTs = timestamps.getOrNull(internIn) ?: 0L
println("Dernier '-' passé. Nouvelle note '$currentNote' trouvée à l'index $internIn (Position du saut)")
newMtdList.clear()
newMtdList.add(
MidiMarkers(
timestamp = currentTs,
template = currentTuo.pTemplate.template,
lastCallerMarker = currentTuo.pTemplate.lastCalledMarker,
marker = "DC_GROUP_PART",
noteBefore = currentTuo.prevTUO!!.pTemplate!!.template,
separat = currentTuo.sep0,
note = currentNote
)
)
break
}
internIn++
}
}
} }
} }
updateMidiData(newMtdList) updateMidiData(newMtdList)
@ -171,10 +221,13 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
fun loadNewSong(newMidiFile: String) { fun loadNewSong(newMidiFile: String) {
_mediaPlayer?.stop() _mediaPlayer?.stop()
_mediaPlayer?.release()
_mediaPlayer = null _mediaPlayer = null
_isPos.value = true _isPos.value = true
_isPlay.value = false _isPlay.value = false
_currentPos.value = 0f _currentPos.value = 0f
_dcDone.value = false
_dsDone.value = false
try { try {
val midiFileName = fileRepository.getFileName(newMidiFile) val midiFileName = fileRepository.getFileName(newMidiFile)
println("Opening xx129 $midiFileName") println("Opening xx129 $midiFileName")
@ -184,11 +237,15 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
_currentPos.value = 0f _currentPos.value = 0f
seekTo(0f) seekTo(0f)
_mediaPlayer?.stop() _mediaPlayer?.stop()
setDcDone(false)
setDsDone(false)
println("fin de lecture du Midi $newMidiFile") println("fin de lecture du Midi $newMidiFile")
_mediaPlayer?.syncNavigationMonitor(this)
}) })
// sync // synchro
_mediaPlayer?.requestSync(this) _mediaPlayer?.requestSync(this)
finalizeMarkers() finalizeMarkers()
_mediaPlayer?.syncNavigationMonitor(this)
} catch(e: Exception) { } catch(e: Exception) {
println("Erreur d'ouverture de mediaPlayer / ") println("Erreur d'ouverture de mediaPlayer / ")
} }

View file

@ -3,13 +3,16 @@ package mg.dot.feufaro.midi
import SharedScreenModel import SharedScreenModel
import kotlinx.coroutines.* import kotlinx.coroutines.*
import mg.dot.feufaro.getConfigDirectoryPath import mg.dot.feufaro.getConfigDirectoryPath
import mg.dot.feufaro.viewmodel.MidiMarkers
import java.io.File import java.io.File
import java.util.prefs.Preferences import java.util.prefs.Preferences
import javax.sound.midi.MetaMessage
import javax.sound.midi.MidiSystem import javax.sound.midi.MidiSystem
import javax.sound.midi.Sequence import javax.sound.midi.Sequence
import javax.sound.midi.Sequencer import javax.sound.midi.Sequencer
import javax.sound.midi.ShortMessage import javax.sound.midi.ShortMessage
import javax.sound.midi.Synthesizer import javax.sound.midi.Synthesizer
import javax.sound.midi.Track
import javax.sound.sampled.AudioSystem import javax.sound.sampled.AudioSystem
import javax.sound.sampled.FloatControl import javax.sound.sampled.FloatControl
@ -39,6 +42,17 @@ actual class FMediaPlayer actual constructor(
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 data class NavigationStep(
val triggerMs: Long, // DC | DS
val targetMs: Long, // farany, $ ,..
val isHold: Boolean = false, // point d'orgue
var alreadyDone: Boolean = false, // done
var beatInDC: Int = 1, // temps note sur le marker
var sustainNedd: Boolean = true
)
private val navigationSteps = mutableListOf<NavigationStep>()
init { init {
try { try {
sequencer?.open() sequencer?.open()
@ -68,6 +82,131 @@ actual class FMediaPlayer actual constructor(
} }
} }
private fun resetNavigationFlags() {
navigationSteps.forEach { it.alreadyDone = false }
}
private fun prepareNavigation(sharedScreenModel: SharedScreenModel) {
val metadataList = sharedScreenModel.getFullMarkers()
navigationSteps.clear()
var holdPos: Long = 0L
val currentBpm = sequencer?.tempoInBPM ?: 120f
val beatDurationMs = (60_000 / currentBpm).toLong()
metadataList.forEach { (timestamp, template, lastCallerMarker, marker, noteBefore, separat, note) ->
val timeMs = (timestamp / 1000)
when {
marker.contains("\uD834\uDD10") -> {
holdPos = timeMs
val beat = if(note.contains('•')) 2 else 1 // demi-ton sur .) ou non
navigationSteps.add(FMediaPlayer.NavigationStep(timeMs, -1L, isHold = true, beatInDC = beat))
println("Point d'orgue (\uD834\uDD10) mémorisée à $holdPos ms")
}
// Déclencheurs DC
marker == "DC_GROUP_PART" -> {
var needSustaine = false
var trigger = timeMs - (beatDurationMs / 2)
navigationSteps.add(NavigationStep(trigger, targetMs = 0L, sustainNedd = needSustaine))
println("Lien créé : $marker à $trigger ms vers cible 0 ms Avec Sustaine? $needSustaine")
}
/* Un Dc se place sur une note '-'*/
marker == "DC" -> {
var needSustaine = false
val trigger = timeMs
// Si le marker si place près de la fin de la partition
if((getDuration() - timeMs) <= beatDurationMs) {
println("Fin très proche")
needSustaine = true
}
navigationSteps.add(NavigationStep(trigger, targetMs = 0, sustainNedd = needSustaine))
println("Lien créé : $marker à $trigger ms vers le début")
}
}
}
}
private fun startNavigationMonitor(sharedScreenModel: SharedScreenModel) {
var dcDone = sharedScreenModel.getDcDone()
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 = playerScope.launch {
while (isActive) {
if (sequencer?.isRunning == true) {
val currentPos = getCurrentPosition()
val duration = getDuration()
val measure = sharedScreenModel.measure.value
val firstVal = measure.substringBefore("/")
val mesureNum = firstVal.toIntOrNull() ?: 4
// On cherche si on est sur un point de saut
val step = navigationSteps.find {
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")
// 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){ if (!sequencer!!.isOpen){
sequencer!!.open() sequencer!!.open()
@ -85,6 +224,11 @@ actual class FMediaPlayer actual constructor(
} }
} }
actual fun syncNavigationMonitor(sharedScreenModel: SharedScreenModel) {
prepareNavigation(sharedScreenModel)
startNavigationMonitor(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()
@ -122,10 +266,13 @@ actual class FMediaPlayer actual constructor(
} }
actual fun stop(){ actual fun stop(){
sequencer?.stop() // sequencer?.stop()
sequencer?.microsecondPosition = 0 sequencer?.microsecondPosition = 0
navigationJob?.cancel()
resetNavigationFlags()
navigationSteps.clear()
clearLoop() clearLoop()
release() // release()
} }
actual fun getDuration(): Long { actual fun getDuration(): Long {
return (sequencer?.microsecondLength ?: 0L) / 1000 return (sequencer?.microsecondLength ?: 0L) / 1000
@ -136,7 +283,7 @@ actual class FMediaPlayer actual constructor(
actual fun seekTo(position: Long) { actual fun seekTo(position: Long) {
sequencer?.microsecondPosition = position * 1000 sequencer?.microsecondPosition = position * 1000
} }
fun release() { actual fun release() {
sequencer?.close() sequencer?.close()
synthetizer?.close() synthetizer?.close()
playerScope.cancel() playerScope.cancel()