This commit is contained in:
dotmg 2026-01-09 08:43:24 +01:00
parent 27aabc139d
commit 581bf42b32
20 changed files with 1544 additions and 192 deletions

View file

@ -13,6 +13,13 @@ plugins {
}
kotlin {
linuxX64 {
binaries {
executable {
}
}
}
androidTarget {
@OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions {
@ -21,7 +28,20 @@ kotlin {
}
jvm("desktop")
afterEvaluate {
afterEvaluate {
tasks.configureEach {
if (
name.startsWith("compile")
&& name.endsWith("KotlinMetadata")
) {
println("disabling $name")
enabled = false
}
}
}
}
sourceSets {
val desktopMain by getting
@ -30,7 +50,11 @@ kotlin {
implementation(libs.androidx.activity.compose)
implementation(libs.koin.android) // Koin Android-specific extensions
implementation(libs.koin.androidx.compose)
implementation("com.google.zxing:core:3.5.4")
implementation("androidx.camera:camera-view:1.5.2")
implementation("androidx.camera:camera-core:1.5.2")
implementation("androidx.camera:camera-camera2:1.5.2")
implementation("androidx.camera:camera-lifecycle:1.5.2")
}
commonMain.dependencies {
implementation(compose.components.resources)
@ -57,6 +81,9 @@ kotlin {
implementation(libs.cafe.voyager.koin)
implementation(libs.androidx.material.icons.extended)
implementation(kotlin("stdlib-jdk8"))
implementation(compose.desktop.currentOs)
//implementation("org.jetbrains.compose.foundation:foundation-desktop")
//implementation(libs.ktmidi)
}
commonTest.dependencies {
@ -65,6 +92,8 @@ kotlin {
desktopMain.dependencies {
implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutinesSwing)
implementation("com.google.zxing:core:3.5.4")
implementation("com.google.zxing:javase:3.5.4")
}
}
}

View file

@ -0,0 +1,293 @@
package mg.dot.feufaro.data
private val LISTE_FICHIERS_SOLFA = listOf(
"480.txt",
"ews-10.txt",
"ews-11.txt",
"ews-12.txt",
"ews-13.txt",
"ews-14.txt",
"ews-15.txt",
"ews-16.txt",
"ews-179.txt",
"ews-17.txt",
"ews-18.txt",
"ews-190.txt",
"ews-19.txt",
"ews-1.txt",
"ews-20.txt",
"ews-21.txt",
"ews-22.txt",
"ews-23.txt",
"ews-24.txt",
"ews-25-1.txt",
"ews-25-2.txt",
"ews-26-1.txt",
"ews-26-2.txt",
"ews-27.txt",
"ews-28.txt",
"ews-29.txt",
"ews-2.txt",
"ews-30-1.txt",
"ews-30-2.txt",
"ews-31.txt",
"ews-32.txt",
"ews-33.txt",
"ews-34.txt",
"ews-351-1.txt",
"ews-35.txt",
"ews-36.txt",
"ews-37.txt",
"ews-38.txt",
"ews-39.txt",
"ews-3.txt",
"ews-40.txt",
"ews-41.txt",
"ews-42.txt",
"ews-43.txt",
"ews-443.txt",
"ews-44.txt",
"ews-456.txt",
"ews-45.txt",
"ews-46.txt",
"ews-47.txt",
"ews-4.txt",
"ews-5.txt",
"ews-66.txt",
"ews-6.txt",
"ews-7.txt",
"ews-89.txt",
"ews-8.txt",
"ews-999.txt",
"ews-9.txt",
"ff-33.txt",
"ff-43.txt",
"ff-7.txt",
"ff-8.txt",
"ff-9.txt",
"ffmp-16.txt",
"ffpm-100.txt",
"ffpm-101.txt",
"ffpm-103.txt",
"ffpm-105.txt",
"ffpm-106.txt",
"ffpm-110.txt",
"ffpm-111.txt",
"ffpm-118.txt",
"ffpm-12-1.txt",
"ffpm-12-2.txt",
"ffpm-123.txt",
"ffpm-126.txt",
"ffpm-127.txt",
"ffpm-129.txt",
"ffpm-1-2.txt",
"ffpm-131.txt",
"ffpm-132.txt",
"ffpm-133.txt",
"ffpm-134.txt",
"ffpm-136.txt",
"ffpm-137.txt",
"ffpm-140-1.txt",
"ffpm-140-2.txt",
"ffpm-141.txt",
"ffpm-14.txt",
"ffpm-153.txt",
"ffpm-154.txt",
"ffpm-155.txt",
"ffpm-157.txt",
"ffpm-160.txt",
"ffpm-161.txt",
"ffpm-163.txt",
"ffpm-164.txt",
"ffpm-165.txt",
"ffpm-166-new.txt",
"ffpm-166.txt",
"ffpm-16.txt",
"ffpm-170.txt",
"ffpm-172-1.txt",
"ffpm-172-2.txt",
"ffpm-175.txt",
"ffpm-178.txt",
"ffpm-179.txt",
"ffpm-17.txt",
"ffpm-180.txt",
"ffpm-189.txt",
"ffpm-190.txt",
"ffpm-191.txt",
"ffpm-192.txt",
"ffpm-194.txt",
"ffpm-195-1.txt",
"ffpm-195-2.txt",
"ffpm-198.txt",
"ffpm-199.txt",
"ffpm-19.txt",
"ffpm-202.txt",
"ffpm-203.txt",
"ffpm-204.txt",
"ffpm-209.txt",
"ffpm-20.txt",
"ffpm-210.txt",
"ffpm-211.txt",
"ffpm-212.txt",
"ffpm-213.txt",
"ffpm-214.txt",
"ffpm-21.txt",
"ffpm-220.txt",
"ffpm-221.txt",
"ffpm-222.txt",
"ffpm-223.txt",
"ffpm-224.txt",
"ffpm-225.txt",
"ffpm-229.txt",
"ffpm-232.txt",
"ffpm-233.txt",
"ffpm-236.txt",
"ffpm-237.txt",
"ffpm-238-1.txt",
"ffpm-238-2.txt",
"ffpm-239-1.txt",
"ffpm-239-2.txt",
"ffpm-249.txt",
"ffpm-250-1.txt",
"ffpm-250-2.txt",
"ffpm-251.txt",
"ffpm-253.txt",
"ffpm-254.txt",
"ffpm-255.txt",
"ffpm-256.txt",
"ffpm-257-1.txt",
"ffpm-257-2.txt",
"ffpm-259.txt",
"ffpm-260.txt",
"ffpm-263.txt",
"ffpm-269.txt",
"ffpm-270.txt",
"ffpm-271.txt",
"ffpm-272.txt",
"ffpm-273.txt",
"ffpm-274.txt",
"ffpm-276.txt",
"ffpm-279.txt",
"ffpm-281.txt",
"ffpm-297.txt",
"ffpm-307.txt",
"ffpm-310.txt",
"ffpm-311.txt",
"ffpm-315.txt",
"ffpm-321.txt",
"ffpm-32.txt",
"ffpm-332.txt",
"ffpm-33.txt",
"ffpm-352.txt",
"ffpm-353-1.txt",
"ffpm-353-2.txt",
"ffpm-357.txt",
"ffpm-358.txt",
"ffpm-35.txt",
"ffpm-367.txt",
"ffpm-381-1.txt",
"ffpm-381-2.txt",
"ffpm-38.txt",
"ffpm-392.txt",
"ffpm-408-1.txt",
"ffpm-408-2.txt",
"ffpm-413.txt",
"ffpm-4-1.txt",
"ffpm-42-1.txt",
"ffpm-42-2.txt",
"ffpm-428.txt",
"ffpm-4-2.txt",
"ffpm-43.txt",
"ffpm-440-1.txt",
"ffpm-444-1.txt",
"ffpm-444-2.txt",
"ffpm-449.txt",
"ffpm-44.txt",
"ffpm-46.txt",
"ffpm-47.txt",
"ffpm-483.txt",
"ffpm-489.txt",
"ffpm-490.txt",
"ffpm-49-1.txt",
"ffpm-49-2.txt",
"ffpm-501.txt",
"ffpm-503.txt",
"ffpm-5-1.txt",
"ffpm-51.txt",
"ffpm-5-2.txt",
"ffpm-52.txt",
"ffpm-539.txt",
"ffpm-546.txt",
"ffpm-549.txt",
"ffpm-54.txt",
"ffpm-558.txt",
"ffpm-55.txt",
"ffpm-563.txt",
"ffpm-564.txt",
"ffpm-56-new.txt",
"ffpm-56.txt",
"ffpm-57-new.txt",
"ffpm-57.txt",
"ffpm-58.txt",
"ffpm-603-1.txt",
"ffpm-603-2.txt",
"ffpm-610.txt",
"ffpm-611-1.txt",
"ffpm-611-2.txt",
"ffpm-616.txt",
"ffpm-61.txt",
"ffpm-626.txt",
"ffpm-636.txt",
"ffpm-640.txt",
"ffpm-642-1.txt",
"ffpm-642-2.txt",
"ffpm-653.txt",
"ffpm-674.txt",
"ffpm-69.txt",
"ffpm-6.txt",
"ffpm-705-2.txt",
"ffpm-71.txt",
"ffpm-725.txt",
"ffpm-726-1.txt",
"ffpm-726-2.txt",
"ffpm-729.txt",
"ffpm-72.txt",
"ffpm-733.txt",
"ffpm-734.txt",
"ffpm-735.txt",
"ffpm-737.txt",
"ffpm-750.txt",
"ffpm-75.txt",
"ffpm-760.txt",
"ffpm-79-1.txt",
"ffpm-79-2.txt",
"ffpm-796.txt",
"ffpm-799.txt",
"ffpm-7.txt",
"ffpm-82-1.txt",
"ffpm-82-2.txt",
"ffpm-825.txt",
"ffpm-87.txt",
"ffpm-89.txt",
"ffpm-8.txt",
"ffpm-91.txt",
"ffpm-93.txt",
"ffpm-94.txt",
"ffpm-97-1.txt",
"ffpm-97-2.txt",
"ffpm-9.txt"
)
private const val ASSET_PREFIX = "assets://"
actual fun getDrawerItems(): List<DrawerItem> {
return LISTE_FICHIERS_SOLFA.mapIndexed { index, fileName ->
val titleWithoutExtension = fileName.removeSuffix(".txt")
DrawerItem(
id = index + 1,
title = titleWithoutExtension,
path = ASSET_PREFIX + fileName
)
}
}

View file

@ -0,0 +1,25 @@
package mg.dot.feufaro.midi
import android.media.midi.*
import mg.dot.feufaro.FileRepository
actual class MidiWriterKotlin actual constructor(private val fileRepository: FileRepository) {
private val midiEvents = mutableListOf<String>()
actual fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long) {
// TODO: Implémentation Android pour ajouter des notes
midiEvents.add("Note: $note on tick $tick")
}
actual fun save(filePath: String) {
// TODO: Implémentation Android pour écrire le fichier MIDI
println("Sauvegarde MIDI sur Android (Non implémenté complètement)")
}
actual fun addMetaMessage(type: Int, tick: Int, nbData: Int, metaByteString: String) {
// TODO: Implémentation Android pour ajouter des Meta Messages
}
actual fun process(pitches: List<MidiPitch>) {
// TODO: Implémentation Android pour traiter les pitches
}
}

View file

@ -0,0 +1,30 @@
package mg.dot.feufaro.ui
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.asImageBitmap
import com.google.zxing.BarcodeFormat
import com.google.zxing.qrcode.QRCodeWriter
import android.graphics.Bitmap
import android.graphics.Color
import java.io.IOException
actual fun generateQRCode(content: String, size: Int): ImageBitmap? {
if (content.isNullOrBlank()) return null
return try {
val bitMatrix = QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, size, size)
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.RGB_565)
for (x in 0 until size) {
for (y in 0 until size) {
bitmap.setPixel(x, y, if (bitMatrix[x, y]) Color.BLACK else Color.WHITE)
}
}
return bitmap.asImageBitmap()
} catch (e: Exception) {
println("Erreur de génération du QR Code Android: ${e.message}")
null
}
}

View file

@ -0,0 +1,17 @@
package mg.dot.feufaro.ui
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
actual fun ScrollableDrawerContent(
modifier: Modifier,
lazyListState: LazyListState,
content: @Composable () -> Unit
) {
Box(modifier = modifier) {
content()
}
}

View file

@ -37,6 +37,7 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Popup
import mg.dot.feufaro.solfa.LazyVerticalGridTUO
import mg.dot.feufaro.ui.MainScreenWithDrawer
import kotlin.math.roundToInt
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.koinScreenModel
@ -61,82 +62,105 @@ object ScreenSolfa : Screen {
val coroutineScope = rememberCoroutineScope()
val scrollState = rememberScrollState()
Box(
Modifier.fillMaxSize()
) {
Column(
Modifier
.verticalScroll(scrollState)
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = { offset ->
menuPosition = offset
showContextualMenu = true
}
)
},
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (showContextualMenu) {
Popup(
alignment = Alignment.TopStart,
offset = IntOffset(
menuPosition.x.roundToInt(),
menuPosition.y.roundToInt()
),
onDismissRequest = {
showContextualMenu = false
}
) {
ContextualMenu(onMenuItemClick = { item ->
println("Clicked: $item")
showContextualMenu = false
})
}
}
Column(
modifier = Modifier
.fillMaxSize()
) {
FlowRow(
modifier = Modifier.fillMaxWidth()
.windowInsetsPadding(WindowInsets.safeDrawing)
.padding(start = 8.dp, end = 8.dp, top = 8.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
val measureString: String by sharedScreenModel.measure.collectAsState()
val songTitle: String by sharedScreenModel.songTitle.collectAsState()
val songKey: String by sharedScreenModel.songKey.collectAsState()
Text(text = songTitle, fontWeight = FontWeight.Bold)
Text(
text = songKey,
modifier = Modifier.background(Color(0xff, 0xea, 0xe7))
.padding(horizontal = 4.dp)
)
Text(text = measureString)
Text(text = "Stanza: $stanza")
ScreenTranspose.Content()
}
LazyVerticalGridTUO(
gridTUOData,
gridWidthPx = gridWidthPx,
onGridWidthMeasured = { width -> gridWidthPx = width }
)
FlowRow(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
MGButton(onClick = {
//showContent = !showContent
var isScanning by remember { mutableStateOf(false) }
var qrCodeResult by remember { mutableStateOf("Aucun code scanné") }
solfaScreenModel.loadNextInPlaylist()
}, modifier = Modifier.height(40.dp)) {
val nextLabel: String by sharedScreenModel.nextLabel.collectAsState()
Text(nextLabel)
/* if (isScanning) {
ScannerScreen(
onScanComplete = { result ->
qrCodeResult = result
isScanning = false
},
onClose = {
isScanning = false
}
)
} else {*/
MainScreenWithDrawer(
solfaScreenModel,
sharedScreenModel,
qrCodeResult = qrCodeResult,
onScannerButtonClick = {
isScanning = true
}
) { paddingValues ->
Box(
Modifier.fillMaxSize().padding(paddingValues)
) {
Column(
Modifier
.verticalScroll(scrollState)
.pointerInput(Unit) {
detectTapGestures(
onDoubleTap = { offset ->
menuPosition = offset
showContextualMenu = true
}
)
},
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (showContextualMenu) {
Popup(
alignment = Alignment.TopStart,
offset = IntOffset(
menuPosition.x.roundToInt(),
menuPosition.y.roundToInt()
),
onDismissRequest = {
showContextualMenu = false
}
) {
ContextualMenu(onMenuItemClick = { item ->
println("Clicked: $item")
showContextualMenu = false
})
}
MGButton(onClick = {
/*println("Load btn clicked")
}
Column(
modifier = Modifier
.fillMaxSize()
) {
FlowRow(
modifier = Modifier.fillMaxWidth()
.windowInsetsPadding(WindowInsets.safeDrawing)
.padding(start = 8.dp, end = 8.dp, top = 8.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
val measureString: String by sharedScreenModel.measure.collectAsState()
val songTitle: String by sharedScreenModel.songTitle.collectAsState()
val songKey: String by sharedScreenModel.songKey.collectAsState()
Text(text = songTitle, fontWeight = FontWeight.Bold)
Text(
text = songKey,
modifier = Modifier.background(Color(0xff, 0xea, 0xe7))
.padding(horizontal = 4.dp)
)
Text(text = measureString)
Text(text = "Stanza: $stanza")
ScreenTranspose.Content()
}
LazyVerticalGridTUO(
gridTUOData,
gridWidthPx = gridWidthPx,
onGridWidthMeasured = { width -> gridWidthPx = width }
)
FlowRow(
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
MGButton(onClick = {
//showContent = !showContent
solfaScreenModel.loadNextInPlaylist()
}, modifier = Modifier.height(40.dp)) {
val nextLabel: String by sharedScreenModel.nextLabel.collectAsState()
Text(nextLabel)
}
MGButton(onClick = {
/*println("Load btn clicked")
launchFilePicker(
mimeTypes = arrayOf("text/plain"),
onFileSelected = { path ->
@ -147,48 +171,49 @@ object ScreenSolfa : Screen {
}
}
)*/
solfaScreenModel.loadCustomFile()
}) {
val loadFile: String by sharedScreenModel.loadFile.collectAsState()
Text(loadFile)
}
}
FlowRow(
modifier = Modifier.fillMaxWidth()
.windowInsetsPadding(WindowInsets.safeDrawing)
.padding(start = 8.dp, end = 8.dp, top = 8.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
val nbStanzas: Int by sharedScreenModel.nbStanzas.collectAsState()
for (i in 1..nbStanzas) {
MGButton(
enabled = (i != stanza.toInt()),
onClick = {
sharedScreenModel.setStanza(i)
coroutineScope.launch {
scrollState.animateScrollTo(0)
}
},
modifier = Modifier.padding(horizontal = 4.dp)
) {
Text("$i")
solfaScreenModel.loadCustomFile()
}) {
val loadFile: String by sharedScreenModel.loadFile.collectAsState()
Text(loadFile)
}
}
val songAuthor: String by sharedScreenModel.songAuthor.collectAsState()
val songComposer: String by sharedScreenModel.songComposer.collectAsState()
val songRhythm: String by sharedScreenModel.songRhythm.collectAsState()
Text(text = songAuthor, fontStyle = FontStyle.Italic)
Text(text = songRhythm)
Text(text = songComposer)
FlowRow(
modifier = Modifier.fillMaxWidth()
.windowInsetsPadding(WindowInsets.safeDrawing)
.padding(start = 8.dp, end = 8.dp, top = 8.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
val nbStanzas: Int by sharedScreenModel.nbStanzas.collectAsState()
for (i in 1..nbStanzas) {
MGButton(
enabled = (i != stanza.toInt()),
onClick = {
sharedScreenModel.setStanza(i)
coroutineScope.launch {
scrollState.animateScrollTo(0)
}
},
modifier = Modifier.padding(horizontal = 4.dp)
) {
Text("$i")
}
}
val songAuthor: String by sharedScreenModel.songAuthor.collectAsState()
val songComposer: String by sharedScreenModel.songComposer.collectAsState()
val songRhythm: String by sharedScreenModel.songRhythm.collectAsState()
Text(text = songAuthor, fontStyle = FontStyle.Italic)
Text(text = songRhythm)
Text(text = songComposer)
}
}
}
MyVerticalScrollbar(
scrollState,
modifier = Modifier.align(Alignment.CenterEnd)
) {}
}
MyVerticalScrollbar(
scrollState,
modifier = Modifier.align(Alignment.CenterEnd)
) {}
}
}

View file

@ -0,0 +1,9 @@
package mg.dot.feufaro.data
data class DrawerItem(
val id: Int,
val title: String,
val path: String
)
expect fun getDrawerItems(): List<DrawerItem>

View file

@ -1,71 +1,10 @@
package mg.dot.feufaro.midi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mg.dot.feufaro.FileRepository
import javax.sound.midi.*
class MidiWriterKotlin (private val fileRepository: FileRepository) {
private val sequence = Sequence(Sequence.PPQ, 60)
private val track = sequence.createTrack()
private var tick: Long = 0
private var nextTick: MutableList<Int> = mutableListOf()
private val noteOn = ShortMessage()
private val noteOff = ShortMessage()
private val lastPitch : MutableList<Int> = mutableListOf()
private val useChord : Boolean = true
fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long) {
var channel: Int = voiceNumber - 1
if (useChord) {
channel = channel / 2
}
var note = note
if (voiceNumber == 3 || voiceNumber == 4) {
note -= 12
}
if (lastPitch.size > voiceNumber && lastPitch[voiceNumber] > 0) {
noteOff.setMessage(ShortMessage.NOTE_OFF, channel, lastPitch[voiceNumber], 0)
val n2 = noteOff.clone() as MidiMessage
track.add(MidiEvent(n2, tick))
}
var velocity = velocity
if (note <= 0) {
note = 40
velocity = 0
}
noteOn.setMessage(ShortMessage.NOTE_ON, channel, note, velocity)
val n1: MidiMessage = noteOn.clone() as MidiMessage
track.add(MidiEvent(n1, tick))
while(lastPitch.size <= voiceNumber) {
lastPitch.add(0)
}
lastPitch[voiceNumber] = note
}
fun save(filePath: String) {
val parseScope = CoroutineScope(Dispatchers.Default)
parseScope.launch {
val out = fileRepository.getOutputStream(filePath)
MidiSystem.write(sequence, 1, out)
out.close()
}
}
fun addMetaMessage(type: Int, tick: Int, nbData: Int, metaByteString: String) {
val byteArray = metaByteString.toByteArray()
val metaMessage = MetaMessage(type, byteArray, nbData)
track.add(MidiEvent(metaMessage, tick.toLong()))
}
fun process(pitches: List<MidiPitch>) {
val lastTick = 0
nextTick.clear()
// addMetaMessage(0x59, 4, 2, 2,0)
tick = 0
pitches.forEach {
if (it.metaType > 0) {
addMetaMessage(it.metaType, it.tick, it.metaByteSize, it.metaBytes)
} else if (it.pitch != "") {
addNote(it.voiceNumber, it.pitch.toInt(), 100, it.tick.toLong())
}
}
}
expect class MidiWriterKotlin(fileRepository: FileRepository) {
fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long)
fun save(filePath: String)
fun addMetaMessage(type: Int, tick: Int, nbData: Int, metaByteString: String)
fun process(pitches: List<MidiPitch>)
}

View file

@ -2,10 +2,12 @@ package mg.dot.feufaro.solfa
import SharedScreenModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import mg.dot.feufaro.FileRepository
import mg.dot.feufaro.getConfigDirectoryPath
import mg.dot.feufaro.launchFilePicker
import mg.dot.feufaro.midi.MidiPitch
import mg.dot.feufaro.midi.MidiWriterKotlin
@ -144,20 +146,28 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
}
fun loadFile() {
println("Load btn clicked")
launchFilePicker(
mimeTypes = arrayOf("text/plain"),
onFileSelected = { path ->
if (path != null) {
println("fichier $path");
sharedScreenModel.reset()
parse(path)
//loadSolfa()
} else {
println("Pas de dichier")
val screenModelScope= CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
screenModelScope.launch {
val initialPath = fileRepository.readLastDirectoryPath()
val homedir = getConfigDirectoryPath();
launchFilePicker(
mimeTypes = arrayOf("text/plain"),
onFileSelected = { path ->
if (path != null) {
println("fichier $path");
sharedScreenModel.reset()
parse(path)
screenModelScope.launch {
fileRepository.saveLastDirectoryPath(path)
}
//loadSolfa()
} else {
println("Pas de dichier")
}
}
}
)
)
}
}
fun parse(sourceFile: String) {
@ -170,6 +180,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
println("Opening $sourceFile raised exception {${e.message}")
emptyList()
}
val contentString = fileRepository.readFileLines(sourceFile).joinToString("\n")
sharedScreenModel.setFileContent(contentString, sourceFile)
T.clear()
N.clear()
L.clear()

View file

@ -0,0 +1,373 @@
package mg.dot.feufaro.ui
import SharedScreenModel
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
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.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.*
import mg.dot.feufaro.data.DrawerItem
import mg.dot.feufaro.data.getDrawerItems
import mg.dot.feufaro.getPlatform
import mg.dot.feufaro.solfa.Solfa
import mg.dot.feufaro.viewmodel.SolfaScreenModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MainScreenWithDrawer(
solfaScreenModel: SolfaScreenModel,
sharedScreenModel: SharedScreenModel,
qrCodeResult: String,
onScannerButtonClick: () -> Unit,
content: @Composable (PaddingValues) -> Unit,
) {
val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed)
val scope = rememberCoroutineScope()
val items = getDrawerItems()
val songTitle = sharedScreenModel.songTitle.collectAsState().value
val filteredSongs by sharedScreenModel.filteredSongs.collectAsState()
val songKey = sharedScreenModel.songKey.collectAsState().value
val measure = sharedScreenModel.measure.collectAsState().value
val scrollState = rememberScrollState()
val currentActivePath = Solfa.currentFile
var textInput = sharedScreenModel.searchTitle.collectAsState().value
var isSearchActive by remember { mutableStateOf(false) }
val focusRequester = remember { FocusRequester() }
LaunchedEffect(isSearchActive) {
if (isSearchActive) {
focusRequester.requestFocus()
}
}
ModalNavigationDrawer(
drawerState = drawerState,
drawerContent = {
SimpleDrawerContent(
items, solfaScreenModel, sharedScreenModel, currentActivePath, drawerState, scope,
onScannerButtonClick = {
scope.launch { drawerState.close() }
onScannerButtonClick()
}
)
},
content = {
Scaffold(
contentWindowInsets = WindowInsets(0, 0, 0, 0),
topBar = {
TopAppBar(
modifier = Modifier
.height(55.dp)
.windowInsetsPadding(WindowInsets.statusBars),
title = {
Column (
modifier= Modifier
.fillMaxSize()
.verticalScroll(scrollState)
) {
AnimatedContent(
targetState = isSearchActive,
label = "Search Transition"
) {
targetIsActive ->
if(targetIsActive) {
TextField(
value = textInput,
onValueChange = { newValue ->
textInput = newValue
sharedScreenModel.updateSearchTxt(newValue)
},
placeholder = {
Text("Rechercher...")
},
leadingIcon = {
Icon(
imageVector = Icons.Default.Search,
contentDescription = "Icône de recherche",
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
},
textStyle = MaterialTheme.typography.titleLarge.copy(
fontSize = 17.sp,
fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onSurface
),
modifier = Modifier
.focusRequester(focusRequester)
.fillMaxWidth()
)
} else {
Text(songTitle,
modifier = Modifier.weight(1f, fill = true),
maxLines = 1,
softWrap = false,
overflow = TextOverflow.Ellipsis,
)
}
}
}
},
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(
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary,
actionIconContentColor = MaterialTheme.colorScheme.onPrimary,
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
scrolledContainerColor = MaterialTheme.colorScheme.onPrimary,
)
)
},
floatingActionButton = {
FloatingActionButton (
onClick = {
sharedScreenModel.toggleQRCodeVisibility()
},
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
fun SimpleDrawerContent(
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()
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)
}
)
}
}
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")
}
}
}
}
}
}
}
}

