Compare commits

...

4 commits

5 changed files with 215 additions and 110 deletions

View file

@ -2,8 +2,19 @@ package mg.dot.feufaro
import android.content.Context import android.content.Context
import org.koin.core.context.GlobalContext import org.koin.core.context.GlobalContext
import java.io.File
actual fun getConfigDirectoryPath(): String { actual fun getConfigDirectoryPath(): String {
val context = GlobalContext.get().get<Context>() val baseDir = GlobalContext.get().get<Context>().filesDir
return context.filesDir.absolutePath val feufa2Folder = File(baseDir, "Feufaro")
if (!feufa2Folder.exists()) {
val created = feufa2Folder.mkdirs()
if (created) {
println("CP:15: Dossier créé avec succès : ${feufa2Folder.absolutePath}")
}
}
val path = feufa2Folder.absolutePath
return if (path.endsWith(File.separator)) path else path + File.separator
} }

View file

@ -1,129 +1,142 @@
package mg.dot.feufaro.midi package mg.dot.feufaro.midi
import java.io.File
import android.media.MediaPlayer import android.media.MediaPlayer
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File
import java.io.FileInputStream
private var androidMediaPlayer: MediaPlayer?= null private var androidMediaPlayer: MediaPlayer?= null
actual class MediaPlayer actual constructor(filename: String, onFinished: () -> Unit) { actual class MediaPlayer actual constructor(filename: String, onFinished: () -> Unit) {
private val player = androidMediaPlayer?.apply { private var mediaPlayer: MediaPlayer? = MediaPlayer()
setDataSource(filename)
prepare()
setOnCompletionListener {
seekTo(0)
onFinished()
}
// val file = File(android.app.Instrumentation().context.filesDir, filename)
// if(file.exists()){
// androidMediaPlayer?.setDataSource(file.path)
// androidMediaPlayer?.prepare()
// androidMediaPlayer?.setOnCompletionListener {
// onFinished()
// }
// } else {
// println("Fichier midi non trouvée")
// }
}
private val voiceStates = mutableListOf(true, true, true, true) private val voiceStates = mutableListOf(true, true, true, true)
private var currentGlobalVolume: Float = 0.8f // private var currentGlobalVolume: Float = 0.8f
private var pointA: Long = -1L private var pointA: Long = -1L
private var pointB: Long = -1L private var pointB: Long = -1L
private val playerScope = CoroutineScope(Dispatchers.Default + SupervisorJob())
private var abJob: Job? = null
private var isLoopingAB: Boolean = false private var isLoopingAB: Boolean = false
init {
try {
val file = File(filename)
if (file.exists()) {
val fis = FileInputStream(file)
mediaPlayer?.setDataSource(fis.fd)
mediaPlayer?.prepare()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
val params = mediaPlayer?.playbackParams ?: android.media.PlaybackParams()
params.speed = 1.0f
mediaPlayer?.playbackParams = params
}
fis.close()
mediaPlayer?.setOnCompletionListener {
onFinished()
}
}
} catch (e: Exception) {
e.printStackTrace()
}
}
actual fun play() { actual fun play() {
// player?.start() mediaPlayer?.start()
} }
actual fun pause() { actual fun pause() {
// player?.pause() mediaPlayer?.pause()
} }
actual fun stop() { actual fun stop() {
// player?.stop() mediaPlayer?.stop()
mediaPlayer?.prepare()
mediaPlayer?.seekTo(0)
clearLoop()
} }
actual fun getDuration(): Long { actual fun getDuration(): Long {
// player!!.duration.toLong() ?: 0L return mediaPlayer?.duration?.toLong() ?: 0L
return 4.toLong()
} }
actual fun getCurrentPosition(): Long { actual fun getCurrentPosition(): Long {
// player!!.currentPosition.toLong() return mediaPlayer?.currentPosition?.toLong() ?: 0L
return 4.toLong()
} }
actual fun seekTo(position: Long) { actual fun seekTo(position: Long) {
// player!!.seekTo(position.toInt()) mediaPlayer?.seekTo(position.toInt())
}
actual fun setVolume(level: Float) {
mediaPlayer?.setVolume(level, level)
} }
actual fun setVolume(level: Float){ actual fun setPointA() {
// currentGlobalVolume =level pointA = mediaPlayer?.currentPosition?.toLong() ?: 0L
// player?.setVolume(level, level)
} }
actual fun getLoopState() = Triple(pointA, pointB, isLoopingAB) actual fun setPointB() {
pointB = mediaPlayer?.currentPosition?.toLong() ?: 0L
actual fun toggleVoice(index: Int): Unit{ if (pointB > pointA && pointA != -1L) {
voiceStates[index] = !voiceStates[index]
}
actual fun getVoiceStates(): List<Boolean> = voiceStates
actual fun changeInstru(noInstru: Int): Unit{
}
actual fun setPointA(): Unit{
// pointA = getCurrentPosition()
}
actual fun setPointB(): Unit{
pointB = getCurrentPosition()
if(pointB > pointA) {
isLoopingAB = true isLoopingAB = true
startABMonitor() startABMonitor()
} }
} }
actual fun clearLoop(): Unit{ actual fun clearLoop() {
isLoopingAB = false isLoopingAB = false
pointA = -1L
pointB = -1L
abJob?.cancel()
} }
private fun startABMonitor(){
GlobalScope.launch { private fun startABMonitor() {
while(isLoopingAB){ abJob?.cancel()
// if ((player?.currentPosition ?: 0) >= pointB){ abJob = playerScope.launch {
// player?.seekTo(pointA?.toInt()) while (isLoopingAB) {
// } val currentPos = mediaPlayer?.currentPosition?.toLong() ?: 0L
if (currentPos >= pointB) {
mediaPlayer?.seekTo(pointA.toInt())
}
delay(50) delay(50)
} }
} }
} }
actual fun setTempo(factor: Float){
actual fun getLoopState() = Triple(pointA, pointB, isLoopingAB)
actual fun toggleVoice(index: Int) {
voiceStates[index] = !voiceStates[index]
println("Toggle voice $index (Limitation: Nécessite SoundFont/Fluidsynth sur Android)")
}
actual fun getVoiceStates(): List<Boolean> = voiceStates
actual fun changeInstru(noInstru: Int) {
}
actual fun setTempo(factor: Float) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
mediaPlayer?.let {
val params = it.playbackParams
params.speed = factor
it.playbackParams = params
}
}
} }
actual fun getCurrentBPM(): Float { actual fun getCurrentBPM(): Float {
return 120f return 120f
} }
}
/*
actual fun MidiPlayer(filename: String, onFinished: () -> Unit) {
StopMidi()
val file = File(android.app.Instrumentation().context.filesDir, filename)
if(file.exists()){
androidMediaPlayer?.setDataSource(file.path)
androidMediaPlayer?.prepare()
androidMediaPlayer?.setOnCompletionListener {
onFinished()
}
androidMediaPlayer?.start()
}
}
actual fun StopMidi() { fun release() {
androidMediaPlayer?.let { mediaPlayer?.release()
if (it.isPlaying) { mediaPlayer = null
it.stop() playerScope.coroutineContext.cancel()
it.release()
}
} }
androidMediaPlayer = null }
}*/

