Compare commits

..

No commits in common. "78876d7ec0e72cd244813a9a98b9a4db7fc4c1c1" and "8b171fceaff75711b678018ebd62ecbc45e8b410" have entirely different histories.

13 changed files with 72 additions and 232 deletions

View file

@ -181,8 +181,6 @@ actual class FMediaPlayer actual constructor(
private val navigationSteps = mutableListOf<NavigationStep>() private val navigationSteps = mutableListOf<NavigationStep>()
private val prefs: Settings = provideSettings() private val prefs: Settings = provideSettings()
@Volatile private var userCustomTempo: Boolean = false
init { init {
midiDriver.start() midiDriver.start()
loadVoiceVolumes() loadVoiceVolumes()
@ -198,9 +196,7 @@ actual class FMediaPlayer actual constructor(
val usPerQuarter = ((md[0].toInt() and 0xFF) shl 16) or val usPerQuarter = ((md[0].toInt() and 0xFF) shl 16) or
((md[1].toInt() and 0xFF) shl 8) or ((md[1].toInt() and 0xFF) shl 8) or
(md[2].toInt() and 0xFF) (md[2].toInt() and 0xFF)
val parsedBpm = 60_000_000f / usPerQuarter targetBpm = 60_000_000f / usPerQuarter
targetBpm = parsedBpm
initialBpm = parsedBpm
} }
usPerTick = (60_000_000.0 / targetBpm) / resolution usPerTick = (60_000_000.0 / targetBpm) / resolution
@ -296,10 +292,8 @@ actual class FMediaPlayer actual constructor(
private fun resetTempoToInitial() { private fun resetTempoToInitial() {
tempoChangeJob?.cancel() tempoChangeJob?.cancel()
isInTempoChange = false isInTempoChange = false
//targetBpm = initialBpm targetBpm = initialBpm
userCustomTempo = false boundModel?.setBpmFlow(initialBpm)
usPerTick = (60_000_000.0 / targetBpm.toDouble()) / resolution.toDouble()
boundModel?.setBpmFlow(targetBpm)
} }
private fun extractDynamic(marker: String) = Dynamic.entries.firstOrNull { private fun extractDynamic(marker: String) = Dynamic.entries.firstOrNull {
@ -388,7 +382,6 @@ actual class FMediaPlayer actual constructor(
} else { } else {
yield() yield()
} }
if (!isRunning || !isActive) break
currentTickPos = ev.tickAbsolute currentTickPos = ev.tickAbsolute
lastEventNano = System.nanoTime() lastEventNano = System.nanoTime()
@ -445,13 +438,9 @@ actual class FMediaPlayer actual constructor(
currentTickPos = gridIndex.toLong() * resolution currentTickPos = gridIndex.toLong() * resolution
lastEventNano = System.nanoTime() lastEventNano = System.nanoTime()
val initialOrPreviousBpm = if (userCustomTempo) { val initialOrPreviousBpm = navigationSteps
targetBpm .filter { it.newBpm != null && it.gridIndex <= gridIndex }
} else { .maxByOrNull { it.gridIndex }?.newBpm ?: initialBpm
navigationSteps
.filter { it.newBpm != null && it.gridIndex <= gridIndex }
.maxByOrNull { it.gridIndex }?.newBpm ?: initialBpm
}
targetBpm = initialOrPreviousBpm targetBpm = initialOrPreviousBpm
sharedScreenModel.setBpmFlow(initialOrPreviousBpm) sharedScreenModel.setBpmFlow(initialOrPreviousBpm)
@ -462,9 +451,6 @@ actual class FMediaPlayer actual constructor(
navigationSteps.forEach { step -> navigationSteps.forEach { step ->
if (step.gridIndex > gridIndex) { if (step.gridIndex > gridIndex) {
step.alreadyDone = false step.alreadyDone = false
if (step.isFarany) {
step.faranyActive = false
}
} }
} }
applyVoiceStates() applyVoiceStates()
@ -484,10 +470,7 @@ actual class FMediaPlayer actual constructor(
} }
private fun resetNavigationFlags() { private fun resetNavigationFlags() {
navigationSteps.forEach { navigationSteps.forEach { it.alreadyDone = false }
it.alreadyDone = false
it.faranyActive = false
}
} }
private fun prepareNavigation(sharedScreenModel: SharedScreenModel) { private fun prepareNavigation(sharedScreenModel: SharedScreenModel) {
@ -612,12 +595,10 @@ actual class FMediaPlayer actual constructor(
// ── Farany ──────────────────────────────── // ── Farany ────────────────────────────────
finReg.containsMatchIn(mTrim) -> { finReg.containsMatchIn(mTrim) -> {
val hasDcOrDsAfter = metadataList.any { m -> val hasDcAfter = metadataList.any { m ->
(m.gridIndex ?: 0) > ci && m.marker.any { mk -> (m.gridIndex ?: 0) > ci && m.marker.any { mk -> dcR.containsMatchIn(mk) }
dcR.containsMatchIn(mk) || dsR.containsMatchIn(mk)
}
} }
if (hasDcOrDsAfter) { if (hasDcAfter) {
navigationSteps.add( navigationSteps.add(
NavigationStep( NavigationStep(
marker, marker,
@ -710,14 +691,9 @@ actual class FMediaPlayer actual constructor(
//♩ //♩
step.newBpm != null -> { step.newBpm != null -> {
step.alreadyDone = true step.alreadyDone = true
if (step.gridIndex == 0 && userCustomTempo) { println("Exécution changement de tempo : ${step.newBpm} BPM à la grille $currentIndex")
targetBpm = step.newBpm
} sharedScreenModel.setBpmFlow(step.newBpm)
else {
println("Exécution changement de tempo : ${step.newBpm} BPM à la grille $currentIndex")
targetBpm = step.newBpm
sharedScreenModel.setBpmFlow(step.newBpm)
}
} }
// Modulation // Modulation
@ -779,13 +755,9 @@ actual class FMediaPlayer actual constructor(
step.isFarany -> { step.isFarany -> {
println("C Farany ok $step") println("C Farany ok $step")
if (pendingFin || step.faranyActive) { if (pendingFin || step.faranyActive) {
step.alreadyDone = true
isRunning = false
allNotesOff()
playJob?.cancel()
seekToGrid(0)
boundModel?.updateActiveIndexByIndex(0)
println("Farany activé → STOP à grille $currentIndex") println("Farany activé → STOP à grille $currentIndex")
isRunning = false
allNotesOff()
onFinished() onFinished()
return@collect return@collect
} else { } else {
@ -838,7 +810,7 @@ actual class FMediaPlayer actual constructor(
val interpolated = (currentTickPos + extraTicks) / resolution val interpolated = (currentTickPos + extraTicks) / resolution
val rawGrid = interpolated.toInt().coerceAtLeast(0) val rawGrid = interpolated.toInt().coerceAtLeast(0)
//sharedScreenModel.updateActiveIndexByIndex(rawGrid) //sharedScreenModel.updateActiveIndexByIndex(rawGrid)
if (interpolated.toInt() != lastSentGrid) { if (interpolated.toInt() >= lastSentGrid) {
lastSentGrid = interpolated.toInt() lastSentGrid = interpolated.toInt()
sharedScreenModel.updateActiveIndexByIndex(interpolated.toInt()) sharedScreenModel.updateActiveIndexByIndex(interpolated.toInt())
} }
@ -950,9 +922,7 @@ actual class FMediaPlayer actual constructor(
allNotesOff(); currentTickPos = 0L allNotesOff(); currentTickPos = 0L
resetNavigationFlags(); navigationSteps.clear(); clearLoop() resetNavigationFlags(); navigationSteps.clear(); clearLoop()
currentDynamicFactor = Dynamic.MF.factor currentDynamicFactor = Dynamic.MF.factor
//resetTempoToNormal() resetTempoToNormal()
currentPlaybackBpm = targetBpm
usPerTick = (60_000_000.0 / targetBpm.toDouble()) / resolution.toDouble()
resetTempoToInitial() resetTempoToInitial()
} }
actual fun release() { actual fun release() {
@ -973,9 +943,7 @@ actual class FMediaPlayer actual constructor(
actual fun setTempo(bpm: Float) { actual fun setTempo(bpm: Float) {
targetBpm = bpm targetBpm = bpm
currentPlaybackBpm = bpm currentPlaybackBpm = bpm
userCustomTempo = true
usPerTick = (60_000_000.0 / bpm.toDouble()) / resolution.toDouble() usPerTick = (60_000_000.0 / bpm.toDouble()) / resolution.toDouble()
boundModel?.setBpmFlow(bpm)
boundModel?.let { prepareNavigation(it) } boundModel?.let { prepareNavigation(it) }
needsClockSync = true needsClockSync = true
} }

View file

@ -1,8 +1,8 @@
M0:|c:G|m:6/4|r:P.M|t:FF 7 Tsy hainay ny hangina|a:Ramarolahy Aina Nirina Jaona|h: M0:|c:G|m:6/4|r:P.M|t:FF 7 Tsy hainay ny hangina|a:Ramarolahy Aina Nirina Jaona|h:
U0:zK:4C444C84444444C84C444C84444444C8${$}${Fiverenana}4444444444444444444444444444444444444444444${Farany}K4444444(444)84444444C84444444(444)8444444484${D.S.Fin}8 U0:zK:4 C444 C84 444444 C84 C444 C84 444444 C8${$}4 ${x7} 444444${x0} K4 444444 (444)84 444444 C84 444444 (444)84 444444 84${D.S}8
N1:s,mmfsls smsmfmrrd mrFmrsr #s,tmrdrtlss sm-3f#t,ls-4 ssrmfsls-4 s,m-3fls-ssfrdsmfmrd d4msmf--d d4mrdsf r4Fmrs--r trrrd#s,tls-- N1:s,mmfsls smsmfmrrd mrFmrsr #s,tmrdrtlss sm-3f#t,ls-4 ssrmfsls-4 s,m-3fls-ssfrdsmfmrd d4msmf--d d4mrdsf r4Fmrs--r trrrd#s,tls--
N2:#f,sddrmRm sdddttsss sFdddtt sF6r,f sdstdrRmdrmddt--t-- dmdtdrdsdt-- dmrrdlsddttss T7llTl T7ll d7ttdt sl3F3r,m,f N2:#f,sddrmRm sdddttsss sFdddtt sF6r,f sdstdrRmdrmddt--t-- dmdtdrdsdt-- dmrrdlsddttss T7llTl T7ll d7ttdt sl3F3r,m,f
N3:#l,s5Fs ssmdrsffm dr7d4rdtt ssmfs--smfsmmr--f-- msmrmfsmmf-- msT'T'l'fmmsrsfm m4smsddrd m4sfmdd F4l'Fl'rrmr rF3rrdtdr N3:#l,s5Fs ssmdrsffm dr7d4rdtt ssmfs--smfsmmr--f-- msmrmfsmmf-- msT'T'l'fmmsrsfm m4smsddrd m4sfmdd F4l'Fl'rrmr rF3rrdtdr
N4:#r,sd6 s7dd ddllFss sr6ss sdmsd-- dmsddls--s-- ddmslt d3s--dmmfrs7d, d7f4 d7ff r'7s4 r'4r3slt N4:#r,sd6 s7dd ddllFss sr6ss sdmsd-- dmsddls--s-- ddmslt d3s--dmmfrs7d, d7f4 d7ff r'7s4 r'4r3slt
Y1:Tsy hainay ny hangina/Ka tsy hankalaza ny Avo/Nameno fitahiana/Ka tsy mba namela ho lavo.${R=}Ka de_4ra ry Ray!_4/Sy saotra no\ atolotra\ Anao_4/ny fo_4tena fa_2ly loatra no entinay midera\ Anao./Ny sainay tsy ampy\ hahalala/Ny fatranny fitiavana_o/Ka raiso ry Tompo Malala/Ny fankasitrahana\ Anao (ry Ray). Y1:Tsy hainay ny hangina/Ka tsy hankalaza ny Avo/Nameno fitahiana/Ka tsy mba namela ho lavo.${R=}Ka de_4ra ry Ray!_5/Sy saotra no\ atolotra\ Anao_5/ny fo_4tena fa_2ly loatra no entinay midera\ Anao./Ny sainay tsy ampy\ hahalala/Ny fatranny fitiavana_o/Ka raiso ry Tompo Malala/Ny fankasitrahana\ Anao (ry Ray).
Y2:Tsy hainay hadinoina/Lay fara-fitiavanao, Ray o!/Fa maty novonoina/Ny Zanakao hamonjy anay./ Y2:Tsy hainay hadinoina/Lay fara-fitiavanao, Ray o!/Fa maty novonoina/Ny Zanakao hamonjy anay./

View file

@ -56,6 +56,7 @@ object ScreenSolfa : Screen {
var showContextualMenu = false var showContextualMenu = false
var viewportHeight by remember { mutableStateOf(0) } var viewportHeight by remember { mutableStateOf(0) }
var isScanning by remember { mutableStateOf(false) }
var qrCodeResult by remember { mutableStateOf("Aucun code scanné") } var qrCodeResult by remember { mutableStateOf("Aucun code scanné") }
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
DeepLinkHandler.onSongReceived = { content, filePath -> DeepLinkHandler.onSongReceived = { content, filePath ->
@ -87,6 +88,9 @@ object ScreenSolfa : Screen {
solfaScreenModel, solfaScreenModel,
sharedScreenModel, sharedScreenModel,
qrCodeResult = qrCodeResult, qrCodeResult = qrCodeResult,
onScannerButtonClick = {
isScanning = true
},
solfaScrollState = scrollState solfaScrollState = scrollState
) { paddingValues -> ) { paddingValues ->
Box( Box(

View file

@ -32,7 +32,7 @@ data class MidiPitch (
metaType = type metaType = type
} }
fun setNote(note: String, theDuration: Int) { fun setNote(note: String, theDuration: Int) {
val midiPitch = Transpose.transposeToMidi(note, "C", key) val midiPitch = Transpose.transposeToMidi(note, key, "C")
velocity = if (midiPitch < 0) { velocity = if (midiPitch < 0) {
0 0
} else { } else {

View file

@ -102,19 +102,17 @@ object HarmonicAnalyzer {
return degreeLine return degreeLine
} }
fun analyzeChordName(notes: Map<Int, String>, songKey: String = "C", transposeInt: Int = 0): String { fun analyzeChordName(notes: Map<Int, String>, songKey: String = "C"): String {
val keyOffset = keyOffsets[songKey] ?: 0 val keyOffset = keyOffsets[songKey] ?: 0
val totalOffset = ((keyOffset + transposeInt) % 12 + 12) % 12
val slices = splitSlices(notes) val slices = splitSlices(notes)
val names = slices.map { slice -> val names = slices.map { slice ->
val cleaned = cleanNotes(slice) val cleaned = cleanNotes(slice)
val match = findChordMatch(cleaned) ?: return@map "" val match = findChordMatch(cleaned) ?: return@map ""
val absoluteRoot = (keyOffset + match.root) % 12
val absoluteRoot = ((totalOffset + match.root) % 12 + 12) % 12
val chordName = noteNames[absoluteRoot] + match.quality val chordName = noteNames[absoluteRoot] + match.quality
if (match.bass != -1 && match.bass != match.root) { if (match.bass != -1 && match.bass != match.root) {
val absoluteBass = ((totalOffset + match.bass) % 12 + 12) % 12 val absoluteBass = (keyOffset + match.bass) % 12
"$chordName/${noteNames[absoluteBass]}" "$chordName/${noteNames[absoluteBass]}"
} else { } else {
chordName chordName

View file

@ -264,7 +264,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
private val prefs: Settings = provideSettings() private val prefs: Settings = provideSettings()
fun generateMidiFile(bpm: Float, outputPath: String = "whawyd3.mid", transpI: Int = 0) { fun generateMidiFile(bpm: Float, outputPath: String = "whawyd3.mid") {
val parseScope = CoroutineScope(Dispatchers.Default) val parseScope = CoroutineScope(Dispatchers.Default)
parseScope.launch { parseScope.launch {
val pitchesSorted = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber })) val pitchesSorted = pitches.sortedWith(compareBy({ it.tick }, { it.voiceNumber }))
@ -276,14 +276,12 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
3 to prefs.getInt("voice_instrument", 0), 3 to prefs.getInt("voice_instrument", 0),
4 to prefs.getInt("voice_instrument", 0), 4 to prefs.getInt("voice_instrument", 0),
) )
val originalKey = sharedScreenModel.songKey.value
val transposedKey = Transpose.transposeKey(baseKey = originalKey, interval = transpI)
midiWriter.process( midiWriter.process(
pitchesSorted, pitchesSorted,
sharedScreenModel.getFullMarkers(), sharedScreenModel.getFullMarkers(),
initialBpm = bpm, initialBpm = bpm,
instruments, instruments,
initialKey = transposedKey initialKey = sharedScreenModel.songKey.value
) )
midiWriter.save(outputPath) midiWriter.save(outputPath)
} }
@ -2088,12 +2086,7 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
midiPitch.currentVoiceNumber(voiceNumber) midiPitch.currentVoiceNumber(voiceNumber)
if (voiceNumber == 1) { if (voiceNumber == 1) {
val rawKey = sharedScreenModel.songKey.value.ifEmpty { meta["C"] ?: "C" } midiPitch.initKey(meta["C"] ?: "C", meta["C"]?.endsWith("m") ?: false)
val interval = sharedScreenModel.transpositionInterval.value
val isMinor = rawKey.endsWith("m", ignoreCase = true)
val transposedKey = Transpose.transposeKey(rawKey, interval)
midiPitch.initKey(transposedKey, isMinor)
pushMidi("meta") pushMidi("meta")
if (midiPitch.initMeasure(meta["m"] ?: "4/4")) { if (midiPitch.initMeasure(meta["m"] ?: "4/4")) {
pushMidi("measure") pushMidi("measure")

View file

@ -959,10 +959,8 @@ fun LazyVerticalGridTUO(
val degreeName = remember(chordMap) { val degreeName = remember(chordMap) {
if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeDegree(chordMap) else null if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeDegree(chordMap) else null
} }
val appliedInterval by sharedScreenModel.transpositionInterval.collectAsState() val chordName = remember(chordMap) {
val songKey = sharedScreenModel.songKey.value if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeChordName(chordMap, sharedScreenModel.songKey.value) else null
val chordName = remember(chordMap, songKey, appliedInterval) {
if (chordMap.isNotEmpty()) HarmonicAnalyzer.analyzeChordName(chordMap, songKey, appliedInterval) else null
} }
Column( Column(
@ -1106,7 +1104,7 @@ fun LazyVerticalGridTUO(
) )
} }
) { ) {
val currentInterval by sharedScreenModel.transpToCInt.collectAsState() val currentInterval by sharedScreenModel.transpositionInterval.collectAsState()
TimeUnitComposable( TimeUnitComposable(
tuo = oneTUO, tuo = oneTUO,
stanzaNumber = currentStanza, stanzaNumber = currentStanza,

View file

@ -200,81 +200,22 @@ class Transpose {
return note return note
} }
val noteNaked = regexFound.groupValues[1] val noteNaked = regexFound.groupValues[1]
val originalOctaveStr = regexFound.groupValues[2]
val rest = regexFound.groupValues[3]
val noteNum = noteToNumber.indexOf(noteNaked) val noteNum = noteToNumber.indexOf(noteNaked)
val oldKey = keyToNumber.indexOf(fromKey) val oldKey = keyToNumber.indexOf(fromKey)
val newKey = keyToNumber.indexOf(toKey) val newKey = keyToNumber.indexOf(toKey)
if (oldKey == -1 || newKey == -1) return note var newNoteNum = noteNum - newKey + oldKey + alter
var suffix = ""
var keyShift = newKey - oldKey
if (keyShift > 6) {
keyShift -= 12
} else if (keyShift < -6) {
keyShift += 12
}
var newNoteNum = noteNum + keyShift + alter
var octaveShift = 0
val noteSize = noteToNumber.size val noteSize = noteToNumber.size
while (newNoteNum < 0) { while (newNoteNum < 0) {
newNoteNum += noteSize newNoteNum += noteSize
octaveShift -= 1 suffix += ","
} }
while (newNoteNum >= noteSize) { while (newNoteNum >= noteSize) {
newNoteNum -= noteSize newNoteNum -= noteSize
octaveShift += 1 suffix += "'"
} }
val newNote = noteToNumber[newNoteNum] + regexFound.groupValues[2] + suffix
val originalOctaveVal = octaveToInt(originalOctaveStr) return simplifyNote(newNote) + regexFound.groupValues[3]
val finalOctaveVal = originalOctaveVal + octaveShift
val finalOctaveStr = intToOctave(finalOctaveVal)
val transposedNote = noteToNumber[newNoteNum]
return transposedNote + finalOctaveStr + rest
}
fun octaveToInt(octaveStr: String): Int {
var valOctave = 0
for (char in octaveStr) {
when (char) {
'\'', '¹' -> valOctave += 1
',', '₁' -> valOctave -= 1
'²' -> valOctave += 2
'₂' -> valOctave -= 2
'³' -> valOctave += 3
'₃' -> valOctave -= 3
'⁴' -> valOctave += 4
'₄' -> valOctave -= 4
}
}
return valOctave
}
fun intToOctave(octaveVal: Int): String {
val index = octaveVal + 4
return if (index in octaveSigns.indices) {
octaveSigns[index]
} else if (octaveVal > 0) {
"'".repeat(octaveVal)
} else if (octaveVal < 0) {
",".repeat(-octaveVal)
} else {
""
}
}
fun transposeKey(baseKey: String, interval: Int): String {
if (baseKey.isBlank()) return "C"
val isMinor = baseKey.endsWith("m", ignoreCase = true)
val rootKey = if (isMinor) baseKey.dropLast(1) else baseKey
val baseIndex = keyToNumber.indexOfFirst { it.equals(rootKey, ignoreCase = true) }
if (baseIndex == -1) return baseKey
val newIndex = (baseIndex + interval).mod(12)
val newRootKey = keyToNumber[newIndex]
return if (isMinor) "${newRootKey}m" else newRootKey
} }
@OptIn(ExperimentalMaterial3Api::class) // Ajoutez cette annotation pour utiliser ExposedDropdownMenuBox @OptIn(ExperimentalMaterial3Api::class) // Ajoutez cette annotation pour utiliser ExposedDropdownMenuBox
@Composable @Composable

View file

@ -40,8 +40,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
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 cafe.adriel.voyager.core.annotation.InternalVoyagerApi
import cafe.adriel.voyager.navigator.internal.BackHandler
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -55,13 +53,13 @@ import mg.dot.feufaro.solfa.Transpose
import mg.dot.feufaro.viewmodel.SolfaScreenModel import mg.dot.feufaro.viewmodel.SolfaScreenModel
import java.io.File import java.io.File
@OptIn(ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class, InternalVoyagerApi::class)
@Composable @Composable
fun MainScreenWithDrawer( fun MainScreenWithDrawer(
solfaScreenModel: SolfaScreenModel, solfaScreenModel: SolfaScreenModel,
sharedScreenModel: SharedScreenModel, sharedScreenModel: SharedScreenModel,
qrCodeResult: String, qrCodeResult: String,
onScannerButtonClick: () -> Unit,
solfaScrollState: ScrollState, solfaScrollState: ScrollState,
content: @Composable (PaddingValues) -> Unit, content: @Composable (PaddingValues) -> Unit,
) { ) {
@ -201,22 +199,6 @@ fun MainScreenWithDrawer(
val density = LocalDensity.current val density = LocalDensity.current
var keyboardHeightDp by remember { mutableStateOf(0.dp) } var keyboardHeightDp by remember { mutableStateOf(0.dp) }
BackHandler(enabled = isSearchActive) {
sharedScreenModel.showSearchMenu(false)
}
BackHandler(enabled = showMidiCtrl) {
sharedScreenModel.setMidiCtrl(false)
}
BackHandler(enabled = isQrVisible) {
sharedScreenModel.toggleQRCodeVisibility()
}
BackHandler(enabled = (!showMidiCtrl && isExpanded)) {
sharedScreenModel.setExpandedFAB(!isExpanded)
}
BackHandler(enabled = drawerState.isOpen) {
scope.launch { drawerState.close() }
}
ModalNavigationDrawer(drawerState = drawerState, drawerContent = { ModalNavigationDrawer(drawerState = drawerState, drawerContent = {
SimpleDrawerContent( SimpleDrawerContent(
items, items,
@ -225,6 +207,10 @@ fun MainScreenWithDrawer(
currentActivePath, currentActivePath,
drawerState, drawerState,
scope, scope,
onScannerButtonClick = {
scope.launch { drawerState.close() }
onScannerButtonClick()
},
onSongSelected = { newSong -> onSongSelected = { newSong ->
sharedScreenModel.loadNewSong("$midiFile") sharedScreenModel.loadNewSong("$midiFile")
}, },
@ -468,8 +454,7 @@ fun MainScreenWithDrawer(
} }
} }
}, actions = { }, actions = {
val appliedInterval by sharedScreenModel.transpositionInterval.collectAsState() var tempInterval by remember(fileContent) { mutableStateOf(0) }
var tempInterval by remember(fileContent, appliedInterval) { mutableStateOf(appliedInterval) }
var isEyeVisible by remember { mutableStateOf(false) } var isEyeVisible by remember { mutableStateOf(false) }
val isMinor = songKey.endsWith("m", ignoreCase = true) val isMinor = songKey.endsWith("m", ignoreCase = true)
val cleanSongKey = if (isMinor) songKey.dropLast(1) else songKey val cleanSongKey = if (isMinor) songKey.dropLast(1) else songKey
@ -477,9 +462,10 @@ fun MainScreenWithDrawer(
val songKeyIndex = keysOrder.indexOf(cleanSongKey).takeIf { it != -1 } ?: 0 val songKeyIndex = keysOrder.indexOf(cleanSongKey).takeIf { it != -1 } ?: 0
val rawKeyIndex = (songKeyIndex + tempInterval) % 12 val rawKeyIndex = (songKeyIndex + tempInterval) % 12
val tempUiKey = keysOrder[if (rawKeyIndex < 0) rawKeyIndex + 12 else rawKeyIndex] val tempUiKey = keysOrder[if (rawKeyIndex < 0) rawKeyIndex + 12 else rawKeyIndex]
val appliedInterval by sharedScreenModel.transpositionInterval.collectAsState()
val isPendingChange = tempInterval != appliedInterval val isPendingChange = tempInterval != appliedInterval
val isCurrentlyTransposed = appliedInterval != 0 val isCurrentlyTransposed = appliedInterval != 0
val formattedKey = if (isMinor) "${tempUiKey}m" else tempUiKey
Box( Box(
modifier = Modifier.padding(end = 16.dp), modifier = Modifier.padding(end = 16.dp),
contentAlignment = Alignment.TopCenter contentAlignment = Alignment.TopCenter
@ -489,11 +475,11 @@ fun MainScreenWithDrawer(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Text( Text(
text = formattedKey, text = if(isMinor) "${tempUiKey}m" else tempUiKey,
style = MaterialTheme.typography.displaySmall, style = MaterialTheme.typography.displaySmall,
fontWeight = FontWeight.Black, fontWeight = FontWeight.Black,
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
color = if (isCurrentlyTransposed || isPendingChange) MaterialTheme.colorScheme.tertiary else Color.White, color = if (isCurrentlyTransposed && !isPendingChange) Color(0xFFFFD700) else Color.White,
modifier = Modifier modifier = Modifier
.clickable( .clickable(
interactionSource = sharedInteractionSource, interactionSource = sharedInteractionSource,
@ -507,10 +493,7 @@ fun MainScreenWithDrawer(
DropdownMenu( DropdownMenu(
expanded = isEyeVisible, expanded = isEyeVisible,
onDismissRequest = { onDismissRequest = { isEyeVisible = false },
tempInterval = appliedInterval
isEyeVisible = false
},
containerColor = Color(0xFF2C3135).copy(alpha = 0.75f), containerColor = Color(0xFF2C3135).copy(alpha = 0.75f),
shape = RoundedCornerShape(16.dp), shape = RoundedCornerShape(16.dp),
modifier = Modifier.padding(12.dp) modifier = Modifier.padding(12.dp)
@ -520,8 +503,6 @@ fun MainScreenWithDrawer(
verticalArrangement = Arrangement.spacedBy(8.dp) verticalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
/* < = > */ /* < = > */
val isOriginalKeyC = cleanSongKey.equals("C", ignoreCase = true)
val isTransposedToC = tempUiKey.equals("C", ignoreCase = true) && !isOriginalKeyC
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceEvenly, horizontalArrangement = Arrangement.SpaceEvenly,
@ -554,11 +535,11 @@ fun MainScreenWithDrawer(
} }
val centralIcon = val centralIcon =
if (isPendingChange && !isTransposedToC) Icons.Filled.Check else Icons.Filled.SwapHoriz if (isPendingChange) Icons.Filled.Check else Icons.Filled.SwapHoriz
val centralTint = val centralTint =
if (isPendingChange) MaterialTheme.colorScheme.tertiary else Color.White if (isPendingChange) MaterialTheme.colorScheme.tertiary else Color.White
val tooltipText = val tooltipText =
if (isPendingChange) "Écoutez en $tempUiKey" else if (isCurrentlyTransposed) "Revenir en $songKey" else "" if (isPendingChange) "Transposer en $tempUiKey" else if (isCurrentlyTransposed) "Revenir en $songKey" else "Transposer"
TooltipBox( TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
@ -573,18 +554,13 @@ fun MainScreenWithDrawer(
IconButton( IconButton(
modifier = Modifier.size(36.dp), modifier = Modifier.size(36.dp),
onClick = { onClick = {
if (isPendingChange) { if (!isPendingChange && isCurrentlyTransposed) {
sharedScreenModel.setTranspositionInterval(tempInterval)
sharedScreenModel.stopMidi()
sharedScreenModel.seekTo(0f)
solfaScreenModel.loadFromFile(sharedScreenModel.activeFilePath.value)
} else if (isCurrentlyTransposed) {
tempInterval = 0 tempInterval = 0
sharedScreenModel.setTranspositionInterval(0) sharedScreenModel.setTranspositionInterval(0)
sharedScreenModel.stopMidi() } else {
sharedScreenModel.seekTo(0f) sharedScreenModel.setTranspositionInterval(tempInterval)
solfaScreenModel.loadFromFile(sharedScreenModel.activeFilePath.value)
} }
solfaScreenModel.loadFromFile(sharedScreenModel.activeFilePath.value)
isEyeVisible = false isEyeVisible = false
} }
) { ) {
@ -623,35 +599,22 @@ fun MainScreenWithDrawer(
val cIndex = keysOrder.indexOf("C") val cIndex = keysOrder.indexOf("C")
val intervalToC = if (cIndex != -1) cIndex - songKeyIndex else 0 val intervalToC = if (cIndex != -1) cIndex - songKeyIndex else 0
val buttonText = if (isTransposedToC) "Revenir en $songKey" else "Transposer en C"
val tooltipTextC = if (isTransposedToC) "Revenir en clé $songKey" else "Écrire les notes en Do (C)"
TooltipBox( TooltipBox(
positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(), positionProvider = TooltipDefaults.rememberPlainTooltipPositionProvider(),
tooltip = { tooltip = {
PlainTooltip( PlainTooltip(
containerColor = Color.DarkGray, containerColor = Color.DarkGray,
contentColor = Color.White contentColor = Color.White
) { Text(text = tooltipTextC, fontSize = 12.sp) } ) { Text(text = "Transposer en Do (C)", fontSize = 12.sp) }
}, },
state = rememberTooltipState() state = rememberTooltipState()
) { ) {
OutlinedButton( OutlinedButton(
onClick = { onClick = {
if (isTransposedToC) { tempInterval = intervalToC
tempInterval = 0 sharedScreenModel.setTranspositionInterval(intervalToC)
sharedScreenModel.setTranspositionInterval(0) solfaScreenModel.loadFromFile(sharedScreenModel.activeFilePath.value)
sharedScreenModel.setTranspToC(0)
sharedScreenModel.stopMidi()
sharedScreenModel.seekTo(0f)
} else {
val currentKey = sharedScreenModel.songKey.value
val intervalC = getIntervalToC(currentKey)
tempInterval = -intervalC
sharedScreenModel.setTranspToC(intervalC)
sharedScreenModel.stopMidi()
sharedScreenModel.seekTo(0f)
}
isEyeVisible = false isEyeVisible = false
}, },
modifier = Modifier.fillMaxWidth().height(36.dp), modifier = Modifier.fillMaxWidth().height(36.dp),
@ -665,7 +628,7 @@ fun MainScreenWithDrawer(
) )
) { ) {
Text( Text(
text = buttonText, text = "Transposer en C",
fontSize = 12.sp, fontSize = 12.sp,
fontWeight = FontWeight.Bold fontWeight = FontWeight.Bold
) )
@ -822,10 +785,9 @@ fun MainScreenWithDrawer(
) )
if (!savedPath.isNullOrEmpty()) { if (!savedPath.isNullOrEmpty()) {
val transpositionInterval = sharedScreenModel.transpositionInterval.value
val bpm = sharedScreenModel.getBpmFlow().value ?: 120f val bpm = sharedScreenModel.getBpmFlow().value ?: 120f
solfaScreenModel.generateMidiFile(bpm, savedPath, transpositionInterval) solfaScreenModel.generateMidiFile(bpm, savedPath)
println("Fichier MIDI enregistré avec succès sous : $savedPath") println("Fichier MIDI enregistré avec succès sous : $savedPath")
} }
} catch (e: Exception) { } catch (e: Exception) {
@ -1162,24 +1124,6 @@ fun MainScreenWithDrawer(
} }
}) })
} }
private fun getIntervalToC(key: String): Int {
val cleanKey = key.removeSuffix("m").trim().uppercase()
return when (cleanKey) {
"C" -> 0
"DB" -> 1
"D" -> 2
"EB" -> 3
"E" -> 4
"F" -> 5
"GB" -> 6
"G" -> 7
"AB" -> 8
"A" -> 9
"BB" -> 10
"B" -> 11
else -> 0
}
}
@Composable @Composable
private fun MyFAB( private fun MyFAB(

View file

@ -45,6 +45,7 @@ fun SimpleDrawerContent(
activePath: String = Solfa.currentFile, activePath: String = Solfa.currentFile,
drawerState: DrawerState, drawerState: DrawerState,
scope: CoroutineScope, scope: CoroutineScope,
onScannerButtonClick: () -> Unit,
onSongSelected: (String) -> Unit, onSongSelected: (String) -> Unit,
onSettingsCheck: () -> Unit, onSettingsCheck: () -> Unit,
onAboutCheck: () -> Unit, onAboutCheck: () -> Unit,
@ -194,7 +195,7 @@ fun SimpleDrawerContent(
stickyHeader { stickyHeader {
DrawerHeaderSticky( DrawerHeaderSticky(
title = "Fichiers locaux", title = "Personnel",
icon = Icons.Default.LibraryMusic, icon = Icons.Default.LibraryMusic,
isExpanded = externalExpanded, isExpanded = externalExpanded,
onToggle = { externalExpanded = !externalExpanded }, onToggle = { externalExpanded = !externalExpanded },

View file

@ -290,11 +290,6 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
transpositionInterval.value = interval transpositionInterval.value = interval
} }
val transpToCInt = MutableStateFlow(0)
fun setTranspToC(count: Int) {
transpToCInt.value = count
}
private val _synchronizedSYllables = MutableStateFlow<List<String>>(emptyList()) private val _synchronizedSYllables = MutableStateFlow<List<String>>(emptyList())
val synchronizedSyllables: StateFlow<List<String>> = _synchronizedSYllables.asStateFlow() val synchronizedSyllables: StateFlow<List<String>> = _synchronizedSYllables.asStateFlow()
@ -733,7 +728,6 @@ class SharedScreenModel(private val fileRepository: FileRepository) : ScreenMode
_sourceModeState.value = false _sourceModeState.value = false
_harmonyView.value = false _harmonyView.value = false
setTranspositionInterval(0) setTranspositionInterval(0)
setTranspToC(0)
setSilentDurBefr(0) setSilentDurBefr(0)
_midiMarkersList.value = emptyList() _midiMarkersList.value = emptyList()
_tuoTimestamps.value = emptyList() _tuoTimestamps.value = emptyList()

View file

@ -55,7 +55,7 @@ class SolfaScreenModel(
solfa.createNewSolfa(metadata) solfa.createNewSolfa(metadata)
} }
} }
fun generateMidiFile(bpm: Float, path: String, transpI: Int) { fun generateMidiFile(bpm: Float, path: String) {
solfa.generateMidiFile(bpm, path, transpI) solfa.generateMidiFile(bpm, path)
} }
} }

View file

@ -405,7 +405,7 @@ actual class FMediaPlayer actual constructor(
} }
// Farany // Farany
finReg.containsMatchIn(mTrim) -> { finReg.containsMatchIn(mTrim) -> {
val hasDcOrDsAfter = metadataList.any { m -> val hasDcAfter = metadataList.any { m ->
val after = (m.gridIndex ?: 0) > currentIndex val after = (m.gridIndex ?: 0) > currentIndex
println( println(
@ -413,15 +413,14 @@ actual class FMediaPlayer actual constructor(
) )
val hasMarker = m.marker.any { mk -> val hasMarker = m.marker.any { mk ->
dcRegex.containsMatchIn(mk) || dsRegex.containsMatchIn(mk) dcRegex.matches(mk.trim())
} }
println("after=$after, hasMarker=$hasMarker, result=${after && hasMarker}") println("after=$after, hasMarker=$hasMarker, result=${after && hasMarker}")
after && hasMarker after && hasMarker
} }
if (hasDcAfter) {
if (hasDcOrDsAfter) {
navigationSteps.add( navigationSteps.add(
NavigationStep( NavigationStep(
marker = marker, marker = marker,