View file

@ -0,0 +1,61 @@
package mg.dot.feufaro.ui
import SharedScreenModel
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Card
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.unit.dp
@Composable
fun QRDisplay(sharedScreenModel: SharedScreenModel) {
val content by sharedScreenModel.fileContent
val qrCodeImage = remember(content) {
if (content != null) {
generateQRCode(content!!, size = 800)
} else {
null
}
}
Box(
modifier = Modifier
.fillMaxSize()
.background(MaterialTheme.colorScheme.scrim.copy(alpha = 0.5f)) // Fond semi-transparent
.clickable { sharedScreenModel.toggleQRCodeVisibility() } // Fermer au clic
.imePadding()
.systemBarsPadding(),
contentAlignment = Alignment.Center
) {
Card(
modifier = Modifier.wrapContentSize().shadow(16.dp, shape = MaterialTheme.shapes.large)
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.padding(24.dp)
) {
Text("Scanner ceci: ")
if (qrCodeImage != null) {
Image(
bitmap = qrCodeImage,
contentDescription = "Code QR du fichier actif",
modifier = Modifier.size(400.dp)
)
} else {
Text("Chargement du Code QR ou contenu vide...", modifier = Modifier.padding(16.dp))
}
}
}
}
}

View file

@ -0,0 +1,5 @@
package mg.dot.feufaro.ui
import androidx.compose.ui.graphics.ImageBitmap
expect fun generateQRCode(content: String, size: Int): ImageBitmap?