View file

@ -34,6 +34,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntOffset
@ -64,6 +65,7 @@ object ScreenSolfa : Screen {
val gridTUOData = GridTUOData(measure, tuoList, stanza) val gridTUOData = GridTUOData(measure, tuoList, stanza)
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
var viewportHeight by remember { mutableStateOf(0) }
var isScanning by remember { mutableStateOf(false) } var isScanning by remember { mutableStateOf(false) }
var qrCodeResult by remember { mutableStateOf("Aucun code scanné") } var qrCodeResult by remember { mutableStateOf("Aucun code scanné") }
@ -75,7 +77,8 @@ object ScreenSolfa : Screen {
qrCodeResult = qrCodeResult, qrCodeResult = qrCodeResult,
onScannerButtonClick = { onScannerButtonClick = {
isScanning = true isScanning = true
} },
solfaScrollState = scrollState
) { paddingValues -> ) { paddingValues ->
Box( Box(
Modifier.fillMaxSize().padding(paddingValues) Modifier.fillMaxSize().padding(paddingValues)
@ -91,6 +94,9 @@ object ScreenSolfa : Screen {
showContextualMenu = true showContextualMenu = true
} }
) )
}
.onGloballyPositioned { coordinates ->
viewportHeight = coordinates.size.height
}, },
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
@ -192,7 +198,7 @@ object ScreenSolfa : Screen {
} }
Row ( Row (
modifier = Modifier modifier = Modifier
.height(199.dp) .height(75.dp)
) { ) {
} }
} }

