Compare commits

..

No commits in common. "2f0be71dd83097a859e7e669925f943d2b816a22" and "bb64cf1530e8dac047acf18859073eeac55e5854" have entirely different histories.

8 changed files with 383 additions and 5158 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,33 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="256"
android:viewportHeight="256"
android:width="256dp"
android:height="256dp">
<path
android:pathData="M202.2 119.6c-2.1 0 -3.8 1.7 -3.8 3.8V171c0 2.1 1.7 3.8 3.8 3.8c2.1 0 3.8 -1.7 3.8 -3.8v-47.7C206 121.3 204.3 119.6 202.2 119.6z"
android:fillColor="#000000" />
<path
android:pathData="M192.7 117h19c2.1 0 3.8 -1.7 3.8 -3.8V74.4c0 -2.1 -1.7 -3.8 -3.8 -3.8H206V54.9c0 -2.1 -1.7 -3.8 -3.8 -3.8c-2.1 0 -3.8 1.7 -3.8 3.8v15.7h-5.7c-2.1 0 -3.8 1.7 -3.8 3.8v38.8C188.9 115.3 190.6 117 192.7 117zM196.5 78.2h11.4v31.2h-11.4V78.2z"
android:fillColor="#000000" />
<path
android:pathData="M152.7 158.5c-2.1 0 -3.8 1.7 -3.8 3.8v9.3c0 2.1 1.7 3.8 3.8 3.8c2.1 0 3.8 -1.7 3.8 -3.8v-9.3C156.6 160.2 154.9 158.5 152.7 158.5z"
android:fillColor="#000000" />
<path
android:pathData="M143.2 155.9h19c2.1 0 3.8 -1.7 3.8 -3.8v-38.8c0 -2.1 -1.7 -3.8 -3.8 -3.8h-5.7v-54c0 -2.1 -1.7 -3.8 -3.8 -3.8c-2.1 0 -3.8 1.7 -3.8 3.8v54h-5.7c-2.1 0 -3.8 1.7 -3.8 3.8v38.8C139.4 154.2 141.1 155.9 143.2 155.9zM147 117.1h11.4v31.2H147V117.1z"
android:fillColor="#000000" />
<path
android:pathData="M103.3 119.6c-2.1 0 -3.8 1.7 -3.8 3.8V171c0 2.1 1.7 3.8 3.8 3.8c2.1 0 3.8 -1.7 3.8 -3.8v-47.7C107.1 121.3 105.4 119.6 103.3 119.6z"
android:fillColor="#000000" />
<path
android:pathData="M93.7 117h19c2.1 0 3.8 -1.7 3.8 -3.8V74.4c0 -2.1 -1.7 -3.8 -3.8 -3.8H107V54.9c0 -2.1 -1.7 -3.8 -3.8 -3.8c-2.1 0 -3.8 1.7 -3.8 3.8v15.7h-5.7c-2.1 0 -3.8 1.7 -3.8 3.8v38.8C89.9 115.3 91.6 117 93.7 117zM97.5 78.2H109v31.2H97.6L97.5 78.2L97.5 78.2z"
android:fillColor="#000000" />
<path
android:pathData="M53.8 158.5c-2.1 0 -3.8 1.7 -3.8 3.8v9.3c0 2.1 1.7 3.8 3.8 3.8s3.8 -1.7 3.8 -3.8v-9.3C57.6 160.2 55.9 158.5 53.8 158.5z"
android:fillColor="#000000" />
<path
android:pathData="M44.3 155.9h19c2.1 0 3.8 -1.7 3.8 -3.8v-38.8c0 -2.1 -1.7 -3.8 -3.8 -3.8h-5.7v-54c0 -2.1 -1.7 -3.8 -3.8 -3.8S50 53.4 50 55.5v54h-5.7c-2.1 0 -3.8 1.7 -3.8 3.8v38.8C40.4 154.2 42.1 155.9 44.3 155.9zM48.1 117.1h11.4v31.2H48.1V117.1z"
android:fillColor="#000000" />
<path
android:pathData="M242.2 31.1H13.8c-2.1 0 -3.8 1.7 -3.8 3.8v178.6c0 6.3 5.1 11.4 11.4 11.4h213.2c6.3 0 11.4 -5.1 11.4 -11.4V34.9C246 32.8 244.3 31.1 242.2 31.1zM238.4 38.7v149.1H17.6V38.7H238.4zM234.6 217.3H21.4c-2.1 0 -3.8 -1.7 -3.8 -3.8v-18h220.8v18C238.4 215.5 236.7 217.3 234.6 217.3z"
android:fillColor="#000000" />
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="512"
android:viewportHeight="512"
android:width="800dp"
android:height="800dp">
<path
android:pathData="M468.826 97.292v103.374h-55.943V48.647h-36.484v152.019h-55.943V0.001h-36.484v200.665h-55.943V0h-36.484v200.666h-55.943V48.647H99.117v152.019H43.174V97.293H6.689V512h498.621V97.292H468.826zM99.117 334.442H43.174v-36.484h55.943V334.442zM135.602 297.958h55.943v36.484h-55.943V297.958zM228.025 475.516H191.54V370.927h36.484V475.516zM228.03 297.958h55.943v36.484H228.03V297.958zM320.452 475.516h-36.484V370.927h36.484V475.516zM376.399 334.442h-55.943v-36.484h55.943V334.442zM468.826 334.442h-55.943v-36.484h55.943V334.442z"
android:fillColor="#000000" />
</vector>