View file

@ -0,0 +1,12 @@
package mg.dot.feufaro.ui
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.foundation.lazy.LazyListState
@Composable
expect fun ScrollableDrawerContent(
modifier: Modifier,
lazyListState: LazyListState,
content: @Composable () -> Unit
)

View file

@ -1,9 +1,18 @@
// commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import cafe.adriel.voyager.core.model.ScreenModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import mg.dot.feufaro.data.DrawerItem
import mg.dot.feufaro.solfa.TimeUnitObject
import mg.dot.feufaro.data.getDrawerItems
class SharedScreenModel() : ScreenModel {
private val _nextLabel = MutableStateFlow<String>("Next ...")
@ -44,7 +53,44 @@ class SharedScreenModel() : ScreenModel {
var _hasMarker = MutableStateFlow<Boolean>(false)
val hasMarker: StateFlow<Boolean> = _hasMarker.asStateFlow()
fun appendData(otherData: String) {
private val _searchTitle = MutableStateFlow<String>("")
val searchTitle: StateFlow<String> = _searchTitle.asStateFlow()
@OptIn
val filteredSongs: StateFlow<List<DrawerItem>> = searchTitle
.mapLatest { currentTitle ->
val searchTxt = if (currentTitle == "") "" else currentTitle.trim()
return@mapLatest if(searchTxt.isEmpty()) {
emptyList()
} else {
getDrawerItems().filter { item ->
item.title.contains(searchTxt, ignoreCase = true)
}
}
}.stateIn (
scope = CoroutineScope(Dispatchers.Default),
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)
private val _fileContent = mutableStateOf<String?>(null)
val fileContent: State<String?> = _fileContent
private val _isQRCodeVisible = mutableStateOf(false)
val isQRCodeVisible: State<Boolean> = _isQRCodeVisible
private val _activeFilePath = mutableStateOf("")
val activeFilePath: State<String> = _activeFilePath
fun setFileContent(content: String?, path: String) {
_fileContent.value = content
_activeFilePath.value = path
}
fun toggleQRCodeVisibility() {
_isQRCodeVisible.value = !_isQRCodeVisible.value
}
fun updateSearchTxt(searchValue: String) {
_searchTitle.value = searchValue
} fun appendData(otherData: String) {
_nextLabel.value += otherData
}

View file

@ -19,4 +19,7 @@ class SolfaScreenModel(
fun loadCustomFile() {
solfa.loadFile()
}
fun loadFromFile(path: String) {
solfa.parse(path)
}
}

View file

@ -0,0 +1,281 @@
package mg.dot.feufaro.data
actual fun getDrawerItems(): List<DrawerItem> {
return listOf(
DrawerItem(1, "480", "assets://480.txt"),
DrawerItem(2, "ews-10", "assets://ews-10.txt"),
DrawerItem(3, "ews-11", "assets://ews-11.txt"),
DrawerItem(4, "ews-12", "assets://ews-12.txt"),
DrawerItem(5, "ews-13", "assets://ews-13.txt"),
DrawerItem(6, "ews-14", "assets://ews-14.txt"),
DrawerItem(7, "ews-15", "assets://ews-15.txt"),
DrawerItem(8, "ews-16", "assets://ews-16.txt"),
DrawerItem(9, "ews-179", "assets://ews-179.txt"),
DrawerItem(10, "ews-17", "assets://ews-17.txt"),
DrawerItem(11, "ews-18", "assets://ews-18.txt"),
DrawerItem(12, "ews-190", "assets://ews-190.txt"),
DrawerItem(13, "ews-19", "assets://ews-19.txt"),
DrawerItem(14, "ews-1", "assets://ews-1.txt"),
DrawerItem(15, "ews-20", "assets://ews-20.txt"),
DrawerItem(16, "ews-21", "assets://ews-21.txt"),
DrawerItem(17, "ews-22", "assets://ews-22.txt"),
DrawerItem(18, "ews-23", "assets://ews-23.txt"),
DrawerItem(19, "ews-24", "assets://ews-24.txt"),
DrawerItem(20, "ews-25-1", "assets://ews-25-1.txt"),
DrawerItem(21, "ews-25-2", "assets://ews-25-2.txt"),
DrawerItem(22, "ews-26-1", "assets://ews-26-1.txt"),
DrawerItem(23, "ews-26-2", "assets://ews-26-2.txt"),
DrawerItem(24, "ews-27", "assets://ews-27.txt"),
DrawerItem(25, "ews-28", "assets://ews-28.txt"),
DrawerItem(26, "ews-29", "assets://ews-29.txt"),
DrawerItem(27, "ews-2", "assets://ews-2.txt"),
DrawerItem(28, "ews-30-1", "assets://ews-30-1.txt"),
DrawerItem(29, "ews-30-2", "assets://ews-30-2.txt"),
DrawerItem(30, "ews-31", "assets://ews-31.txt"),
DrawerItem(31, "ews-32", "assets://ews-32.txt"),
DrawerItem(32, "ews-33", "assets://ews-33.txt"),
DrawerItem(33, "ews-34", "assets://ews-34.txt"),
DrawerItem(34, "ews-351-1", "assets://ews-351-1.txt"),
DrawerItem(35, "ews-35", "assets://ews-35.txt"),
DrawerItem(36, "ews-36", "assets://ews-36.txt"),
DrawerItem(37, "ews-37", "assets://ews-37.txt"),
DrawerItem(38, "ews-38", "assets://ews-38.txt"),
DrawerItem(39, "ews-39", "assets://ews-39.txt"),
DrawerItem(40, "ews-3", "assets://ews-3.txt"),
DrawerItem(41, "ews-40", "assets://ews-40.txt"),
DrawerItem(42, "ews-41", "assets://ews-41.txt"),
DrawerItem(43, "ews-42", "assets://ews-42.txt"),
DrawerItem(44, "ews-43", "assets://ews-43.txt"),
DrawerItem(45, "ews-443", "assets://ews-443.txt"),
DrawerItem(46, "ews-44", "assets://ews-44.txt"),
DrawerItem(47, "ews-456", "assets://ews-456.txt"),
DrawerItem(48, "ews-45", "assets://ews-45.txt"),
DrawerItem(49, "ews-46", "assets://ews-46.txt"),
DrawerItem(50, "ews-47", "assets://ews-47.txt"),
DrawerItem(51, "ews-4", "assets://ews-4.txt"),
DrawerItem(52, "ews-5", "assets://ews-5.txt"),
DrawerItem(53, "ews-66", "assets://ews-66.txt"),
DrawerItem(54, "ews-6", "assets://ews-6.txt"),
DrawerItem(55, "ews-7", "assets://ews-7.txt"),
DrawerItem(56, "ews-89", "assets://ews-89.txt"),
DrawerItem(57, "ews-8", "assets://ews-8.txt"),
DrawerItem(58, "ews-999", "assets://ews-999.txt"),
DrawerItem(59, "ews-9", "assets://ews-9.txt"),
DrawerItem(60, "ff-33", "assets://ff-33.txt"),
DrawerItem(61, "ff-43", "assets://ff-43.txt"),
DrawerItem(62, "ff-7", "assets://ff-7.txt"),
DrawerItem(63, "ff-8", "assets://ff-8.txt"),
DrawerItem(64, "ff-9", "assets://ff-9.txt"),
DrawerItem(65, "ffmp-16", "assets://ffmp-16.txt"),
DrawerItem(66, "ffpm-100", "assets://ffpm-100.txt"),
DrawerItem(67, "ffpm-101", "assets://ffpm-101.txt"),
DrawerItem(68, "ffpm-103", "assets://ffpm-103.txt"),
DrawerItem(69, "ffpm-105", "assets://ffpm-105.txt"),
DrawerItem(70, "ffpm-106", "assets://ffpm-106.txt"),
DrawerItem(71, "ffpm-110", "assets://ffpm-110.txt"),
DrawerItem(72, "ffpm-111", "assets://ffpm-111.txt"),
DrawerItem(73, "ffpm-118", "assets://ffpm-118.txt"),
DrawerItem(74, "ffpm-12-1", "assets://ffpm-12-1.txt"),
DrawerItem(75, "ffpm-12-2", "assets://ffpm-12-2.txt"),
DrawerItem(76, "ffpm-123", "assets://ffpm-123.txt"),
DrawerItem(77, "ffpm-126", "assets://ffpm-126.txt"),
DrawerItem(78, "ffpm-127", "assets://ffpm-127.txt"),
DrawerItem(79, "ffpm-129", "assets://ffpm-129.txt"),
DrawerItem(80, "ffpm-1-2", "assets://ffpm-1-2.txt"),
DrawerItem(81, "ffpm-131", "assets://ffpm-131.txt"),
DrawerItem(82, "ffpm-132", "assets://ffpm-132.txt"),
DrawerItem(83, "ffpm-133", "assets://ffpm-133.txt"),
DrawerItem(84, "ffpm-134", "assets://ffpm-134.txt"),
DrawerItem(85, "ffpm-136", "assets://ffpm-136.txt"),
DrawerItem(86, "ffpm-137", "assets://ffpm-137.txt"),
DrawerItem(87, "ffpm-140-1", "assets://ffpm-140-1.txt"),
DrawerItem(88, "ffpm-140-2", "assets://ffpm-140-2.txt"),
DrawerItem(89, "ffpm-141", "assets://ffpm-141.txt"),
DrawerItem(90, "ffpm-14", "assets://ffpm-14.txt"),
DrawerItem(91, "ffpm-153", "assets://ffpm-153.txt"),
DrawerItem(92, "ffpm-154", "assets://ffpm-154.txt"),
DrawerItem(93, "ffpm-155", "assets://ffpm-155.txt"),
DrawerItem(94, "ffpm-157", "assets://ffpm-157.txt"),
DrawerItem(95, "ffpm-160", "assets://ffpm-160.txt"),
DrawerItem(96, "ffpm-161", "assets://ffpm-161.txt"),
DrawerItem(97, "ffpm-163", "assets://ffpm-163.txt"),
DrawerItem(98, "ffpm-164", "assets://ffpm-164.txt"),
DrawerItem(99, "ffpm-165", "assets://ffpm-165.txt"),
DrawerItem(100, "ffpm-166-new", "assets://ffpm-166-new.txt"),
DrawerItem(101, "ffpm-166", "assets://ffpm-166.txt"),
DrawerItem(102, "ffpm-16", "assets://ffpm-16.txt"),
DrawerItem(103, "ffpm-170", "assets://ffpm-170.txt"),
DrawerItem(104, "ffpm-172-1", "assets://ffpm-172-1.txt"),
DrawerItem(105, "ffpm-172-2", "assets://ffpm-172-2.txt"),
DrawerItem(106, "ffpm-175", "assets://ffpm-175.txt"),
DrawerItem(107, "ffpm-178", "assets://ffpm-178.txt"),
DrawerItem(108, "ffpm-179", "assets://ffpm-179.txt"),
DrawerItem(109, "ffpm-17", "assets://ffpm-17.txt"),
DrawerItem(110, "ffpm-180", "assets://ffpm-180.txt"),
DrawerItem(111, "ffpm-189", "assets://ffpm-189.txt"),
DrawerItem(112, "ffpm-190", "assets://ffpm-190.txt"),
DrawerItem(113, "ffpm-191", "assets://ffpm-191.txt"),
DrawerItem(114, "ffpm-192", "assets://ffpm-192.txt"),
DrawerItem(115, "ffpm-194", "assets://ffpm-194.txt"),
DrawerItem(116, "ffpm-195-1", "assets://ffpm-195-1.txt"),
DrawerItem(117, "ffpm-195-2", "assets://ffpm-195-2.txt"),
DrawerItem(118, "ffpm-198", "assets://ffpm-198.txt"),
DrawerItem(119, "ffpm-199", "assets://ffpm-199.txt"),
DrawerItem(120, "ffpm-19", "assets://ffpm-19.txt"),
DrawerItem(121, "ffpm-202", "assets://ffpm-202.txt"),
DrawerItem(122, "ffpm-203", "assets://ffpm-203.txt"),
DrawerItem(123, "ffpm-204", "assets://ffpm-204.txt"),
DrawerItem(124, "ffpm-209", "assets://ffpm-209.txt"),
DrawerItem(125, "ffpm-20", "assets://ffpm-20.txt"),
DrawerItem(126, "ffpm-210", "assets://ffpm-210.txt"),
DrawerItem(127, "ffpm-211", "assets://ffpm-211.txt"),
DrawerItem(128, "ffpm-212", "assets://ffpm-212.txt"),
DrawerItem(129, "ffpm-213", "assets://ffpm-213.txt"),
DrawerItem(130, "ffpm-214", "assets://ffpm-214.txt"),
DrawerItem(131, "ffpm-21", "assets://ffpm-21.txt"),
DrawerItem(132, "ffpm-220", "assets://ffpm-220.txt"),
DrawerItem(133, "ffpm-221", "assets://ffpm-221.txt"),
DrawerItem(134, "ffpm-222", "assets://ffpm-222.txt"),
DrawerItem(135, "ffpm-223", "assets://ffpm-223.txt"),
DrawerItem(136, "ffpm-224", "assets://ffpm-224.txt"),
DrawerItem(137, "ffpm-225", "assets://ffpm-225.txt"),
DrawerItem(138, "ffpm-229", "assets://ffpm-229.txt"),
DrawerItem(139, "ffpm-232", "assets://ffpm-232.txt"),
DrawerItem(140, "ffpm-233", "assets://ffpm-233.txt"),
DrawerItem(141, "ffpm-236", "assets://ffpm-236.txt"),
DrawerItem(142, "ffpm-237", "assets://ffpm-237.txt"),
DrawerItem(143, "ffpm-238-1", "assets://ffpm-238-1.txt"),
DrawerItem(144, "ffpm-238-2", "assets://ffpm-238-2.txt"),
DrawerItem(145, "ffpm-239-1", "assets://ffpm-239-1.txt"),
DrawerItem(146, "ffpm-239-2", "assets://ffpm-239-2.txt"),
DrawerItem(147, "ffpm-249", "assets://ffpm-249.txt"),
DrawerItem(148, "ffpm-250-1", "assets://ffpm-250-1.txt"),
DrawerItem(149, "ffpm-250-2", "assets://ffpm-250-2.txt"),
DrawerItem(150, "ffpm-251", "assets://ffpm-251.txt"),
DrawerItem(151, "ffpm-253", "assets://ffpm-253.txt"),
DrawerItem(152, "ffpm-254", "assets://ffpm-254.txt"),
DrawerItem(153, "ffpm-255", "assets://ffpm-255.txt"),
DrawerItem(154, "ffpm-256", "assets://ffpm-256.txt"),
DrawerItem(155, "ffpm-257-1", "assets://ffpm-257-1.txt"),
DrawerItem(156, "ffpm-257-2", "assets://ffpm-257-2.txt"),
DrawerItem(157, "ffpm-259", "assets://ffpm-259.txt"),
DrawerItem(158, "ffpm-260", "assets://ffpm-260.txt"),
DrawerItem(159, "ffpm-263", "assets://ffpm-263.txt"),
DrawerItem(160, "ffpm-269", "assets://ffpm-269.txt"),
DrawerItem(161, "ffpm-270", "assets://ffpm-270.txt"),
DrawerItem(162, "ffpm-271", "assets://ffpm-271.txt"),
DrawerItem(163, "ffpm-272", "assets://ffpm-272.txt"),
DrawerItem(164, "ffpm-273", "assets://ffpm-273.txt"),
DrawerItem(165, "ffpm-274", "assets://ffpm-274.txt"),
DrawerItem(166, "ffpm-276", "assets://ffpm-276.txt"),
DrawerItem(167, "ffpm-279", "assets://ffpm-279.txt"),
DrawerItem(168, "ffpm-281", "assets://ffpm-281.txt"),
DrawerItem(169, "ffpm-297", "assets://ffpm-297.txt"),
DrawerItem(170, "ffpm-307", "assets://ffpm-307.txt"),
DrawerItem(171, "ffpm-310", "assets://ffpm-310.txt"),
DrawerItem(172, "ffpm-311", "assets://ffpm-311.txt"),
DrawerItem(173, "ffpm-315", "assets://ffpm-315.txt"),
DrawerItem(174, "ffpm-321", "assets://ffpm-321.txt"),
DrawerItem(175, "ffpm-32", "assets://ffpm-32.txt"),
DrawerItem(176, "ffpm-332", "assets://ffpm-332.txt"),
DrawerItem(177, "ffpm-33", "assets://ffpm-33.txt"),
DrawerItem(178, "ffpm-352", "assets://ffpm-352.txt"),
DrawerItem(179, "ffpm-353-1", "assets://ffpm-353-1.txt"),
DrawerItem(180, "ffpm-353-2", "assets://ffpm-353-2.txt"),
DrawerItem(181, "ffpm-357", "assets://ffpm-357.txt"),
DrawerItem(182, "ffpm-358", "assets://ffpm-358.txt"),
DrawerItem(183, "ffpm-35", "assets://ffpm-35.txt"),
DrawerItem(184, "ffpm-367", "assets://ffpm-367.txt"),
DrawerItem(185, "ffpm-381-1", "assets://ffpm-381-1.txt"),
DrawerItem(186, "ffpm-381-2", "assets://ffpm-381-2.txt"),
DrawerItem(187, "ffpm-38", "assets://ffpm-38.txt"),
DrawerItem(188, "ffpm-392", "assets://ffpm-392.txt"),
DrawerItem(189, "ffpm-408-1", "assets://ffpm-408-1.txt"),
DrawerItem(190, "ffpm-408-2", "assets://ffpm-408-2.txt"),
DrawerItem(191, "ffpm-413", "assets://ffpm-413.txt"),
DrawerItem(192, "ffpm-4-1", "assets://ffpm-4-1.txt"),
DrawerItem(193, "ffpm-42-1", "assets://ffpm-42-1.txt"),
DrawerItem(194, "ffpm-42-2", "assets://ffpm-42-2.txt"),
DrawerItem(195, "ffpm-428", "assets://ffpm-428.txt"),
DrawerItem(196, "ffpm-4-2", "assets://ffpm-4-2.txt"),
DrawerItem(197, "ffpm-43", "assets://ffpm-43.txt"),
DrawerItem(198, "ffpm-440-1", "assets://ffpm-440-1.txt"),
DrawerItem(199, "ffpm-444-1", "assets://ffpm-444-1.txt"),
DrawerItem(200, "ffpm-444-2", "assets://ffpm-444-2.txt"),
DrawerItem(201, "ffpm-449", "assets://ffpm-449.txt"),
DrawerItem(202, "ffpm-44", "assets://ffpm-44.txt"),
DrawerItem(203, "ffpm-46", "assets://ffpm-46.txt"),
DrawerItem(204, "ffpm-47", "assets://ffpm-47.txt"),
DrawerItem(205, "ffpm-483", "assets://ffpm-483.txt"),
DrawerItem(206, "ffpm-489", "assets://ffpm-489.txt"),
DrawerItem(207, "ffpm-490", "assets://ffpm-490.txt"),
DrawerItem(208, "ffpm-49-1", "assets://ffpm-49-1.txt"),
DrawerItem(209, "ffpm-49-2", "assets://ffpm-49-2.txt"),
DrawerItem(210, "ffpm-501", "assets://ffpm-501.txt"),
DrawerItem(211, "ffpm-503", "assets://ffpm-503.txt"),
DrawerItem(212, "ffpm-5-1", "assets://ffpm-5-1.txt"),
DrawerItem(213, "ffpm-51", "assets://ffpm-51.txt"),
DrawerItem(214, "ffpm-5-2", "assets://ffpm-5-2.txt"),
DrawerItem(215, "ffpm-52", "assets://ffpm-52.txt"),
DrawerItem(216, "ffpm-539", "assets://ffpm-539.txt"),
DrawerItem(217, "ffpm-546", "assets://ffpm-546.txt"),
DrawerItem(218, "ffpm-549", "assets://ffpm-549.txt"),
DrawerItem(219, "ffpm-54", "assets://ffpm-54.txt"),
DrawerItem(220, "ffpm-558", "assets://ffpm-558.txt"),
DrawerItem(221, "ffpm-55", "assets://ffpm-55.txt"),
DrawerItem(222, "ffpm-563", "assets://ffpm-563.txt"),
DrawerItem(223, "ffpm-564", "assets://ffpm-564.txt"),
DrawerItem(224, "ffpm-56-new", "assets://ffpm-56-new.txt"),
DrawerItem(225, "ffpm-56", "assets://ffpm-56.txt"),
DrawerItem(226, "ffpm-57-new", "assets://ffpm-57-new.txt"),
DrawerItem(227, "ffpm-57", "assets://ffpm-57.txt"),
DrawerItem(228, "ffpm-58", "assets://ffpm-58.txt"),
DrawerItem(229, "ffpm-603-1", "assets://ffpm-603-1.txt"),
DrawerItem(230, "ffpm-603-2", "assets://ffpm-603-2.txt"),
DrawerItem(231, "ffpm-610", "assets://ffpm-610.txt"),
DrawerItem(232, "ffpm-611-1", "assets://ffpm-611-1.txt"),
DrawerItem(233, "ffpm-611-2", "assets://ffpm-611-2.txt"),
DrawerItem(234, "ffpm-616", "assets://ffpm-616.txt"),
DrawerItem(235, "ffpm-61", "assets://ffpm-61.txt"),
DrawerItem(236, "ffpm-626", "assets://ffpm-626.txt"),
DrawerItem(237, "ffpm-636", "assets://ffpm-636.txt"),
DrawerItem(238, "ffpm-640", "assets://ffpm-640.txt"),
DrawerItem(239, "ffpm-642-1", "assets://ffpm-642-1.txt"),
DrawerItem(240, "ffpm-642-2", "assets://ffpm-642-2.txt"),
DrawerItem(241, "ffpm-653", "assets://ffpm-653.txt"),
DrawerItem(242, "ffpm-674", "assets://ffpm-674.txt"),
DrawerItem(243, "ffpm-69", "assets://ffpm-69.txt"),
DrawerItem(244, "ffpm-6", "assets://ffpm-6.txt"),
DrawerItem(245, "ffpm-705-2", "assets://ffpm-705-2.txt"),
DrawerItem(246, "ffpm-71", "assets://ffpm-71.txt"),
DrawerItem(247, "ffpm-725", "assets://ffpm-725.txt"),
DrawerItem(248, "ffpm-726-1", "assets://ffpm-726-1.txt"),
DrawerItem(249, "ffpm-726-2", "assets://ffpm-726-2.txt"),
DrawerItem(250, "ffpm-729", "assets://ffpm-729.txt"),
DrawerItem(251, "ffpm-72", "assets://ffpm-72.txt"),
DrawerItem(252, "ffpm-733", "assets://ffpm-733.txt"),
DrawerItem(253, "ffpm-734", "assets://ffpm-734.txt"),
DrawerItem(254, "ffpm-735", "assets://ffpm-735.txt"),
DrawerItem(255, "ffpm-737", "assets://ffpm-737.txt"),
DrawerItem(256, "ffpm-750", "assets://ffpm-750.txt"),
DrawerItem(257, "ffpm-75", "assets://ffpm-75.txt"),
DrawerItem(258, "ffpm-760", "assets://ffpm-760.txt"),
DrawerItem(259, "ffpm-79-1", "assets://ffpm-79-1.txt"),
DrawerItem(260, "ffpm-79-2", "assets://ffpm-79-2.txt"),
DrawerItem(261, "ffpm-796", "assets://ffpm-796.txt"),
DrawerItem(262, "ffpm-799", "assets://ffpm-799.txt"),
DrawerItem(263, "ffpm-7", "assets://ffpm-7.txt"),
DrawerItem(264, "ffpm-82-1", "assets://ffpm-82-1.txt"),
DrawerItem(265, "ffpm-82-2", "assets://ffpm-82-2.txt"),
DrawerItem(266, "ffpm-825", "assets://ffpm-825.txt"),
DrawerItem(267, "ffpm-87", "assets://ffpm-87.txt"),
DrawerItem(268, "ffpm-89", "assets://ffpm-89.txt"),
DrawerItem(269, "ffpm-8", "assets://ffpm-8.txt"),
DrawerItem(270, "ffpm-91", "assets://ffpm-91.txt"),
DrawerItem(271, "ffpm-93", "assets://ffpm-93.txt"),
DrawerItem(272, "ffpm-94", "assets://ffpm-94.txt"),
DrawerItem(273, "ffpm-97-1", "assets://ffpm-97-1.txt"),
DrawerItem(274, "ffpm-97-2", "assets://ffpm-97-2.txt"),
DrawerItem(275, "ffpm-9", "assets://ffpm-9.txt")
)
}

View file

@ -0,0 +1,71 @@
package mg.dot.feufaro.midi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mg.dot.feufaro.FileRepository
import javax.sound.midi.*
class `MidiWriterKotlin-1` (private val fileRepository: FileRepository) {
private val sequence = Sequence(Sequence.PPQ, 60)
private val track = sequence.createTrack()
private var tick: Long = 0
private var nextTick: MutableList<Int> = mutableListOf()
private val noteOn = ShortMessage()
private val noteOff = ShortMessage()
private val lastPitch : MutableList<Int> = mutableListOf()
private val useChord : Boolean = true
fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long) {
var channel: Int = voiceNumber - 1
if (useChord) {
channel = channel / 2
}
var note = note
if (voiceNumber == 3 || voiceNumber == 4) {
note -= 12
}
if (lastPitch.size > voiceNumber && lastPitch[voiceNumber] > 0) {
noteOff.setMessage(ShortMessage.NOTE_OFF, channel, lastPitch[voiceNumber], 0)
val n2 = noteOff.clone() as MidiMessage
track.add(MidiEvent(n2, tick))
}
var velocity = velocity
if (note <= 0) {
note = 40
velocity = 0
}
noteOn.setMessage(ShortMessage.NOTE_ON, channel, note, velocity)
val n1: MidiMessage = noteOn.clone() as MidiMessage
track.add(MidiEvent(n1, tick))
while(lastPitch.size <= voiceNumber) {
lastPitch.add(0)
}
lastPitch[voiceNumber] = note
}
fun save(filePath: String) {
val parseScope = CoroutineScope(Dispatchers.Default)
parseScope.launch {
val out = fileRepository.getOutputStream(filePath)
MidiSystem.write(sequence, 1, out)
out.close()
}
}
fun addMetaMessage(type: Int, tick: Int, nbData: Int, metaByteString: String) {
val byteArray = metaByteString.toByteArray()
val metaMessage = MetaMessage(type, byteArray, nbData)
track.add(MidiEvent(metaMessage, tick.toLong()))
}
fun process(pitches: List<MidiPitch>) {
val lastTick = 0
nextTick.clear()
// addMetaMessage(0x59, 4, 2, 2,0)
tick = 0
pitches.forEach {
if (it.metaType > 0) {
addMetaMessage(it.metaType, it.tick, it.metaByteSize, it.metaBytes)
} else if (it.pitch != "") {
addNote(it.voiceNumber, it.pitch.toInt(), 100, it.tick.toLong())
}
}
}
}

View file

@ -0,0 +1,72 @@
package mg.dot.feufaro.midi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mg.dot.feufaro.FileRepository
import javax.sound.midi.*
actual class MidiWriterKotlin actual constructor(private val fileRepository: FileRepository) {
private val sequence = Sequence(Sequence.PPQ, 60)
private val track = sequence.createTrack()
private var tick: Long = 0
//private var nextTick: MidiPitch<Int> = mutableListOf()
private var nextTick: MutableList<MidiPitch> = mutableListOf()
private val noteOn = ShortMessage()
private val noteOff = ShortMessage()
private val lastPitch : MutableList<Int> = mutableListOf()
private val useChord : Boolean = true
actual fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long) {
var channel: Int = voiceNumber - 1
if (useChord) {
channel = channel / 2
}
var note = note
if (voiceNumber == 3 || voiceNumber == 4) {
note -= 12
}
if (lastPitch.size > voiceNumber && lastPitch[voiceNumber] > 0) {
noteOff.setMessage(ShortMessage.NOTE_OFF, channel, lastPitch[voiceNumber], 0)
val n2 = noteOff.clone() as MidiMessage
track.add(MidiEvent(n2, tick))
}
var velocity = velocity
if (note <= 0) {
note = 40
velocity = 0
}
noteOn.setMessage(ShortMessage.NOTE_ON, channel, note, velocity)
val n1: MidiMessage = noteOn.clone() as MidiMessage
track.add(MidiEvent(n1, tick))
while(lastPitch.size <= voiceNumber) {
lastPitch.add(0)
}
lastPitch[voiceNumber] = note
}
actual fun save(filePath: String) {
val parseScope = CoroutineScope(Dispatchers.Default)
parseScope.launch {
val out = fileRepository.getOutputStream(filePath)
MidiSystem.write(sequence, 1, out)
out.close()
}
}
actual fun addMetaMessage(type: Int, tick: Int, nbData: Int, metaByteString: String) {
val byteArray = metaByteString.toByteArray()
val metaMessage = MetaMessage(type, byteArray, nbData)
track.add(MidiEvent(metaMessage, tick.toLong()))
}
actual fun process(pitches: List<MidiPitch>) {
val lastTick = 0
nextTick.clear()
// addMetaMessage(0x59, 4, 2, 2,0)
tick = 0
pitches.forEach {
if (it.metaType > 0) {
addMetaMessage(it.metaType, it.tick, it.metaByteSize, it.metaBytes)
} else if (it.pitch != "") {
addNote(it.voiceNumber, it.pitch.toInt(), 100, it.tick.toLong())
}
}
}
}

View file

@ -0,0 +1,22 @@
package mg.dot.feufaro.ui
import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap
import com.google.zxing.BarcodeFormat
import com.google.zxing.client.j2se.MatrixToImageWriter
import com.google.zxing.qrcode.QRCodeWriter
actual fun generateQRCode(content: String, size: Int): ImageBitmap? {
return try {
val writer = QRCodeWriter()
val bitMatrix = writer.encode(content, BarcodeFormat.QR_CODE, size, size)
val image = MatrixToImageWriter.toBufferedImage(bitMatrix)
image.toComposeImageBitmap()
} catch (e: Exception) {
println("Erreur QR Code Desktop: ${e.message}")
null
}
}

View file

@ -0,0 +1,27 @@
package mg.dot.feufaro.ui
import androidx.compose.foundation.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListState
@Composable
actual fun ScrollableDrawerContent(
modifier: Modifier,
lazyListState: LazyListState,
content: @Composable () -> Unit
) {
val scrollState = rememberScrollState()
Box(modifier = modifier) {
content()
VerticalScrollbar(
modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight().width(8.dp),
adapter = rememberScrollbarAdapter(lazyListState)
)
}
}