View file

@ -50,6 +50,7 @@ fun MainScreenWithDrawer(
sharedScreenModel: SharedScreenModel, sharedScreenModel: SharedScreenModel,
qrCodeResult: String, qrCodeResult: String,
onScannerButtonClick: () -> Unit, onScannerButtonClick: () -> Unit,
solfaScrollState: ScrollState,
content: @Composable (PaddingValues) -> Unit, content: @Composable (PaddingValues) -> Unit,
) { ) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
@ -79,7 +80,7 @@ val isPos by sharedScreenModel.isPos.collectAsState()
var isDragging = sharedScreenModel.isDragging var isDragging = sharedScreenModel.isDragging
val currentPos by sharedScreenModel.currentPos.collectAsState() val currentPos by sharedScreenModel.currentPos.collectAsState()
val duration by sharedScreenModel.duration.collectAsState() val duration by sharedScreenModel.duration.collectAsState()
val midiFile = "whawyd3.mid" val midiFile = "whawyd3.mid" //What do we gain by all our hard work?
var refreshTrigeer by remember { mutableStateOf(0)} var refreshTrigeer by remember { mutableStateOf(0)}
val volumelevel by sharedScreenModel.volumeLevel.collectAsState() val volumelevel by sharedScreenModel.volumeLevel.collectAsState()
@ -248,6 +249,7 @@ LaunchedEffect(isPlay, isPos) {
currentPos = currentPos, currentPos = currentPos,
volume = volumelevel, volume = volumelevel,
duration = duration, duration = duration,
solfaScrollState,
onPlayPauseClick = { onPlayPauseClick = {
sharedScreenModel.togglePlayPause() sharedScreenModel.togglePlayPause()
}, },

View file

@ -1,6 +1,11 @@
package mg.dot.feufaro.ui package mg.dot.feufaro.ui
import androidx.compose.animation.* import androidx.compose.animation.*
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.animation.core.ExperimentalAnimationSpecApi
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.*
@ -34,6 +39,7 @@ fun MidiControlPanel(
currentPos: Float, currentPos: Float,
volume: Float, volume: Float,
duration: Float, duration: Float,
solfaScrollState: ScrollState,
onPlayPauseClick: () -> Unit, onPlayPauseClick: () -> Unit,
onSeek: (Float) -> Unit, onSeek: (Float) -> Unit,
onVolumeChange: (Float) -> Unit, onVolumeChange: (Float) -> Unit,
@ -61,6 +67,26 @@ fun MidiControlPanel(
var showSATBTools by remember { mutableStateOf(false) } var showSATBTools by remember { mutableStateOf(false) }
var expandedCtl by remember { mutableStateOf(false) } var expandedCtl by remember { mutableStateOf(false) }
val platform = getPlatform().name
val coroutineScope = rememberCoroutineScope()
LaunchedEffect(currentPos, duration, solfaScrollState.viewportSize, solfaScrollState.maxValue) {
if (duration > 0f && solfaScrollState.maxValue > 0) {
val progress = (currentPos / duration).coerceIn(0f, 1f)
val viewportHeight = solfaScrollState.viewportSize.toFloat()
val totalContentHeight = solfaScrollState.maxValue + viewportHeight
val targetY = (progress * (totalContentHeight-200)) - (viewportHeight / 2)
val finalTarget = targetY.coerceIn(0f, solfaScrollState.maxValue.toFloat())
solfaScrollState.animateScrollTo(
value = finalTarget.toInt(),
animationSpec = tween (
durationMillis = 300,
easing = LinearEasing
)
)
}
}
LaunchedEffect(tempo) { LaunchedEffect(tempo) {
currentBpm = mediaPlayer.getCurrentBPM() currentBpm = mediaPlayer.getCurrentBPM()
} }
@ -79,7 +105,7 @@ fun MidiControlPanel(
tempo = newFactor tempo = newFactor
mediaPlayer?.setTempo(newFactor) mediaPlayer?.setTempo(newFactor)
println("tempo : $tempo") // println("tempo : $tempo")
} }
Column( Column(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
@ -91,8 +117,7 @@ fun MidiControlPanel(
) { ) {
Column( Column(
modifier = modifier modifier = modifier
.fillMaxWidth(if (getPlatform().name.startsWith("Android")) 0.9f else 0.6f) .fillMaxWidth(if (platform.startsWith("Android")) 1f else 0.6f)
.padding(16.dp)
.background(color = Color.Gray.copy(alpha = 0.5f), shape = RoundedCornerShape(size = 5.dp)), .background(color = Color.Gray.copy(alpha = 0.5f), shape = RoundedCornerShape(size = 5.dp)),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
@ -214,7 +239,7 @@ fun MidiControlPanel(
slideInHorizontally { -it } + fadeIn() togetherWith slideOutHorizontally { it } + fadeOut() slideInHorizontally { -it } + fadeIn() togetherWith slideOutHorizontally { it } + fadeOut()
} }
}, },
modifier = Modifier.padding(horizontal = 4.dp) modifier = Modifier.padding(horizontal = 2.dp)
) { displayedBpm -> ) { displayedBpm ->
val isCenter = displayedBpm == currentBpmInt val isCenter = displayedBpm == currentBpmInt
Text( Text(
@ -225,7 +250,7 @@ fun MidiControlPanel(
modifier = Modifier modifier = Modifier
.clip(CircleShape) .clip(CircleShape)
.clickable(enabled = !isCenter) { updateTempoToBpm(displayedBpm) } .clickable(enabled = !isCenter) { updateTempoToBpm(displayedBpm) }
.padding(horizontal = 4.dp) .padding(horizontal = 2.dp)
) )
} }
} }
@ -333,7 +358,26 @@ fun MidiControlPanel(
} }
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
Column { if(platform.startsWith("Android")) {
Column(
modifier = Modifier.wrapContentWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
IconButton(
onClick = onPlayPauseClick,
modifier = Modifier.size(48.dp).background(MaterialTheme.colorScheme.primary, CircleShape)
) {
Icon(
imageVector = if (isPause) Icons.Filled.PlayArrow else Icons.Filled.Pause,
contentDescription = "Pla",
tint = Color.White
)
}
}
Spacer(modifier = Modifier.weight(1f))
Column {
IconButton( IconButton(
onClick = { onClick = {
isPianoSelected = !isPianoSelected isPianoSelected = !isPianoSelected
@ -361,29 +405,58 @@ fun MidiControlPanel(
} }
} }
} }
} else {
Column {
IconButton(
onClick = {
isPianoSelected = !isPianoSelected
if (isPianoSelected) {
mediaPlayer?.changeInstru(1)
} else {
mediaPlayer?.changeInstru(20)
}
Spacer(modifier = Modifier.weight(1f)) }
) {
Column( if (isPianoSelected) {
modifier = Modifier.wrapContentWidth(), Icon(
horizontalAlignment = Alignment.CenterHorizontally Icons.Default.Piano,
) { contentDescription = "Piano",
IconButton( tint = Color.Black
onClick = onPlayPauseClick, )
modifier = Modifier.size(48.dp).background(MaterialTheme.colorScheme.primary, CircleShape) } else {
) { Icon(
Icon( painter = painterResource(Res.drawable.ic_organ),
imageVector = if (isPause) Icons.Filled.PlayArrow else Icons.Filled.Pause, contentDescription = "Orgue",
contentDescription = "Pla", tint = Color.Black,
tint = Color.White modifier = Modifier.size(25.dp)
) )
}
}
} }
}
Spacer(modifier = Modifier.weight(1f))
Column(
modifier = Modifier.wrapContentWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
IconButton(
onClick = onPlayPauseClick,
modifier = Modifier.size(48.dp).background(MaterialTheme.colorScheme.primary, CircleShape)
) {
Icon(
imageVector = if (isPause) Icons.Filled.PlayArrow else Icons.Filled.Pause,
contentDescription = "Pla",
tint = Color.White
)
}
}
}
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
val platform = getPlatform() if (platform.startsWith("Java")) {
if (platform.name.startsWith("Java")) {
ModernVolumeSlider( ModernVolumeSlider(
volume, onVolumeChange = onVolumeChange volume, onVolumeChange = onVolumeChange
) )