View file

@ -1,33 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="256"
android:viewportHeight="256"
android:width="256dp"
android:height="256dp">
<path
android:pathData="M202.2 119.6c-2.1 0 -3.8 1.7 -3.8 3.8V171c0 2.1 1.7 3.8 3.8 3.8c2.1 0 3.8 -1.7 3.8 -3.8v-47.7C206 121.3 204.3 119.6 202.2 119.6z"
android:fillColor="#000000" />
<path
android:pathData="M192.7 117h19c2.1 0 3.8 -1.7 3.8 -3.8V74.4c0 -2.1 -1.7 -3.8 -3.8 -3.8H206V54.9c0 -2.1 -1.7 -3.8 -3.8 -3.8c-2.1 0 -3.8 1.7 -3.8 3.8v15.7h-5.7c-2.1 0 -3.8 1.7 -3.8 3.8v38.8C188.9 115.3 190.6 117 192.7 117zM196.5 78.2h11.4v31.2h-11.4V78.2z"
android:fillColor="#000000" />
<path
android:pathData="M152.7 158.5c-2.1 0 -3.8 1.7 -3.8 3.8v9.3c0 2.1 1.7 3.8 3.8 3.8c2.1 0 3.8 -1.7 3.8 -3.8v-9.3C156.6 160.2 154.9 158.5 152.7 158.5z"
android:fillColor="#000000" />
<path
android:pathData="M143.2 155.9h19c2.1 0 3.8 -1.7 3.8 -3.8v-38.8c0 -2.1 -1.7 -3.8 -3.8 -3.8h-5.7v-54c0 -2.1 -1.7 -3.8 -3.8 -3.8c-2.1 0 -3.8 1.7 -3.8 3.8v54h-5.7c-2.1 0 -3.8 1.7 -3.8 3.8v38.8C139.4 154.2 141.1 155.9 143.2 155.9zM147 117.1h11.4v31.2H147V117.1z"
android:fillColor="#000000" />
<path
android:pathData="M103.3 119.6c-2.1 0 -3.8 1.7 -3.8 3.8V171c0 2.1 1.7 3.8 3.8 3.8c2.1 0 3.8 -1.7 3.8 -3.8v-47.7C107.1 121.3 105.4 119.6 103.3 119.6z"
android:fillColor="#000000" />
<path
android:pathData="M93.7 117h19c2.1 0 3.8 -1.7 3.8 -3.8V74.4c0 -2.1 -1.7 -3.8 -3.8 -3.8H107V54.9c0 -2.1 -1.7 -3.8 -3.8 -3.8c-2.1 0 -3.8 1.7 -3.8 3.8v15.7h-5.7c-2.1 0 -3.8 1.7 -3.8 3.8v38.8C89.9 115.3 91.6 117 93.7 117zM97.5 78.2H109v31.2H97.6L97.5 78.2L97.5 78.2z"
android:fillColor="#000000" />
<path
android:pathData="M53.8 158.5c-2.1 0 -3.8 1.7 -3.8 3.8v9.3c0 2.1 1.7 3.8 3.8 3.8s3.8 -1.7 3.8 -3.8v-9.3C57.6 160.2 55.9 158.5 53.8 158.5z"
android:fillColor="#000000" />
<path
android:pathData="M44.3 155.9h19c2.1 0 3.8 -1.7 3.8 -3.8v-38.8c0 -2.1 -1.7 -3.8 -3.8 -3.8h-5.7v-54c0 -2.1 -1.7 -3.8 -3.8 -3.8S50 53.4 50 55.5v54h-5.7c-2.1 0 -3.8 1.7 -3.8 3.8v38.8C40.4 154.2 42.1 155.9 44.3 155.9zM48.1 117.1h11.4v31.2H48.1V117.1z"
android:fillColor="#000000" />
<path
android:pathData="M242.2 31.1H13.8c-2.1 0 -3.8 1.7 -3.8 3.8v178.6c0 6.3 5.1 11.4 11.4 11.4h213.2c6.3 0 11.4 -5.1 11.4 -11.4V34.9C246 32.8 244.3 31.1 242.2 31.1zM238.4 38.7v149.1H17.6V38.7H238.4zM234.6 217.3H21.4c-2.1 0 -3.8 -1.7 -3.8 -3.8v-18h220.8v18C238.4 215.5 236.7 217.3 234.6 217.3z"
android:fillColor="#000000" />
</vector>

