Implement DC (simple) & 𝄐
This commit is contained in:
parent
0cd33c0e43
commit
fa603dc71e
5 changed files with 222 additions and 8 deletions
|
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)) {
|
)) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
@ -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 / ")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue