diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt index 52e3910..44e7953 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt @@ -149,6 +149,7 @@ object ScreenSolfa : Screen { LazyVerticalGridTUO( gridTUOData, gridWidthPx = gridWidthPx, + sharedScreenModel = sharedScreenModel, onGridWidthMeasured = { width -> gridWidthPx = width } ) FlowRow( diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt index b509243..233188c 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/TimeUnitObject.kt @@ -42,6 +42,9 @@ import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.style.BaselineShift import androidx.compose.ui.text.withStyle +import SharedScreenModel +import androidx.compose.runtime.collectAsState +import kotlinx.coroutines.delay val FEUFAROO_TRIOLET_COLOR = Color.DarkGray val FEUFAROO_KEY_CHANGE_COLOR = Color.Blue @@ -228,13 +231,14 @@ class TimeUnitObject (val pTemplate: PTemplate, val prevTUO: TimeUnitObject?, co fun TimeUnitComposable( tuo: TimeUnitObject, stanzaNumber: Int, - gridColumnCount: Int + gridColumnCount: Int, + gridActive: Boolean ) { val col = if (tuo.getNum() % 2 == 0) Color(0xff, 0xfa, 0xf7) else Color(0xfb, 0xf3, 0xff) val currentDensity = LocalDensity.current Column( modifier = Modifier - .background(col) + .background(if(gridActive) Color.Cyan.copy(alpha = 0.5f) else col) ) { if (TimeUnitObject._hasMarker) { val lineHeight = 20.sp @@ -511,6 +515,7 @@ fun AutoResizingText( fun LazyVerticalGridTUO( viewModel: GridTUOData, gridWidthPx: Int, + sharedScreenModel: SharedScreenModel, onGridWidthMeasured: (Int) -> Unit, modifier: Modifier = Modifier ) { @@ -547,6 +552,16 @@ fun LazyVerticalGridTUO( val currentStanza = viewModel.stanza + val currentPos by sharedScreenModel.currentPos.collectAsState() + val duration by sharedScreenModel.duration.collectAsState() + val isPlay by sharedScreenModel.isPlay.collectAsState() + val displayedList = tuoList.drop(1) + val nbTotalDesRow = displayedList.size + val activeRowIndex = if (duration > 0f) { + ((currentPos / duration) * nbTotalDesRow).toInt().coerceIn(0, nbTotalDesRow - 1) + } else { + -1 + } FlowRow( modifier = Modifier.fillMaxWidth(), @@ -556,12 +571,16 @@ fun LazyVerticalGridTUO( // state = lazyGridState ) { tuoList.drop(n=1).forEachIndexed { relativeIndex, oneTUO -> + val isActive = (relativeIndex == activeRowIndex) Box( modifier = Modifier .fillMaxWidth(flowRowSize) + .background(Color.Transparent) .combinedClickable( onClick = { - println("Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex FL $flowRowSize GC $gridColumnCount") + sharedScreenModel.updatePositionFromPartition(relativeIndex, nbTotalDesRow) + println("590: relative $relativeIndex active? $isActive") + println("TimeUnitObj:566 Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex FL $flowRowSize totaRow $nbTotalDesRow") } , onDoubleClick = { println("Double-Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex") @@ -571,7 +590,8 @@ fun LazyVerticalGridTUO( TimeUnitComposable( tuo = oneTUO, currentStanza, - gridColumnCount + gridColumnCount, + gridActive = isActive ) } } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt index fd91b65..1889a37 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt @@ -74,18 +74,23 @@ fun MainScreenWithDrawer( -var isDragging by remember { mutableStateOf(false) } -var isPlay by remember { mutableStateOf(false) } -var isPos by remember { mutableStateOf(true) } +//var isDragging by remember { mutableStateOf(false) } +//var isPlay by remember { mutableStateOf(false) } +//var isPos by remember { mutableStateOf(true) } var isPlayMid by remember { mutableStateOf(false) } -var currentPos by remember { mutableStateOf(0f) } -var duration by remember { mutableStateOf(0f) } +//var currentPos by remember { mutableStateOf(0f) } +val isPlay by sharedScreenModel.isPlay.collectAsState() +val isPos by sharedScreenModel.isPos.collectAsState() +var isDragging = sharedScreenModel.isDragging +val currentPos by sharedScreenModel.currentPos.collectAsState() +val duration by sharedScreenModel.duration.collectAsState() var midiFile = "whawyd3.mid" var refreshTrigeer by remember { mutableStateOf(0)} -var volumelevel by remember { mutableStateOf(0.8f) } +val volumelevel by sharedScreenModel.volumeLevel.collectAsState() -val mediaPlayer = remember(refreshTrigeer) { +val player = sharedScreenModel.mediaPlayer +/*val mediaPlayer = remember(refreshTrigeer) { MediaPlayer(filename = midiFile, onFinished = { isPos = true isPlay = false @@ -94,9 +99,18 @@ val mediaPlayer = remember(refreshTrigeer) { println("fin de lecture du whawyd3.mid") }).apply { setVolume(volumelevel) } +}*/ +LaunchedEffect(isPlay, isPos) { + if (isPlay && !isPos) { +// while (isPlay && !isPos) { + while (true) { + sharedScreenModel.updateProgress() + delay(100) + } + } } -LaunchedEffect(isPlay, isPos, mediaPlayer) { +/*LaunchedEffect(isPlay, isPos, mediaPlayer) { if (isPlay && !isPos) { val d = mediaPlayer.getDuration().toFloat() if (d > 0) duration = d @@ -108,13 +122,16 @@ LaunchedEffect(isPlay, isPos, mediaPlayer) { delay(100) } } -} +}*/ LaunchedEffect(isSearchActive) { if (isSearchActive) { focusRequester.requestFocus() } } + LaunchedEffect(Unit) { + sharedScreenModel.loadNewSong("whawyd3.mid") + } ModalNavigationDrawer(drawerState = drawerState, drawerContent = { SimpleDrawerContent( items, @@ -128,11 +145,12 @@ LaunchedEffect(isPlay, isPos, mediaPlayer) { onScannerButtonClick() }, onSongSelected = { newSong -> - mediaPlayer?.stop() - isPos = true - isPlay = false - currentPos = 0f - refreshTrigeer++ +// mediaPlayer?.stop() +// isPos = true +// isPlay = false +// currentPos = 0f + sharedScreenModel.loadNewSong("whawyd3.mid") +// refreshTrigeer++ } ) }, content = { @@ -231,11 +249,12 @@ LaunchedEffect(isPlay, isPos, mediaPlayer) { ) { FloatingActionButton( onClick = { - isPlayMid = !isPlayMid - if(mediaPlayer.getCurrentPosition() != 0L) { - mediaPlayer?.seekTo(0) - mediaPlayer?.stop() - } + isPlayMid = !isPlayMid +// if(isPlayMid) sharedScreenModel.stopMidi() +// if(mediaPlayer.getCurrentPosition() != 0L) { +// mediaPlayer?.seekTo(0) +// mediaPlayer?.stop() +// } }, modifier = Modifier.alpha(0.45f) ) { Icon( @@ -250,6 +269,7 @@ LaunchedEffect(isPlay, isPos, mediaPlayer) { onClick = { isExpanded = !isExpanded refreshTrigeer++ + sharedScreenModel.loadNewSong("whawyd3.mid") }, modifier = Modifier.alpha(0.45f) ) { Icon( @@ -267,13 +287,14 @@ LaunchedEffect(isPlay, isPos, mediaPlayer) { Box( modifier = Modifier.fillMaxWidth(0.9f) ) { - MidiControlPanel( - isPause = isPos, - currentPos = currentPos, - volume = volumelevel, - duration = duration, - onPlayPauseClick = { - if(isPlay){ + if(player != null) { + MidiControlPanel( + isPause = isPos, + currentPos = currentPos, + volume = volumelevel, + duration = duration, + onPlayPauseClick = { + sharedScreenModel.togglePlayPause()/*if(isPlay){ mediaPlayer?.pause() isPlay = false isPos = true @@ -284,49 +305,51 @@ LaunchedEffect(isPlay, isPos, mediaPlayer) { mediaPlayer?.play() mediaPlayer?.setVolume(volumelevel) isPlay = true - isPos = false - } - /* if(!isPlay) { - if (currentPos == 0f) mediaPlayer.seekTo(0) - mediaPlayer?.play() - isPlay = true - isPos = false - } else { - mediaPlayer?.stop() - isPlay = false - isPos = true - }*/ + isPos = false*/ + }, + /* if(!isPlay) { + if (currentPos == 0f) mediaPlayer.seekTo(0) + mediaPlayer?.play() + isPlay = true + isPos = false + } else { + mediaPlayer?.stop() + isPlay = false + isPos = true + }*/ - println("je clique pause = $isPos play = $isPlay") -// if(isPos) { -// mediaPlayer.play() -// isPos = false -// } else { -// mediaPlayer.pause() -// isPos = true -// } + // println("je clique pause = ${sharedScreenModel.isPlay} play = ${sharedScreenModel.isPos}") + // if(isPos) { + // mediaPlayer.play() + // isPos = false + // } else { + // mediaPlayer.pause() + // isPos = true + // } /*if (isPlayMid) { // mediaPlayer.seekTo(0f.toLong()) mediaPlayer.play() isPlayMid = false - }*/ - }, - onSeek = { newPos -> - currentPos = newPos - isDragging = true - mediaPlayer.seekTo(newPos.toLong()) - scope.launch { - delay(100) - isDragging = false } - }, - mediaPlayer = mediaPlayer, - onVolumeChange = { newVolume -> - volumelevel = newVolume - mediaPlayer?.setVolume(newVolume) - println("Changement volume $newVolume -l $volumelevel") - } - ) + }*/ + onSeek = { newPos -> // currentPos = newPos + sharedScreenModel.setDragging(true) + sharedScreenModel.seekTo(newPos) + scope.launch { + delay(100) + sharedScreenModel.setDragging(false) + } + println("DrawerUI:335: mihetsika $newPos") + }, + mediaPlayer = player, + onVolumeChange = { newVolume -> // volumelevel = newVolume + sharedScreenModel.setVolume(newVolume) + println("Changement volume $newVolume -l $volumelevel") + }, + ) + } else { + Text("Sélectionner un morceau") + } /*Row( modifier = Modifier.align(Alignment.Center).padding(16.dp), verticalAlignment = Alignment.CenterVertically, diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt index a101859..5cf7bf9 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt @@ -1,6 +1,8 @@ // commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.setValue import cafe.adriel.voyager.core.model.ScreenModel import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -11,8 +13,9 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.stateIn import mg.dot.feufaro.data.DrawerItem -import mg.dot.feufaro.solfa.TimeUnitObject import mg.dot.feufaro.data.getDrawerItems +import mg.dot.feufaro.solfa.TimeUnitObject +import mg.dot.feufaro.midi.MediaPlayer class SharedScreenModel() : ScreenModel { private val _nextLabel = MutableStateFlow("Next ...") @@ -90,7 +93,113 @@ class SharedScreenModel() : ScreenModel { } fun updateSearchTxt(searchValue: String) { _searchTitle.value = searchValue - } fun appendData(otherData: String) { + } + + private var _mediaPlayer by mutableStateOf(null) + val mediaPlayer: MediaPlayer? get() = _mediaPlayer + + private val _isPlay = MutableStateFlow(false) + val isPlay = _isPlay.asStateFlow() + + private val _isPos = MutableStateFlow(true) + val isPos = _isPos.asStateFlow() + + private val _isDragging = MutableStateFlow(true) + val isDragging = _isDragging.asStateFlow() + + private val _currentPos = MutableStateFlow(0f) + val currentPos = _currentPos.asStateFlow() + private val _duration = MutableStateFlow(0f) + val duration = _duration.asStateFlow() + + private val _volumeLevel = MutableStateFlow(0.8f) + val volumeLevel = _volumeLevel.asStateFlow() + + private val _isPlayMid = MutableStateFlow(false) + val isPlayMid = _isPlayMid.asStateFlow() + + private var midiFile = "whawyd3.mid" + + fun loadNewSong(newMidiFile: String) { + _mediaPlayer?.stop() + _isPos.value = true + _isPlay.value = false + _currentPos.value = 0f + _mediaPlayer = MediaPlayer(filename = newMidiFile, onFinished = { + _isPos.value = true + _isPlay.value = false + _currentPos.value = 0f + println("fin de lecture du Midi $newMidiFile") + }) + println("New media Player crée $newMidiFile") + } +// val mediaPlayer = + + fun togglePlayPause() { + _mediaPlayer?.let { player -> + if (_isPlay.value) { + _isPlay.value = false + _isPos.value = true + player.pause() + } else { + _isPlay.value = true + _isPos.value = false + player.play() + player.setVolume(_volumeLevel.value) + if(currentPos.value == 0f) { + player.seekTo(0) + } + } + println("128: Status de isPlay ${_isPlay.value} \nisPos ${_isPos.value} \ncurrentPos ${_currentPos.value} \n volume ${_volumeLevel.value}") + // _isPlay.value = !_isPlay.value + } + } + fun stopMidi() { + _mediaPlayer?.let { player -> + _isPlay.value = false + _isPos.value = true + player.pause() + } + } + fun seekTo(pos: Float) { + _currentPos.value = pos + _mediaPlayer?.let { player -> + player.seekTo(pos.toLong()) + } + } + + fun setDragging(dragState: Boolean) { + _isDragging.value = dragState + } + fun setVolume(level: Float) { + _volumeLevel.value = level + _mediaPlayer?.let { player -> + player.setVolume(level) } + } + fun updateProgress(){ + _mediaPlayer?.let { player-> + if (_isPlay.value) { + val p = player.getCurrentPosition().toFloat() + val d = player.getDuration().toFloat() + + if (p >= 0) _currentPos.value = p + if ((d > 0) && _duration.value != d) _duration.value = d + } + } + } + var currentNoteIndex by mutableStateOf(0f) + + fun updatePositionFromPartition(index: Int, totalRow: Int) { + val duration = _duration.value + if(totalRow > 0) { + currentNoteIndex = index.toFloat() + val newPos = (currentNoteIndex / totalRow.toFloat()) * duration + seekTo(newPos) + println("Shared:196 currentNoteIndex $currentNoteIndex, Index $index et curret = ${_currentPos.value}") + } + } + + fun appendData(otherData: String) { _nextLabel.value += otherData } diff --git a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/midi/MidiPlayer.kt b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/midi/MidiPlayer.kt index 20a3c95..e3de7f2 100644 --- a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/midi/MidiPlayer.kt +++ b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/midi/MidiPlayer.kt @@ -8,9 +8,11 @@ import java.io.ByteArrayInputStream import java.io.File import javax.sound.midi.MidiSystem import javax.sound.midi.Sequencer -import javax.sound.midi.ShortMessage -import javax.sound.midi.Synthesizer -import javax.sound.sampled.* +import javax.sound.midi.Synthesizer //import javax.sound.midi.ShortMessage +//import javax.sound.midi.Synthesizer +import javax.sound.sampled.AudioFormat +import javax.sound.sampled.AudioSystem +import javax.sound.sampled.FloatControl //private var sequencer: javax.sound.midi.Sequencer?= null actual class MediaPlayer actual constructor( @@ -20,11 +22,12 @@ actual class MediaPlayer actual constructor( private var sequencer: Sequencer? = try { MidiSystem.getSequencer(false) } catch (e: Exception){ + println("Erreur impossible obtenir ${e.message}") null } - private var synthetizer: Synthesizer? = MidiSystem.getSynthesizer() + private var synthetizer = MidiSystem.getSynthesizer() as Synthesizer? private var pointA: Long = -1L private var pointB: Long = -1L @@ -37,24 +40,28 @@ actual class MediaPlayer actual constructor( private var currentTempo: Float = 1.0f init { - sequencer?.open() - synthetizer?.open() + try { + sequencer?.open() + synthetizer?.open() + val transmitter = sequencer?.transmitter + val synthReceiver = synthetizer?.receiver + transmitter?.receiver = synthReceiver + } catch (e: Exception) { + e.printStackTrace() + } - val transmitter = sequencer?.transmitter - val synthReceiver = synthetizer?.receiver - transmitter?.receiver = synthReceiver - - val file = File(filename) - if (file.exists()){ - sequencer?.sequence = MidiSystem.getSequence(file) - applyVoiceStates() - sequencer?.addMetaEventListener { meta -> - if(meta.type == 47){ - onFinished() - } + val file = File(filename) + if (file.exists()){ + sequencer?.sequence = MidiSystem.getSequence(file) + applyVoiceStates() + sequencer?.addMetaEventListener { meta -> + if(meta.type == 47){ + onFinished() } } + } } + actual fun play(){ if (sequencer!!.isOpen){ sequencer?.start() @@ -67,7 +74,8 @@ actual class MediaPlayer actual constructor( actual fun stop(){ sequencer?.stop() sequencer?.microsecondPosition = 0 -// disableLoop() + clearLoop() + release() } actual fun getDuration(): Long { return (sequencer?.microsecondLength ?: 0L) / 1000 @@ -80,7 +88,7 @@ actual class MediaPlayer actual constructor( } fun release() { sequencer?.close() -// synthetizer?.close() + synthetizer?.close() } actual fun setVolume(level: Float) { try { @@ -209,32 +217,18 @@ actual class MediaPlayer actual constructor( sequencer?.tempoFactor = factor } fun getTempo(): Float = currentTempo -} + private val MS8PER_NOTE = 500L - -/* - -private var sequencer: javax.sound.midi.Sequencer?= null -actual fun MidiPlayer(filename: String, onFinished: () -> Unit) { - val file = File(filename) - if (file.exists()){ - StopMidi() - sequencer = MidiSystem.getSequencer().apply { - open() - sequence = MidiSystem.getSequence(file) - addMetaEventListener { meta -> - if(meta.type == 47){ - onFinished() + fun seekToNote(index: Int) { + try { + sequencer?.let { sequencer -> + if (sequencer.isOpen) { + val targetPos = index * MS8PER_NOTE * 1000 + sequencer.microsecondPosition = targetPos } } - start() + } catch (e: Exception) { + e.printStackTrace() } } -} - -actual fun StopMidi() { - if(sequencer?.isRunning == true){ - sequencer?.stop() - sequencer?.close() - } -}*/ +} \ No newline at end of file