View file

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
android:viewportWidth="512"
android:viewportHeight="512"
android:width="800dp"
android:height="800dp">
<path
android:pathData="M468.826 97.292v103.374h-55.943V48.647h-36.484v152.019h-55.943V0.001h-36.484v200.665h-55.943V0h-36.484v200.666h-55.943V48.647H99.117v152.019H43.174V97.293H6.689V512h498.621V97.292H468.826zM99.117 334.442H43.174v-36.484h55.943V334.442zM135.602 297.958h55.943v36.484h-55.943V297.958zM228.025 475.516H191.54V370.927h36.484V475.516zM228.03 297.958h55.943v36.484H228.03V297.958zM320.452 475.516h-36.484V370.927h36.484V475.516zM376.399 334.442h-55.943v-36.484h55.943V334.442zM468.826 334.442h-55.943v-36.484h55.943V334.442z"
android:fillColor="#000000" />
</vector>

View file

@ -1,31 +1,91 @@
package mg.dot.feufaro.ui
package mg.dot.feufaro.ui
import androidx.compose.animation.*
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.clickable
import androidx.compose.foundation.layout.*
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.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
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.automirrored.filled.KeyboardArrowLeft
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.material.icons.filled.AvTimer
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.automirrored.filled.VolumeUp
import androidx.compose.material.icons.automirrored.filled.VolumeOff
import androidx.compose.material.icons.filled.MoreHoriz
import androidx.compose.material.icons.filled.MoreVert
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.graphics.RectangleShape
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
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 feufaro.composeapp.generated.resources.Res
import feufaro.composeapp.generated.resources.ic_mixer_satb
import feufaro.composeapp.generated.resources.ic_organ
import kotlinx.coroutines.delay
import mg.dot.feufaro.getPlatform
import mg.dot.feufaro.midi.MediaPlayer
import org.jetbrains.compose.resources.painterResource
import org.koin.core.component.getScopeId
import javax.swing.Icon
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@ -43,371 +103,310 @@ fun MidiControlPanel(
val momo = duration.toInt() - currentPos.toInt()
val loopState by produceState(Triple(-1L, -1L, false), mediaPlayer) {
while (true) {
while(true) {
value = mediaPlayer.getLoopState()
delay(200)
}
}
val voiceStates = mediaPlayer.getVoiceStates()
val labels = listOf("S", "A", "T", "B")
listOf("Soprano", "Alto", "Ténor", "Basse")
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) }
var expandedCtl by remember { mutableStateOf(false) }
LaunchedEffect(tempo) {
currentBpm = mediaPlayer.getCurrentBPM()
}
fun updateTempoByBpmStep(step: Int) {
val currentBpm = (basseBpm * tempo).toInt()
val newBpm = (currentBpm + step).coerceIn((basseBpm * 0.25f).toInt(), (basseBpm * 1.5f).toInt())
val newFactor = newBpm.toFloat() / basseBpm
tempo = newFactor
mediaPlayer?.setTempo(newFactor)
}
fun updateTempoToBpm(targetBpm: Int) {
val clampedBpm = targetBpm.coerceIn((basseBpm * 0.25f).toInt(), (basseBpm * 1.5f).toInt())
val newFactor = clampedBpm.toFloat() / basseBpm
tempo = newFactor
mediaPlayer?.setTempo(newFactor)
println("tempo : $tempo")
}
Column(
modifier = Modifier.fillMaxWidth()
Column (
modifier = modifier
.fillMaxWidth()
.padding(16.dp)
.background(color = Color.Gray.copy(alpha = 0.5f), shape = RoundedCornerShape(size = 5.dp)),
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.Bottom,
horizontalArrangement = Arrangement.Center
AnimatedVisibility(
visible = !expandedCtl
) {
Column(
modifier = modifier
.fillMaxWidth(if (getPlatform().name.startsWith("Android")) 0.9f else 0.6f)
.padding(16.dp)
.background(color = Color.Gray.copy(alpha = 0.5f), shape = RoundedCornerShape(size = 5.dp)),
horizontalAlignment = Alignment.CenterHorizontally
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
AnimatedVisibility(
visible = !expandedCtl
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Text("${currentPos.toInt() / 1000}s", color = Color.White)
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", color = Color.White)
}
}
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 = !expandedCtl,
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
) {
Row {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier
.height(45.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFF2C3135))
.padding(vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (tempo >= 0.3) { // limite 40BPM
IconButton(
modifier = Modifier.background(Color(0XFF2C3130)),
onClick = { updateTempoByBpmStep(-10) }) {
Icon(
Icons.AutoMirrored.Filled.KeyboardArrowLeft,
"gauche",
tint = Color.White.copy(0.7f),
modifier = Modifier.size(22.dp)
)
}
}
val currentBpmInt = (basseBpm * tempo).toInt()
Row(
modifier = Modifier.padding(horizontal = 2.dp),
verticalAlignment = Alignment.CenterVertically
) {
val values = listOf(currentBpmInt - 1, currentBpmInt, currentBpmInt + 1)
values.forEach { bpmValue ->
AnimatedContent(
targetState = bpmValue,
transitionSpec = {
if (targetState > initialState) {
slideInHorizontally { it } + fadeIn() togetherWith slideOutHorizontally { -it } + fadeOut()
} else {
slideInHorizontally { -it } + fadeIn() togetherWith slideOutHorizontally { it } + fadeOut()
}
},
modifier = Modifier.padding(horizontal = 4.dp)
) { displayedBpm ->
val isCenter = displayedBpm == currentBpmInt
Text(
text = "$displayedBpm",
color = if (isCenter) Color.White else Color.Gray.copy(alpha = 0.4f),
fontSize = if (isCenter) 22.sp else 14.sp,
fontWeight = if (isCenter) FontWeight.Bold else FontWeight.Normal,
modifier = Modifier
.clip(CircleShape)
.clickable(enabled = !isCenter) { updateTempoToBpm(displayedBpm) }
.padding(horizontal = 4.dp)
)
}
}
}
if (tempo <= 1.3) { // limite 156BPM
IconButton(
modifier = Modifier.background(Color(0XFF2C3130)),
onClick = { updateTempoByBpmStep(10) }) {
Icon(
Icons.AutoMirrored.Filled.KeyboardArrowRight,
"droite",
tint = Color.White.copy(0.7f),
modifier = Modifier.size(22.dp)
)
}
}
Text(
text = "bpm",
color = Color.White.copy(0.6f),
fontSize = 12.sp,
modifier = Modifier.padding(end = 8.dp)
)
}
}
}
}
if (!expandedCtl) {
Spacer(modifier = Modifier.height(10.dp))
}
Row(
modifier = Modifier.fillMaxWidth().padding(horizontal = 5.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Column {
val isAtStart = loopState.first == -1L
val isWaitingForB = loopState.first != -1L && !loopState.third
val isLooping = loopState.third
OutlinedButton(
onClick = {
when {
isAtStart -> mediaPlayer.setPointA()
isWaitingForB -> mediaPlayer.setPointB()
isLooping -> {
mediaPlayer.clearLoop()
}
}
},
colors = ButtonDefaults.buttonColors(
containerColor = when {
(isWaitingForB || isLooping) -> Color.Red
else -> Color.LightGray
}
),
contentPadding = PaddingValues(horizontal = 2.dp, vertical = 0.dp),
shape = RoundedCornerShape(5.dp),
Text("${currentPos.toInt() / 1000}s", color = Color.White)
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
.height(34.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "A",
color = if (isWaitingForB || isLooping) Color.White else Color.Black,
fontSize = 16.sp
)
Icon(
imageVector = Icons.Default.Loop,
contentDescription = null,
tint = if (isLooping) Color.White else Color.Black,
modifier = Modifier.size(16.dp)
)
Text(
text = "B",
color = if (isLooping) Color.White else Color.Black,
fontSize = 16.sp
)
}
}
}
Spacer(modifier = Modifier.weight(1f))
Column {
IconButton(
onClick = {
showSATBTools = !showSATBTools
}, modifier = Modifier
.size(40.dp)
.clip(CircleShape)
.background(if (showSATBTools) MaterialTheme.colorScheme.primary else Color.Transparent)
) {
Icon(
painter = painterResource(Res.drawable.ic_mixer_satb),
contentDescription = "SATB",
tint = if (showSATBTools) Color.White else Color.Black,
modifier = Modifier.size(35.dp)
)
}
}
Spacer(modifier = Modifier.weight(1f))
Column {
IconButton(
onClick = {
isPianoSelected = !isPianoSelected
if (isPianoSelected) {
mediaPlayer?.changeInstru(1)
} else {
mediaPlayer?.changeInstru(20)
}
}
) {
if (isPianoSelected) {
Icon(
Icons.Default.Piano,
contentDescription = "Piano",
tint = Color.Black
)
} else {
Icon(
painter = painterResource(Res.drawable.ic_organ),
contentDescription = "Orgue",
tint = Color.Black,
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))
val platform = getPlatform()
if (platform.name.startsWith("Java")) {
ModernVolumeSlider(
volume, onVolumeChange = onVolumeChange
.size(15.dp)
.background(Color.Gray, CircleShape)
)
},
track = { sliderState ->
SliderDefaults.Track(
sliderState = sliderState,
modifier = Modifier.height(5.dp)
)
}
)
Text("${momo / 1000}s", color = Color.White)
}
}
Spacer(modifier = Modifier.weight(1f))
AnimatedVisibility(
visible = showSATBTools,
enter = fadeIn() + scaleIn() + slideInVertically { it / 2 },
exit = fadeOut() + scaleOut() + slideOutVertically { it / 2 }
) {
Row {
Column (
horizontalAlignment = Alignment.CenterHorizontally
) {
Row(
modifier = Modifier.wrapContentWidth(),
horizontalArrangement = Arrangement.End,
modifier = Modifier.padding(vertical = 8.dp),
horizontalArrangement = Arrangement. spacedBy(4.dp)
) {
IconButton(
onClick = { expandedCtl = !expandedCtl },
modifier = Modifier.size(48.dp)
) {
Icon(
imageVector = if (expandedCtl) Icons.Default.MoreHoriz else Icons.Default.MoreVert,
contentDescription = "More",
tint = Color.White
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(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceAround
){
Box(contentAlignment = Alignment.CenterStart) {
IconButton(
onClick = {
showBPMTools = !showBPMTools
}
) {
Icon(imageVector = Icons.Default.AvTimer, contentDescription = "Tempo")
}
}
Box( contentAlignment = Alignment.CenterStart) {
val isAtStart = loopState.first == -1L
val isWaitingForB = loopState.first != -1L && !loopState.third
val isLooping = loopState.third
Button(
onClick = {
when {
isAtStart -> mediaPlayer.setPointA()
isWaitingForB -> mediaPlayer.setPointB()
isLooping -> {
mediaPlayer.clearLoop()
}
}
},
colors = ButtonDefaults.buttonColors(
containerColor = when {
(isWaitingForB || isLooping) -> Color.Red
else -> Color.LightGray
}
),
contentPadding = PaddingValues(horizontal = 8.dp, vertical = 0.dp),
shape = RoundedCornerShape(5.dp) ,
modifier = Modifier
.height(34.dp)
.padding(horizontal = 4.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(
text = "A",
color = if (isWaitingForB || isLooping) Color.White else Color.Black,
fontWeight = FontWeight.Bold,
fontSize = 18.sp
)
Icon(
imageVector = Icons.Default.Loop,
contentDescription = null,
tint = if (isLooping) Color.White else Color.Black,
modifier = Modifier.size(18.dp)
)
Text(
text = "B",
color = if (isLooping) Color.White else Color.Black,
fontWeight = FontWeight.Bold,
fontSize = 18.sp
)
}
}
}
Box(contentAlignment = Alignment.Center) {
IconButton(
onClick = {
showSATBTools = !showSATBTools
}) {
Icon(imageVector = Icons.Default.SettingsVoice, contentDescription = "SATB")
}
}
Box(contentAlignment = Alignment.Center) {
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
)
}
}
Box(contentAlignment = Alignment.CenterEnd) {
IconButton(
onClick = {
showInstruTools = !showInstruTools
}
) {
Icon(imageVector = Icons.Default.Tune, contentDescription = "Instru")
}
}
val platform = getPlatform()
if (platform.name.startsWith("Java")) {
ModernVolumeSlider(
volume, onVolumeChange = onVolumeChange
)
}
Row(
horizontalArrangement = Arrangement.End,
verticalAlignment = Alignment.CenterVertically
) {
Spacer(modifier = Modifier.width(8.dp))
IconButton(onClick = { expandedCtl = !expandedCtl }) {
Icon(
imageVector = if (expandedCtl) Icons.Default.MoreHoriz else Icons.Default.MoreVert,
contentDescription = "More",
tint = Color.White
)
}
}
}
}
}

View file

@ -29,47 +29,45 @@ fun ModernVolumeSlider(
volume: Float,
onVolumeChange: (Float) -> Unit
) {
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.height(42.dp)
.background(Color.White.copy(alpha = 0.1f), CircleShape)
.padding(horizontal = 12.dp, vertical = 4.dp)
) {
Icon(
imageVector = when {
volume == 0f -> Icons.AutoMirrored.Filled.VolumeOff
volume < 0.5f -> Icons.AutoMirrored.Filled.VolumeDown
else -> Icons.AutoMirrored.Filled.VolumeUp
},
contentDescription = null,
tint = if (volume > 0) Color.Black else Color.Gray,
modifier = Modifier.size(20.dp)
)
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.height(42.dp)
.background(Color.White.copy(alpha = 0.1f), CircleShape)
.padding(horizontal = 12.dp, vertical = 4.dp)
) {
Icon(
imageVector = when {
volume == 0f -> Icons.AutoMirrored.Filled.VolumeOff
volume < 0.5f -> Icons.AutoMirrored.Filled.VolumeDown
else -> Icons.AutoMirrored.Filled.VolumeUp
},
contentDescription = null,
tint = if (volume > 0) Color.Black else Color.Gray,
modifier = Modifier.size(20.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Spacer(modifier = Modifier.width(4.dp))
Slider(
value = volume,
onValueChange = onVolumeChange,
modifier = Modifier.fillMaxWidth(0.30f),
colors = SliderDefaults.colors(
activeTrackColor = Color(0xFFF59E0B),
inactiveTrackColor = Color.White.copy(alpha = 0.2f),
thumbColor = Color.Transparent
),
track = { sliderState ->
SliderDefaults.Track(
sliderState = sliderState,
modifier = Modifier.height(15.dp),
thumbTrackGapSize = 0.dp
)
},
thumb = {
Box(Modifier.size(0.dp))
}
)
}
Slider(
value = volume,
onValueChange = onVolumeChange,
modifier = Modifier.fillMaxWidth(0.30f),
colors = SliderDefaults.colors(
activeTrackColor = Color(0xFFF59E0B),
inactiveTrackColor = Color.White.copy(alpha = 0.2f),
thumbColor = Color.Transparent
),
track = { sliderState ->
SliderDefaults.Track(
sliderState = sliderState,
modifier = Modifier.height(15.dp),
thumbTrackGapSize = 0.dp
)
},
thumb = {
Box(Modifier.size(0.dp))
}
)
}
}