added MidiPlayer
This commit is contained in:
parent
5ae8e292c3
commit
5733709853
8 changed files with 1400 additions and 296 deletions
|
|
@ -0,0 +1,129 @@
|
||||||
|
package mg.dot.feufaro.midi
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import android.media.MediaPlayer
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
private var androidMediaPlayer: MediaPlayer?= null
|
||||||
|
|
||||||
|
actual class MediaPlayer actual constructor(filename: String, onFinished: () -> Unit) {
|
||||||
|
private val player = androidMediaPlayer?.apply {
|
||||||
|
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 var currentGlobalVolume: Float = 0.8f
|
||||||
|
|
||||||
|
private var pointA: Long = -1L
|
||||||
|
private var pointB: Long = -1L
|
||||||
|
private var isLoopingAB: Boolean = false
|
||||||
|
actual fun play() {
|
||||||
|
// player?.start()
|
||||||
|
}
|
||||||
|
actual fun pause() {
|
||||||
|
// player?.pause()
|
||||||
|
}
|
||||||
|
actual fun stop() {
|
||||||
|
// player?.stop()
|
||||||
|
}
|
||||||
|
actual fun getDuration(): Long {
|
||||||
|
// player!!.duration.toLong() ?: 0L
|
||||||
|
return 4.toLong()
|
||||||
|
}
|
||||||
|
actual fun getCurrentPosition(): Long {
|
||||||
|
// player!!.currentPosition.toLong()
|
||||||
|
return 4.toLong()
|
||||||
|
}
|
||||||
|
actual fun seekTo(position: Long) {
|
||||||
|
// player!!.seekTo(position.toInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun setVolume(level: Float){
|
||||||
|
// currentGlobalVolume =level
|
||||||
|
// player?.setVolume(level, level)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getLoopState() = Triple(pointA, pointB, isLoopingAB)
|
||||||
|
|
||||||
|
actual fun toggleVoice(index: Int): Unit{
|
||||||
|
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
|
||||||
|
startABMonitor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun clearLoop(): Unit{
|
||||||
|
isLoopingAB = false
|
||||||
|
}
|
||||||
|
private fun startABMonitor(){
|
||||||
|
GlobalScope.launch {
|
||||||
|
while(isLoopingAB){
|
||||||
|
// if ((player?.currentPosition ?: 0) >= pointB){
|
||||||
|
// player?.seekTo(pointA?.toInt())
|
||||||
|
// }
|
||||||
|
delay(50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual fun setTempo(factor: Float){
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getCurrentBPM(): Float {
|
||||||
|
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() {
|
||||||
|
androidMediaPlayer?.let {
|
||||||
|
if (it.isPlaying) {
|
||||||
|
it.stop()
|
||||||
|
it.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
androidMediaPlayer = null
|
||||||
|
}*/
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package mg.dot.feufaro.midi
|
||||||
|
|
||||||
|
import mg.dot.feufaro.FileRepository
|
||||||
|
|
||||||
|
expect class MediaPlayer(filename: String, onFinished: () -> Unit) {
|
||||||
|
fun play()
|
||||||
|
fun pause()
|
||||||
|
fun stop()
|
||||||
|
fun getDuration(): Long
|
||||||
|
fun getLoopState(): Triple<Long, Long, Boolean>
|
||||||
|
fun toggleVoice(index: Int)
|
||||||
|
fun getVoiceStates(): List<Boolean>
|
||||||
|
fun changeInstru(noInstru: Int)
|
||||||
|
fun getCurrentPosition(): Long
|
||||||
|
fun seekTo(position: Long)
|
||||||
|
fun setVolume(level: Float)
|
||||||
|
fun setPointA()
|
||||||
|
fun setPointB()
|
||||||
|
fun clearLoop()
|
||||||
|
fun setTempo(factor: Float)
|
||||||
|
fun getCurrentBPM(): Float
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
expect fun MidiPlayer(filename: String, onFinished: () -> Unit)
|
||||||
|
|
||||||
|
expect fun StopMidi()*/
|
||||||
|
|
@ -20,6 +20,7 @@ import androidx.compose.ui.draw.drawBehind
|
||||||
import androidx.compose.ui.draw.drawWithContent
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.foundation.Canvas
|
import androidx.compose.foundation.Canvas
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.onSizeChanged
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
|
|
@ -35,6 +36,7 @@ import androidx.compose.ui.unit.sp
|
||||||
import mg.dot.feufaro.data.GridTUOData
|
import mg.dot.feufaro.data.GridTUOData
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import androidx.compose.ui.geometry.Size
|
import androidx.compose.ui.geometry.Size
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
import androidx.compose.ui.text.buildAnnotatedString
|
||||||
|
|
@ -302,9 +304,9 @@ fun TimeUnitComposable(
|
||||||
}
|
}
|
||||||
AutoResizingText(
|
AutoResizingText(
|
||||||
text = text,
|
text = text,
|
||||||
minFontSize = 8.sp,
|
minFontSize = 18.sp,
|
||||||
maxFontSize = 18.sp,
|
maxFontSize = 18.sp,
|
||||||
maxLines = 1,
|
maxLines = 2,
|
||||||
fontStyle = fontStyle,
|
fontStyle = fontStyle,
|
||||||
fontWeight = fontWeight,
|
fontWeight = fontWeight,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
|
@ -419,9 +421,9 @@ fun TimeUnitComposable(
|
||||||
}
|
}
|
||||||
AutoResizingText(
|
AutoResizingText(
|
||||||
text = tuo.lyricsAsMultiString(stanzaNumber),
|
text = tuo.lyricsAsMultiString(stanzaNumber),
|
||||||
minFontSize = 8.sp,
|
minFontSize = 16.sp,
|
||||||
maxFontSize = 16.sp,
|
maxFontSize = 16.sp,
|
||||||
maxLines = 1,
|
maxLines = 2,
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -431,6 +433,7 @@ fun TimeUnitComposable(
|
||||||
fun bestTUOWidth(items: List<TimeUnitObject>): Dp {
|
fun bestTUOWidth(items: List<TimeUnitObject>): Dp {
|
||||||
val textMeasurer = rememberTextMeasurer()
|
val textMeasurer = rememberTextMeasurer()
|
||||||
val density = LocalDensity.current
|
val density = LocalDensity.current
|
||||||
|
|
||||||
var maxWidth by remember { mutableStateOf(0.dp) }
|
var maxWidth by remember { mutableStateOf(0.dp) }
|
||||||
LaunchedEffect(items) {
|
LaunchedEffect(items) {
|
||||||
maxWidth = 0.dp
|
maxWidth = 0.dp
|
||||||
|
|
@ -494,7 +497,8 @@ fun AutoResizingText(
|
||||||
fontWeight = fontWeight,
|
fontWeight = fontWeight,
|
||||||
// Le modificateur drawWithContent est utilisé pour retarder le dessin
|
// Le modificateur drawWithContent est utilisé pour retarder le dessin
|
||||||
// jusqu'à ce que la taille de police finale soit déterminée.
|
// jusqu'à ce que la taille de police finale soit déterminée.
|
||||||
modifier = Modifier.fillMaxWidth() // Le modifier du Text interne peut être ajusté
|
softWrap = false,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
.drawWithContent {
|
.drawWithContent {
|
||||||
if (readyToDraw) {
|
if (readyToDraw) {
|
||||||
drawContent()
|
drawContent()
|
||||||
|
|
@ -557,7 +561,7 @@ fun LazyVerticalGridTUO(
|
||||||
.fillMaxWidth(flowRowSize)
|
.fillMaxWidth(flowRowSize)
|
||||||
.combinedClickable(
|
.combinedClickable(
|
||||||
onClick = {
|
onClick = {
|
||||||
println("Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex")
|
println("Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex FL $flowRowSize GC $gridColumnCount")
|
||||||
} ,
|
} ,
|
||||||
onDoubleClick = {
|
onDoubleClick = {
|
||||||
println("Double-Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex")
|
println("Double-Clicked: ${oneTUO.numBlock} / relative index: $relativeIndex")
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,21 @@ package mg.dot.feufaro.ui
|
||||||
|
|
||||||
import SharedScreenModel
|
import SharedScreenModel
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.scaleIn
|
||||||
|
import androidx.compose.animation.scaleOut
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
import androidx.compose.foundation.*
|
import androidx.compose.foundation.*
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.*
|
import androidx.compose.material.icons.filled.*
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.*
|
||||||
|
|
@ -18,16 +27,22 @@ import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.RectangleShape
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.platform.*
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import mg.dot.feufaro.data.DrawerItem
|
import mg.dot.feufaro.data.DrawerItem
|
||||||
import mg.dot.feufaro.data.getDrawerItems
|
import mg.dot.feufaro.data.getDrawerItems
|
||||||
import mg.dot.feufaro.getPlatform
|
import mg.dot.feufaro.getPlatform
|
||||||
|
import mg.dot.feufaro.midi.MediaPlayer
|
||||||
|
//import mg.dot.feufaro.midi.MidiPlayer
|
||||||
|
//import mg.dot.feufaro.midi.StopMidi
|
||||||
import mg.dot.feufaro.solfa.Solfa
|
import mg.dot.feufaro.solfa.Solfa
|
||||||
import mg.dot.feufaro.viewmodel.SolfaScreenModel
|
import mg.dot.feufaro.viewmodel.SolfaScreenModel
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -55,42 +70,348 @@ fun MainScreenWithDrawer(
|
||||||
var isSearchActive by remember { mutableStateOf(false) }
|
var isSearchActive by remember { mutableStateOf(false) }
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
|
var isExpanded by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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 midiFile = "whawyd3.mid"
|
||||||
|
var refreshTrigeer by remember { mutableStateOf(0)}
|
||||||
|
|
||||||
|
var volumelevel by remember { mutableStateOf(0.8f) }
|
||||||
|
|
||||||
|
val mediaPlayer = remember(refreshTrigeer) {
|
||||||
|
MediaPlayer(filename = midiFile, onFinished = {
|
||||||
|
isPos = true
|
||||||
|
isPlay = false
|
||||||
|
currentPos = 0f
|
||||||
|
// isPlayMid = true
|
||||||
|
println("fin de lecture du whawyd3.mid")
|
||||||
|
|
||||||
|
}).apply { setVolume(volumelevel) }
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(isPlay, isPos, mediaPlayer) {
|
||||||
|
if (isPlay && !isPos) {
|
||||||
|
val d = mediaPlayer.getDuration().toFloat()
|
||||||
|
if (d > 0) duration = d
|
||||||
|
while (isPlay && !isPos) {
|
||||||
|
if(!isDragging) {
|
||||||
|
val p = mediaPlayer.getCurrentPosition().toFloat()
|
||||||
|
if (p >= 0) currentPos = p
|
||||||
|
}
|
||||||
|
delay(100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(isSearchActive) {
|
LaunchedEffect(isSearchActive) {
|
||||||
if (isSearchActive) {
|
if (isSearchActive) {
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ModalNavigationDrawer(
|
ModalNavigationDrawer(drawerState = drawerState, drawerContent = {
|
||||||
drawerState = drawerState,
|
SimpleDrawerContent(
|
||||||
drawerContent = {
|
items,
|
||||||
SimpleDrawerContent(
|
solfaScreenModel,
|
||||||
items, solfaScreenModel, sharedScreenModel, currentActivePath, drawerState, scope,
|
sharedScreenModel,
|
||||||
onScannerButtonClick = {
|
currentActivePath,
|
||||||
scope.launch { drawerState.close() }
|
drawerState,
|
||||||
onScannerButtonClick()
|
scope,
|
||||||
}
|
onScannerButtonClick = {
|
||||||
|
scope.launch { drawerState.close() }
|
||||||
|
onScannerButtonClick()
|
||||||
|
},
|
||||||
|
onSongSelected = { newSong ->
|
||||||
|
mediaPlayer?.stop()
|
||||||
|
isPos = true
|
||||||
|
isPlay = false
|
||||||
|
currentPos = 0f
|
||||||
|
refreshTrigeer++
|
||||||
|
}
|
||||||
)
|
)
|
||||||
},
|
}, content = {
|
||||||
content = {
|
Scaffold(contentWindowInsets = WindowInsets(0, 0, 0, 0), topBar = {
|
||||||
Scaffold(
|
TopAppBar(
|
||||||
contentWindowInsets = WindowInsets(0, 0, 0, 0),
|
modifier = Modifier.height(55.dp).windowInsetsPadding(WindowInsets.statusBars), title = {
|
||||||
topBar = {
|
Column(
|
||||||
TopAppBar(
|
modifier = Modifier.fillMaxSize().verticalScroll(scrollState)
|
||||||
modifier = Modifier
|
) {
|
||||||
.height(55.dp)
|
Text(
|
||||||
.windowInsetsPadding(WindowInsets.statusBars),
|
songTitle,
|
||||||
title = {
|
modifier = Modifier.weight(1f, fill = true),
|
||||||
Column (
|
maxLines = 1,
|
||||||
modifier= Modifier
|
softWrap = false,
|
||||||
.fillMaxSize()
|
overflow = TextOverflow.Ellipsis,
|
||||||
.verticalScroll(scrollState)
|
)
|
||||||
|
}
|
||||||
|
}, navigationIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
scope.launch { drawerState.open() }
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Filled.Menu, contentDescription = "Ouvrir Menu")
|
||||||
|
}
|
||||||
|
}, actions = {/* Text(
|
||||||
|
text = measure,
|
||||||
|
modifier = Modifier.weight(1f, fill = false),
|
||||||
|
fontSize = 20.sp,
|
||||||
|
maxLines = 2,
|
||||||
|
softWrap = false,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(8.dp))*/
|
||||||
|
Text(
|
||||||
|
text = songKey,
|
||||||
|
fontSize = 25.sp,
|
||||||
|
fontWeight = FontWeight.Black,
|
||||||
|
)
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
|
||||||
|
}, colors = TopAppBarColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.primary,
|
||||||
|
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
actionIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
scrolledContainerColor = MaterialTheme.colorScheme.onPrimary,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}, floatingActionButton = {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth() //horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(5.dp), horizontalAlignment = Alignment.End,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(7.dp)
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isExpanded and !isPlayMid,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
sharedScreenModel.toggleQRCodeVisibility()
|
||||||
|
isExpanded = false
|
||||||
|
}, modifier = Modifier.alpha(0.45f)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.QrCode,
|
||||||
|
contentDescription = "share qr",
|
||||||
|
tint = Color.Blue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isExpanded and !isPlayMid,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {}, modifier = Modifier.alpha(0.45f)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Filled.Print,
|
||||||
|
contentDescription = "Imprimer",
|
||||||
|
tint = Color.Blue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isExpanded,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
isPlayMid = !isPlayMid
|
||||||
|
if(mediaPlayer.getCurrentPosition() != 0L) {
|
||||||
|
mediaPlayer?.seekTo(0)
|
||||||
|
mediaPlayer?.stop()
|
||||||
|
}
|
||||||
|
}, modifier = Modifier.alpha(0.45f)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if(isPlayMid) Icons.Filled.StopCircle else Icons.Filled.PlayCircle,
|
||||||
|
contentDescription = "Jouer",
|
||||||
|
tint = Color.Blue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!isPlayMid) {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
isExpanded = !isExpanded
|
||||||
|
refreshTrigeer++
|
||||||
|
}, modifier = Modifier.alpha(0.45f)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isExpanded) Icons.Filled.Close else Icons.Filled.Menu,
|
||||||
|
contentDescription = "MenuFermer",
|
||||||
|
tint = Color.Blue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isPlayMid,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth(0.9f)
|
||||||
|
) {
|
||||||
|
MidiControlPanel(
|
||||||
|
isPause = isPos,
|
||||||
|
currentPos = currentPos,
|
||||||
|
volume = volumelevel,
|
||||||
|
duration = duration,
|
||||||
|
onPlayPauseClick = {
|
||||||
|
if(isPlay){
|
||||||
|
mediaPlayer?.pause()
|
||||||
|
isPlay = false
|
||||||
|
isPos = true
|
||||||
|
} else {
|
||||||
|
if(currentPos == 0f) {
|
||||||
|
mediaPlayer?.seekTo(0)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}*/
|
||||||
|
|
||||||
|
println("je clique pause = $isPos play = $isPlay")
|
||||||
|
// 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")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
/*Row(
|
||||||
|
modifier = Modifier.align(Alignment.Center).padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
|
Slider(
|
||||||
|
value = currentPos,
|
||||||
|
onValueChange = { newPos ->
|
||||||
|
currentPos = newPos
|
||||||
|
mediaPlayer?.seekTo(newPos.toLong())
|
||||||
|
},
|
||||||
|
valueRange = 0f..duration
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
isPos = !isPos
|
||||||
|
if(isPlay) mediaPlayer?.pause() else mediaPlayer?.play()
|
||||||
|
if(isPos) mediaPlayer?.pause() else mediaPlayer?.play()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(if (isPos) "Pause" else "play Midi")
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}) { paddingValues ->
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize().padding(paddingValues).windowInsetsPadding(WindowInsets.ime)
|
||||||
|
) {
|
||||||
|
content(PaddingValues(0.dp))
|
||||||
|
if (sharedScreenModel.isQRCodeVisible.value) {
|
||||||
|
QRDisplay(sharedScreenModel = sharedScreenModel)
|
||||||
|
} else {
|
||||||
|
if (filteredSongs.isNotEmpty()) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth().fillMaxHeight().align(Alignment.TopCenter)
|
||||||
|
.background(MaterialTheme.colorScheme.surface).border(
|
||||||
|
1.dp,
|
||||||
|
MaterialTheme.colorScheme.outlineVariant,
|
||||||
|
shape = MaterialTheme.shapes.medium
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (filteredSongs.isNotEmpty()) {
|
||||||
|
LazyColumn(Modifier.fillMaxSize()) {
|
||||||
|
itemsIndexed(filteredSongs) { index, item ->
|
||||||
|
ListItem(
|
||||||
|
headlineContent = { Text(item.title) },
|
||||||
|
//supportingContent = { Text(item.contentTitle, maxLines = 1) },
|
||||||
|
modifier = Modifier.clickable {
|
||||||
|
sharedScreenModel.updateSearchTxt("")
|
||||||
|
sharedScreenModel.reset()
|
||||||
|
solfaScreenModel.loadFromFile(item.path)
|
||||||
|
isSearchActive = false
|
||||||
|
})
|
||||||
|
HorizontalDivider()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content(paddingValues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize().fillMaxWidth(0.75f).align(Alignment.Center)
|
||||||
|
.padding(paddingValues)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.align(Alignment.TopEnd).padding(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
|
||||||
|
) {
|
||||||
|
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = isSearchActive,
|
targetState = isSearchActive, label = "Search Transition"
|
||||||
label = "Search Transition"
|
) { targetIsActive ->
|
||||||
) {
|
if (targetIsActive) {
|
||||||
targetIsActive ->
|
|
||||||
if(targetIsActive) {
|
|
||||||
TextField(
|
TextField(
|
||||||
value = textInput,
|
value = textInput,
|
||||||
onValueChange = { newValue ->
|
onValueChange = { newValue ->
|
||||||
|
|
@ -98,276 +419,76 @@ fun MainScreenWithDrawer(
|
||||||
sharedScreenModel.updateSearchTxt(newValue)
|
sharedScreenModel.updateSearchTxt(newValue)
|
||||||
},
|
},
|
||||||
placeholder = {
|
placeholder = {
|
||||||
Text("Rechercher...")
|
Text("... ...")
|
||||||
},
|
},
|
||||||
leadingIcon = {
|
/*leadingIcon = {
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Search,
|
imageVector = Icons.Default.Search,
|
||||||
contentDescription = "Icône de recherche",
|
contentDescription = "Icône de recherche",
|
||||||
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
tint = MaterialTheme.colorScheme.primary
|
||||||
)
|
)
|
||||||
},
|
},*/
|
||||||
textStyle = MaterialTheme.typography.titleLarge.copy(
|
textStyle = MaterialTheme.typography.titleLarge.copy(
|
||||||
fontSize = 17.sp,
|
fontSize = 17.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.onSurface
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier.focusRequester(focusRequester).fillMaxWidth(0.45f)
|
||||||
.focusRequester(focusRequester)
|
.border(
|
||||||
.fillMaxWidth()
|
width = 2.dp,
|
||||||
)
|
color = Color.Gray,
|
||||||
} else {
|
shape = RoundedCornerShape(8.dp)
|
||||||
Text(songTitle,
|
)
|
||||||
modifier = Modifier.weight(1f, fill = true),
|
.alpha(0.6f)
|
||||||
maxLines = 1,
|
|
||||||
softWrap = false,
|
|
||||||
overflow = TextOverflow.Ellipsis,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
Column(
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = {
|
|
||||||
scope.launch { drawerState.open() }
|
|
||||||
}) {
|
|
||||||
Icon(Icons.Filled.Menu, contentDescription = "Ouvrir Menu")
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
if (isSearchActive) {
|
|
||||||
IconButton(onClick = {
|
|
||||||
textInput = ""
|
|
||||||
isSearchActive = false
|
|
||||||
}) {
|
|
||||||
Icon(Icons.Default.Close, contentDescription = "Annuler la recherche")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = measure,
|
|
||||||
modifier = Modifier.weight(1f, fill = false),
|
|
||||||
fontSize = 20.sp,
|
|
||||||
maxLines = 2,
|
|
||||||
softWrap = false,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
Spacer(Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
text = songKey,
|
|
||||||
fontSize = 25.sp,
|
|
||||||
fontWeight = FontWeight.Black,
|
|
||||||
)
|
|
||||||
Spacer(Modifier.width(8.dp))
|
|
||||||
IconButton(onClick = {
|
|
||||||
isSearchActive = true
|
|
||||||
}) {
|
|
||||||
Icon(Icons.Default.Search, contentDescription = "Rechercher")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
) {
|
||||||
colors = TopAppBarColors(
|
IconButton(
|
||||||
containerColor = MaterialTheme.colorScheme.primary,
|
onClick = {
|
||||||
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
isSearchActive = !isSearchActive
|
||||||
actionIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
sharedScreenModel.updateSearchTxt("")
|
||||||
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
textInput = ""
|
||||||
scrolledContainerColor = MaterialTheme.colorScheme.onPrimary,
|
}, modifier = Modifier.size(56.dp).alpha(0.45f).background(
|
||||||
)
|
color = Color.Blue, shape = CircleShape
|
||||||
)
|
)
|
||||||
},
|
) {
|
||||||
floatingActionButton = {
|
Icon(
|
||||||
FloatingActionButton (
|
if(isSearchActive) Icons.Default.Close else Icons.Default.Search,
|
||||||
onClick = {
|
contentDescription = "la recherche",
|
||||||
sharedScreenModel.toggleQRCodeVisibility()
|
tint = Color.White
|
||||||
},
|
|
||||||
modifier = Modifier.alpha(0.5f)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Filled.Share,
|
|
||||||
contentDescription = "Partager",
|
|
||||||
tint = Color.Blue
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
paddingValues ->
|
|
||||||
|
|
||||||
Box(modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(paddingValues)
|
|
||||||
.windowInsetsPadding(WindowInsets.ime)
|
|
||||||
){
|
|
||||||
content(PaddingValues(0.dp))
|
|
||||||
if (sharedScreenModel.isQRCodeVisible.value) {
|
|
||||||
QRDisplay(sharedScreenModel = sharedScreenModel)
|
|
||||||
} else {
|
|
||||||
if (filteredSongs.isNotEmpty()) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth(0.65f)
|
|
||||||
.heightIn(max = 350.dp)
|
|
||||||
.align(Alignment.TopCenter)
|
|
||||||
.background(MaterialTheme.colorScheme.surface)
|
|
||||||
.border(1.dp, MaterialTheme.colorScheme.outlineVariant, shape = MaterialTheme.shapes.medium)
|
|
||||||
){
|
|
||||||
if (filteredSongs.isNotEmpty()) {
|
|
||||||
LazyColumn(Modifier.fillMaxSize()) {
|
|
||||||
itemsIndexed(filteredSongs){ index, item ->
|
|
||||||
ListItem(
|
|
||||||
headlineContent = { Text(item.title) },
|
|
||||||
supportingContent = { Text(item.path, maxLines = 1) },
|
|
||||||
modifier = Modifier.clickable {
|
|
||||||
sharedScreenModel.updateSearchTxt("")
|
|
||||||
sharedScreenModel.reset()
|
|
||||||
solfaScreenModel.loadFromFile(item.path)
|
|
||||||
isSearchActive = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
HorizontalDivider()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (songTitle.trim().isNotEmpty() && songTitle != sharedScreenModel.songTitle.value) {
|
|
||||||
Text(
|
|
||||||
text = "Aucune chanson trouvée pour \"$songTitle\"",
|
|
||||||
modifier = Modifier.padding(16.dp)
|
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
content(paddingValues)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
/*
|
||||||
|
|
||||||
@Composable
|
var isPlay by mutableStateOf(false)
|
||||||
fun SimpleDrawerContent(
|
private set
|
||||||
items: List<DrawerItem>,
|
|
||||||
solfaScreenModel: SolfaScreenModel,
|
|
||||||
sharedScreenModel: SharedScreenModel,
|
|
||||||
activePath: String = Solfa.currentFile,
|
|
||||||
drawerState: DrawerState,
|
|
||||||
scope: CoroutineScope,
|
|
||||||
onScannerButtonClick: () -> Unit
|
|
||||||
) {
|
|
||||||
var searchTxt by remember { mutableStateOf("") }
|
|
||||||
|
|
||||||
val context = getPlatform()
|
fun playMidi() {
|
||||||
|
if(!isPlay) {
|
||||||
|
isPlay = true
|
||||||
ModalDrawerSheet(
|
MidiPlayer("whawyd3.mid") {
|
||||||
modifier = Modifier.width(300.dp)
|
isPlay = false
|
||||||
) {
|
|
||||||
Box (
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
)
|
|
||||||
{
|
|
||||||
val lazyListState = rememberLazyListState()
|
|
||||||
ScrollableDrawerContent(
|
|
||||||
lazyListState = lazyListState,
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
LazyColumn(
|
|
||||||
state = lazyListState,
|
|
||||||
){
|
|
||||||
stickyHeader {
|
|
||||||
Surface(
|
|
||||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
|
||||||
modifier = Modifier.fillParentMaxWidth()
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
"Liste des solfa disponibles",
|
|
||||||
style = MaterialTheme.typography.titleLarge
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
itemsIndexed(items){ index, item ->
|
|
||||||
val isSelected = item.path == activePath
|
|
||||||
val title = item.title
|
|
||||||
var isFfpm = false
|
|
||||||
var isEws = false
|
|
||||||
var isFF = false
|
|
||||||
|
|
||||||
if (title.startsWith("ffpm")) {
|
|
||||||
isFfpm = true
|
|
||||||
} else if(title.startsWith("ews")) {
|
|
||||||
isEws = true
|
|
||||||
} else {
|
|
||||||
isFF = true
|
|
||||||
}
|
|
||||||
NavigationDrawerItem(
|
|
||||||
label = {
|
|
||||||
Text(item.title)
|
|
||||||
},
|
|
||||||
icon = {
|
|
||||||
val isIcon = when {
|
|
||||||
isFfpm -> Icons.Filled.MenuBook
|
|
||||||
isEws -> Icons.Filled.MusicNote
|
|
||||||
isFF -> Icons.Filled.Book
|
|
||||||
else -> Icons.Filled.Menu
|
|
||||||
}
|
|
||||||
Icon(
|
|
||||||
isIcon,
|
|
||||||
contentDescription = "",
|
|
||||||
tint = Color.Blue
|
|
||||||
)
|
|
||||||
},
|
|
||||||
badge = {
|
|
||||||
Icon(
|
|
||||||
Icons.Filled.Menu,
|
|
||||||
contentDescription = ""
|
|
||||||
)
|
|
||||||
},
|
|
||||||
selected = if(isSelected){ true } else { false },
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
drawerState.close()
|
|
||||||
}
|
|
||||||
sharedScreenModel.reset()
|
|
||||||
solfaScreenModel.loadFromFile(item.path)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
.padding(16.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Row (
|
|
||||||
|
|
||||||
) {
|
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
onScannerButtonClick()
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text("Scanner")
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
|
||||||
Button(
|
|
||||||
onClick = {
|
|
||||||
scope.launch {
|
|
||||||
drawerState.close()
|
|
||||||
}
|
|
||||||
solfaScreenModel.loadCustomFile()
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Text("Importer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun stopMidi() {
|
||||||
|
if(isPlay) {
|
||||||
|
StopMidi()
|
||||||
|
isPlay = false
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,410 @@
|
||||||
|
package mg.dot.feufaro.ui
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.scaleIn
|
||||||
|
import androidx.compose.animation.scaleOut
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardActions
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Church
|
||||||
|
import androidx.compose.material.icons.filled.Clear
|
||||||
|
import androidx.compose.material.icons.filled.ClearAll
|
||||||
|
import androidx.compose.material.icons.filled.Keyboard
|
||||||
|
import androidx.compose.material.icons.filled.Loop
|
||||||
|
import androidx.compose.material.icons.filled.MusicNote
|
||||||
|
import androidx.compose.material.icons.filled.Pause
|
||||||
|
import androidx.compose.material.icons.filled.Piano
|
||||||
|
import androidx.compose.material.icons.filled.PianoOff
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
|
import androidx.compose.material.icons.filled.SettingsVoice
|
||||||
|
import androidx.compose.material.icons.filled.Star
|
||||||
|
import androidx.compose.material.icons.filled.Tonality
|
||||||
|
import androidx.compose.material.icons.filled.Tune
|
||||||
|
import androidx.compose.material.icons.filled.VolumeOff
|
||||||
|
import androidx.compose.material.icons.filled.VolumeUp
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.ButtonDefaults
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FilledIconToggleButton
|
||||||
|
import androidx.compose.material3.FilterChip
|
||||||
|
import androidx.compose.material3.FilterChipDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.SliderDefaults
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import mg.dot.feufaro.midi.MediaPlayer
|
||||||
|
import javax.swing.Icon
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun MidiControlPanel(
|
||||||
|
isPause: Boolean,
|
||||||
|
currentPos: Float,
|
||||||
|
volume: Float,
|
||||||
|
duration: Float,
|
||||||
|
onPlayPauseClick: () -> Unit,
|
||||||
|
onSeek: (Float) -> Unit,
|
||||||
|
onVolumeChange: (Float) -> Unit,
|
||||||
|
mediaPlayer: MediaPlayer,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val momo = duration.toInt() - currentPos.toInt()
|
||||||
|
|
||||||
|
val loopState by produceState(Triple(-1L, -1L, false), mediaPlayer) {
|
||||||
|
while(true) {
|
||||||
|
value = mediaPlayer.getLoopState()
|
||||||
|
delay(200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val voiceStates = mediaPlayer.getVoiceStates()
|
||||||
|
val labels = listOf("S","A","T","B")
|
||||||
|
val fullLabels = listOf("Soprano","Alto","Ténor","Basse")
|
||||||
|
|
||||||
|
var tempo by remember { mutableStateOf(1.0f) }
|
||||||
|
var currentBpm by remember { mutableStateOf(mediaPlayer.getCurrentBPM()) }
|
||||||
|
val basseBpm = 120f
|
||||||
|
var bpmInput by remember { mutableStateOf((basseBpm * tempo).toInt().toString()) }
|
||||||
|
|
||||||
|
var isPianoSelected by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
var showBPMTools by remember { mutableStateOf(false) }
|
||||||
|
var showInstruTools by remember { mutableStateOf(false) }
|
||||||
|
var showSATBTools by remember { mutableStateOf(false) }
|
||||||
|
var showVolumeTools by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
LaunchedEffect(tempo) {
|
||||||
|
currentBpm = mediaPlayer.getCurrentBPM()
|
||||||
|
}
|
||||||
|
Column (
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Row (
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Text("${currentPos.toInt() / 1000}s")
|
||||||
|
Slider(
|
||||||
|
value = currentPos,
|
||||||
|
onValueChange = onSeek,
|
||||||
|
valueRange = 0f..(if (duration > 0) duration else 1f),
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
colors = SliderDefaults.colors(
|
||||||
|
thumbColor = Color.Red,
|
||||||
|
activeTrackColor = Color.Green,
|
||||||
|
inactiveTrackColor = Color.Gray,
|
||||||
|
inactiveTickColor = Color(0xffb0BEC5),
|
||||||
|
disabledThumbColor = Color(0xff78909C),
|
||||||
|
disabledActiveTickColor = Color(0xff757575),
|
||||||
|
disabledActiveTrackColor = Color(0xffBDBDBD),
|
||||||
|
disabledInactiveTickColor = Color(0xff616161),
|
||||||
|
disabledInactiveTrackColor = Color(0xffBCAAA4),
|
||||||
|
),
|
||||||
|
thumb = {
|
||||||
|
Box (
|
||||||
|
modifier = Modifier
|
||||||
|
.size(15.dp)
|
||||||
|
.background(Color.Gray, CircleShape)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
track = { sliderState ->
|
||||||
|
SliderDefaults.Track(
|
||||||
|
sliderState = sliderState,
|
||||||
|
modifier = Modifier.height(5.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Text("${momo / 1000}s")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
){
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
IconButton(
|
||||||
|
onClick = onPlayPauseClick,
|
||||||
|
modifier = Modifier.size(48.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isPause) Icons.Filled.PlayArrow else Icons.Filled.Pause,
|
||||||
|
contentDescription = "Pla",
|
||||||
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = showSATBTools,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Column (
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(vertical = 8.dp),
|
||||||
|
horizontalArrangement = Arrangement. spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
labels.forEachIndexed { index, label ->
|
||||||
|
FilterChip(
|
||||||
|
selected = voiceStates[index],
|
||||||
|
onClick = { mediaPlayer.toggleVoice(index) },
|
||||||
|
label = { Text(label) },
|
||||||
|
colors = FilterChipDefaults.filterChipColors(
|
||||||
|
selectedContainerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
selectedLabelColor = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = showInstruTools,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
Row (
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(10.dp)
|
||||||
|
) {
|
||||||
|
FilledIconToggleButton(
|
||||||
|
checked = isPianoSelected,
|
||||||
|
onCheckedChange = {
|
||||||
|
isPianoSelected = true;
|
||||||
|
mediaPlayer.changeInstru(1)
|
||||||
|
}
|
||||||
|
){
|
||||||
|
Icon(imageVector = Icons.Filled.Piano, contentDescription = "Piano")
|
||||||
|
}
|
||||||
|
FilledIconToggleButton(
|
||||||
|
checked = !isPianoSelected,
|
||||||
|
onCheckedChange = {
|
||||||
|
isPianoSelected = false;
|
||||||
|
mediaPlayer.changeInstru(20)
|
||||||
|
}){
|
||||||
|
Icon(imageVector = Icons.Filled.PianoOff, contentDescription = "Church Organ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = showBPMTools,
|
||||||
|
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
|
||||||
|
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
){
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = bpmInput, onValueChange = { newValue ->
|
||||||
|
if (newValue.all { it.isDigit() } && newValue.length <= 3) {
|
||||||
|
bpmInput = newValue
|
||||||
|
}
|
||||||
|
}, label = { Text("BPM") }, singleLine = true, keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Number, imeAction = ImeAction.Done
|
||||||
|
), keyboardActions = KeyboardActions(
|
||||||
|
onDone = {
|
||||||
|
val newBpm = bpmInput.toFloatOrNull() ?: 0f
|
||||||
|
val newFactor = newBpm / basseBpm
|
||||||
|
tempo = newFactor
|
||||||
|
mediaPlayer.setTempo(newFactor)
|
||||||
|
bpmInput = (basseBpm * newFactor).toInt().toString()
|
||||||
|
}), modifier = Modifier.width(65.dp))
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Slider(value = tempo, onValueChange = {
|
||||||
|
tempo = it
|
||||||
|
mediaPlayer?.setTempo(it)
|
||||||
|
currentBpm = mediaPlayer.getCurrentBPM()
|
||||||
|
bpmInput = (basseBpm * it).toInt().toString()
|
||||||
|
}, valueRange = 0.25f..1.5f, modifier = Modifier.width(200.dp), thumb = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(15.dp).background(Color.Magenta, CircleShape)
|
||||||
|
)
|
||||||
|
}, track = { sliderState ->
|
||||||
|
SliderDefaults.Track(
|
||||||
|
sliderState = sliderState, modifier = Modifier.height(5.dp)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.End
|
||||||
|
) {
|
||||||
|
IconButton(onClick = {
|
||||||
|
tempo = 1.0f
|
||||||
|
mediaPlayer?.setTempo(1.0f)
|
||||||
|
currentBpm = mediaPlayer.getCurrentBPM()
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.Refresh, contentDescription = "Reset")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
){
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
if (loopState.first == -1L) {
|
||||||
|
mediaPlayer.setPointA()
|
||||||
|
} else if(!loopState.third) {
|
||||||
|
mediaPlayer.setPointB()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors = ButtonDefaults.buttonColors(
|
||||||
|
containerColor = if (loopState.third) Color.Green else MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
when {
|
||||||
|
loopState.first == -1L -> Text("A?")
|
||||||
|
!loopState.third -> Text("B?")
|
||||||
|
else -> Icon(
|
||||||
|
imageVector = Icons.Default.Loop,
|
||||||
|
contentDescription = "boucle"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
if(loopState.first != -1L) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
mediaPlayer.clearLoop()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.Clear, contentDescription = "Actualiser", )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
showBPMTools = !showBPMTools
|
||||||
|
}) {
|
||||||
|
Icon(imageVector = Icons.Default.MusicNote, contentDescription = "Tempo")
|
||||||
|
}
|
||||||
|
AnimatedVisibility(visible = showBPMTools){
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(20.dp)
|
||||||
|
.height(3.dp)
|
||||||
|
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(2.dp))
|
||||||
|
)}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
showInstruTools = !showInstruTools
|
||||||
|
}) {
|
||||||
|
Icon(imageVector = Icons.Default.Tune, contentDescription = "Instru")
|
||||||
|
}
|
||||||
|
AnimatedVisibility(visible = showInstruTools){
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(20.dp)
|
||||||
|
.height(3.dp)
|
||||||
|
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(2.dp))
|
||||||
|
)}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
IconButton(
|
||||||
|
onClick = {
|
||||||
|
showSATBTools = !showSATBTools
|
||||||
|
}) {
|
||||||
|
Icon(imageVector = Icons.Default.SettingsVoice, contentDescription = "SATB")
|
||||||
|
}
|
||||||
|
AnimatedVisibility(visible = showSATBTools){
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(20.dp)
|
||||||
|
.height(3.dp)
|
||||||
|
.background(MaterialTheme.colorScheme.primary, RoundedCornerShape(2.dp))
|
||||||
|
)}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (volume > 0) Icons.Filled.VolumeUp else Icons.Filled.VolumeOff,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Slider(
|
||||||
|
value = volume,
|
||||||
|
onValueChange = onVolumeChange,
|
||||||
|
valueRange = 0f..1f,
|
||||||
|
modifier = Modifier.width(100.dp),
|
||||||
|
thumb = {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(15.dp).background(Color.Blue, CircleShape)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
track = { sliderState ->
|
||||||
|
SliderDefaults.Track(
|
||||||
|
sliderState = sliderState, modifier = Modifier.height(5.dp)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,173 @@
|
||||||
|
package mg.dot.feufaro.ui
|
||||||
|
|
||||||
|
import SharedScreenModel
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Book
|
||||||
|
import androidx.compose.material.icons.filled.Menu
|
||||||
|
import androidx.compose.material.icons.filled.MenuBook
|
||||||
|
import androidx.compose.material.icons.filled.MusicNote
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.DrawerState
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.ModalDrawerSheet
|
||||||
|
import androidx.compose.material3.NavigationDrawerItem
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mg.dot.feufaro.data.DrawerItem
|
||||||
|
import mg.dot.feufaro.getPlatform
|
||||||
|
import mg.dot.feufaro.solfa.Solfa
|
||||||
|
import mg.dot.feufaro.viewmodel.SolfaScreenModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun SimpleDrawerContent(
|
||||||
|
items: List<DrawerItem>,
|
||||||
|
solfaScreenModel: SolfaScreenModel,
|
||||||
|
sharedScreenModel: SharedScreenModel,
|
||||||
|
activePath: String = Solfa.currentFile,
|
||||||
|
drawerState: DrawerState,
|
||||||
|
scope: CoroutineScope,
|
||||||
|
onScannerButtonClick: () -> Unit,
|
||||||
|
onSongSelected: (String) -> Unit,
|
||||||
|
) {
|
||||||
|
var searchTxt by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val context = getPlatform()
|
||||||
|
|
||||||
|
val midi = "whawyd3.mid"
|
||||||
|
ModalDrawerSheet(
|
||||||
|
modifier = Modifier.width(300.dp)
|
||||||
|
) {
|
||||||
|
Box (
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
)
|
||||||
|
{
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
ScrollableDrawerContent(
|
||||||
|
lazyListState = lazyListState,
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
state = lazyListState,
|
||||||
|
){
|
||||||
|
stickyHeader {
|
||||||
|
Surface(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||||
|
modifier = Modifier.fillParentMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
"Liste des solfa disponibles",
|
||||||
|
style = MaterialTheme.typography.titleLarge
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
itemsIndexed(items){ index, item ->
|
||||||
|
val isSelected = item.path == activePath
|
||||||
|
val title = item.title
|
||||||
|
var isFfpm = false
|
||||||
|
var isEws = false
|
||||||
|
var isFF = false
|
||||||
|
|
||||||
|
if (title.startsWith("ffpm")) {
|
||||||
|
isFfpm = true
|
||||||
|
} else if(title.startsWith("ews")) {
|
||||||
|
isEws = true
|
||||||
|
} else {
|
||||||
|
isFF = true
|
||||||
|
}
|
||||||
|
NavigationDrawerItem(
|
||||||
|
label = {
|
||||||
|
Text(item.title)
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
val isIcon = when {
|
||||||
|
isFfpm -> Icons.Filled.MenuBook
|
||||||
|
isEws -> Icons.Filled.MusicNote
|
||||||
|
isFF -> Icons.Filled.Book
|
||||||
|
else -> Icons.Filled.Menu
|
||||||
|
}
|
||||||
|
Icon(
|
||||||
|
isIcon,
|
||||||
|
contentDescription = "",
|
||||||
|
tint = Color.Blue
|
||||||
|
)
|
||||||
|
},
|
||||||
|
badge = {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Menu,
|
||||||
|
contentDescription = ""
|
||||||
|
)
|
||||||
|
},
|
||||||
|
selected = if(isSelected){ true } else { false },
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
}
|
||||||
|
sharedScreenModel.reset()
|
||||||
|
solfaScreenModel.loadFromFile(item.path)
|
||||||
|
onSongSelected(midi)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.padding(16.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
Row (
|
||||||
|
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onScannerButtonClick()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Scanner")
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
}
|
||||||
|
solfaScreenModel.loadCustomFile()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Importer")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
package mg.dot.feufaro.midi
|
||||||
|
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import mg.dot.feufaro.FileRepository
|
||||||
|
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.*
|
||||||
|
|
||||||
|
//private var sequencer: javax.sound.midi.Sequencer?= null
|
||||||
|
actual class MediaPlayer actual constructor(
|
||||||
|
private val filename: String,
|
||||||
|
private val onFinished: () -> Unit
|
||||||
|
) {
|
||||||
|
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 pointA: Long = -1L
|
||||||
|
private var pointB: Long = -1L
|
||||||
|
private var isLoopingAB: Boolean = false
|
||||||
|
|
||||||
|
private val voiceStates = mutableListOf(true, true, true, true)
|
||||||
|
|
||||||
|
private var currentGlobalVolume: Float = 0.8f
|
||||||
|
|
||||||
|
private var currentTempo: Float = 1.0f
|
||||||
|
|
||||||
|
init {
|
||||||
|
sequencer?.open()
|
||||||
|
synthetizer?.open()
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual fun play(){
|
||||||
|
if (sequencer!!.isOpen){
|
||||||
|
sequencer?.start()
|
||||||
|
println("La sequence vient d etre lancé ${sequencer?.isRunning}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual fun pause(){
|
||||||
|
sequencer?.stop()
|
||||||
|
}
|
||||||
|
actual fun stop(){
|
||||||
|
sequencer?.stop()
|
||||||
|
sequencer?.microsecondPosition = 0
|
||||||
|
// disableLoop()
|
||||||
|
}
|
||||||
|
actual fun getDuration(): Long {
|
||||||
|
return (sequencer?.microsecondLength ?: 0L) / 1000
|
||||||
|
}
|
||||||
|
actual fun getCurrentPosition(): Long {
|
||||||
|
return (sequencer?.microsecondPosition ?: 0L) / 1000
|
||||||
|
}
|
||||||
|
actual fun seekTo(position: Long) {
|
||||||
|
sequencer?.microsecondPosition = position * 1000
|
||||||
|
}
|
||||||
|
fun release() {
|
||||||
|
sequencer?.close()
|
||||||
|
// synthetizer?.close()
|
||||||
|
}
|
||||||
|
actual fun setVolume(level: Float) {
|
||||||
|
try {
|
||||||
|
this.currentGlobalVolume = level
|
||||||
|
val volumeInt = (level * 127).toInt().coerceIn(0, 127)
|
||||||
|
|
||||||
|
synthetizer?.channels?.forEachIndexed { index, channel ->
|
||||||
|
if (index < 4) {
|
||||||
|
val vol = if (voiceStates[index]) volumeInt else 0
|
||||||
|
channel?.controlChange(7, vol)
|
||||||
|
} else {
|
||||||
|
channel?.controlChange(7, volumeInt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/*val logVolume = if (level > 0 ){
|
||||||
|
(Math.log10(level.toDouble() * 9 + 1) / Math.log10(10.0)).toFloat()
|
||||||
|
} else {
|
||||||
|
0f
|
||||||
|
}
|
||||||
|
val volumeInt = (logVolume * 127).toInt().coerceIn(0, 127)
|
||||||
|
|
||||||
|
synthetizer?.channels?.forEach { it?.controlChange(7, volumeInt) }
|
||||||
|
synthetizer?.channels?.forEach { it?.controlChange(11, volumeInt) }
|
||||||
|
*/
|
||||||
|
val mixer = AudioSystem.getMixer(null)
|
||||||
|
val lines = mixer.sourceLines
|
||||||
|
for (line in lines) {
|
||||||
|
if(line.isControlSupported(FloatControl.Type.MASTER_GAIN)) {
|
||||||
|
val gainControl = line.getControl(FloatControl.Type.MASTER_GAIN) as FloatControl
|
||||||
|
|
||||||
|
val dB = (Math.log10(level.toDouble().coerceAtLeast(0.0001)) * 20).toFloat()
|
||||||
|
gainControl.value = dB.coerceIn(gainControl.minimum, gainControl.maximum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// val msg = ShortMessage()
|
||||||
|
// val receiver = sequencer?.receiver
|
||||||
|
// for (i in 0 until 16) {
|
||||||
|
// val msg = ShortMessage()
|
||||||
|
// msg.setMessage(ShortMessage.CONTROL_CHANGE, i, 7, volumeInt)
|
||||||
|
// receiver?.send(msg, -1)
|
||||||
|
// }
|
||||||
|
|
||||||
|
} catch (e: Exception){
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
println("la volume $level")
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun setPointA() {
|
||||||
|
pointA = sequencer?.tickPosition ?: 0L
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun setPointB() {
|
||||||
|
pointB = sequencer?.tickPosition ?: 0L
|
||||||
|
if (pointB > pointA && pointA != -1L) {
|
||||||
|
isLoopingAB = true
|
||||||
|
startABMonitor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun clearLoop() {
|
||||||
|
isLoopingAB = false
|
||||||
|
pointA = -1L
|
||||||
|
pointB = -1L
|
||||||
|
}
|
||||||
|
private fun startABMonitor() {
|
||||||
|
GlobalScope.launch {
|
||||||
|
while(isLoopingAB) {
|
||||||
|
val currentTick = sequencer?.tickPosition?: 0L
|
||||||
|
if (currentTick >= pointB) {
|
||||||
|
sequencer?.tickPosition = pointA
|
||||||
|
}
|
||||||
|
delay(50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual fun getLoopState() = Triple(pointA, pointB, isLoopingAB)
|
||||||
|
|
||||||
|
actual fun toggleVoice(index: Int) {
|
||||||
|
voiceStates[index] = !voiceStates[index]
|
||||||
|
applyVoiceStates()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyVoiceStates() {
|
||||||
|
try {
|
||||||
|
synthetizer?.channels?.let { channels ->
|
||||||
|
for (i in 0 until 4) {
|
||||||
|
if (i < channels.size) {
|
||||||
|
val isVoiceActive = voiceStates[i]
|
||||||
|
val volume = if (voiceStates[i]) 127 else 0
|
||||||
|
channels[i].controlChange(7, volume)
|
||||||
|
channels[i].controlChange(11, volume)
|
||||||
|
if(!isVoiceActive) {
|
||||||
|
channels[i].controlChange(123, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("STAB màj: $voiceStates")
|
||||||
|
}
|
||||||
|
} catch (e: Exception) { e.printStackTrace() }
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun getVoiceStates(): List<Boolean> = voiceStates
|
||||||
|
|
||||||
|
actual fun changeInstru(noInstru: Int) {
|
||||||
|
val pgm = noInstru
|
||||||
|
synthetizer?.channels?.let {
|
||||||
|
channels ->
|
||||||
|
for (i in 0 until 4) {
|
||||||
|
channels[i].programChange(pgm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
actual fun getCurrentBPM(): Float {
|
||||||
|
// return sequencer?.tempoInBPM?.toInt() ?: 0
|
||||||
|
val currentFactor = sequencer?.tempoFactor ?: 1.0f
|
||||||
|
// val currentBPM = sequencer?.tempoInBPM ?: 120.0f
|
||||||
|
val currentBPM = (120f * currentFactor)
|
||||||
|
// println("202: ${sequencer?.tempoInBPM} ${sequencer?.tempoFactor} ${sequencer?.tempoInMPQ}")
|
||||||
|
return currentBPM
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun setTempo(factor: Float){
|
||||||
|
currentTempo = factor
|
||||||
|
sequencer?.tempoFactor = factor
|
||||||
|
}
|
||||||
|
fun getTempo(): Float = currentTempo
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
actual fun StopMidi() {
|
||||||
|
if(sequencer?.isRunning == true){
|
||||||
|
sequencer?.stop()
|
||||||
|
sequencer?.close()
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
@ -17,31 +17,31 @@ actual class MidiWriterKotlin actual constructor(private val fileRepository: Fil
|
||||||
private val lastPitch : MutableList<Int> = mutableListOf()
|
private val lastPitch : MutableList<Int> = mutableListOf()
|
||||||
private val useChord : Boolean = true
|
private val useChord : Boolean = true
|
||||||
actual fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long) {
|
actual fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long) {
|
||||||
var channel: Int = voiceNumber - 1
|
val channel = (voiceNumber -1).coerceIn(0, 3)
|
||||||
if (useChord) {
|
var finalNote = note
|
||||||
channel = channel / 2
|
|
||||||
}
|
|
||||||
var note = note
|
|
||||||
if (voiceNumber == 3 || voiceNumber == 4) {
|
if (voiceNumber == 3 || voiceNumber == 4) {
|
||||||
note -= 12
|
finalNote -= 12
|
||||||
}
|
}
|
||||||
if (lastPitch.size > voiceNumber && lastPitch[voiceNumber] > 0) {
|
if (lastPitch.size > voiceNumber && lastPitch[voiceNumber] > 0) {
|
||||||
noteOff.setMessage(ShortMessage.NOTE_OFF, channel, lastPitch[voiceNumber], 0)
|
val offMsg = ShortMessage()
|
||||||
val n2 = noteOff.clone() as MidiMessage
|
offMsg.setMessage(ShortMessage.NOTE_OFF, channel, lastPitch[voiceNumber], 0)
|
||||||
track.add(MidiEvent(n2, tick))
|
track.add(MidiEvent(offMsg, tick))
|
||||||
}
|
}
|
||||||
var velocity = velocity
|
var finalVelocity = velocity
|
||||||
if (note <= 0) {
|
var midiNote = finalNote
|
||||||
note = 40
|
if (finalNote <= 0) {
|
||||||
velocity = 0
|
midiNote = 40
|
||||||
|
finalVelocity = 0
|
||||||
}
|
}
|
||||||
noteOn.setMessage(ShortMessage.NOTE_ON, channel, note, velocity)
|
val onMsg = ShortMessage()
|
||||||
val n1: MidiMessage = noteOn.clone() as MidiMessage
|
onMsg.setMessage(ShortMessage.NOTE_ON, channel, midiNote, finalVelocity)
|
||||||
track.add(MidiEvent(n1, tick))
|
track.add(MidiEvent(onMsg, tick))
|
||||||
|
|
||||||
while(lastPitch.size <= voiceNumber) {
|
while(lastPitch.size <= voiceNumber) {
|
||||||
lastPitch.add(0)
|
lastPitch.add(0)
|
||||||
}
|
}
|
||||||
lastPitch[voiceNumber] = note
|
lastPitch[voiceNumber] = midiNote
|
||||||
}
|
}
|
||||||
actual fun save(filePath: String) {
|
actual fun save(filePath: String) {
|
||||||
val parseScope = CoroutineScope(Dispatchers.Default)
|
val parseScope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue