diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 7d9985f..5d0aceb 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -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") } } } diff --git a/composeApp/src/androidMain/kotlin/mg/dot/feufaro/data/DrawerItem.kt b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/data/DrawerItem.kt new file mode 100644 index 0000000..529acea --- /dev/null +++ b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/data/DrawerItem.kt @@ -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 { + return LISTE_FICHIERS_SOLFA.mapIndexed { index, fileName -> + val titleWithoutExtension = fileName.removeSuffix(".txt") + + DrawerItem( + id = index + 1, + title = titleWithoutExtension, + path = ASSET_PREFIX + fileName + ) + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin.kt b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin.kt new file mode 100644 index 0000000..b2ed4bb --- /dev/null +++ b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin.kt @@ -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() + 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) { + // TODO: Implémentation Android pour traiter les pitches + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/mg/dot/feufaro/ui/QRGenerator.kt b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/ui/QRGenerator.kt new file mode 100644 index 0000000..24db1ac --- /dev/null +++ b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/ui/QRGenerator.kt @@ -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 + } +} \ No newline at end of file diff --git a/composeApp/src/androidMain/kotlin/mg/dot/feufaro/ui/ScrollableDrawer.kt b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/ui/ScrollableDrawer.kt new file mode 100644 index 0000000..311243d --- /dev/null +++ b/composeApp/src/androidMain/kotlin/mg/dot/feufaro/ui/ScrollableDrawer.kt @@ -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() + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt index d9347dd..5525227 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ScreenSolfa.kt @@ -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) - ) {} } } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/data/DrawerItem.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/data/DrawerItem.kt new file mode 100644 index 0000000..1673539 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/data/DrawerItem.kt @@ -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 \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin.kt index 9db3982..cc32aad 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin.kt @@ -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 = mutableListOf() - private val noteOn = ShortMessage() - private val noteOff = ShortMessage() - private val lastPitch : MutableList = 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) { - 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) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt index 707ea8c..73c727f 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/solfa/Solfa.kt @@ -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() diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt new file mode 100644 index 0000000..fdf2787 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/DrawerUI.kt @@ -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, + 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") + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/QRDisplay.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/QRDisplay.kt new file mode 100644 index 0000000..83cfa8f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/QRDisplay.kt @@ -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)) + } + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/QRGenerator.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/QRGenerator.kt new file mode 100644 index 0000000..51f050f --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/QRGenerator.kt @@ -0,0 +1,5 @@ +package mg.dot.feufaro.ui + +import androidx.compose.ui.graphics.ImageBitmap + +expect fun generateQRCode(content: String, size: Int): ImageBitmap? \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/ScrollableDrawer.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/ScrollableDrawer.kt new file mode 100644 index 0000000..af835ac --- /dev/null +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/ui/ScrollableDrawer.kt @@ -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 +) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt index eda7205..76990a2 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SharedScreenModel.kt @@ -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("Next ...") @@ -44,7 +53,44 @@ class SharedScreenModel() : ScreenModel { var _hasMarker = MutableStateFlow(false) val hasMarker: StateFlow = _hasMarker.asStateFlow() - fun appendData(otherData: String) { + private val _searchTitle = MutableStateFlow("") + val searchTitle: StateFlow = _searchTitle.asStateFlow() + + @OptIn + val filteredSongs: StateFlow> = 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(null) + val fileContent: State = _fileContent + + private val _isQRCodeVisible = mutableStateOf(false) + val isQRCodeVisible: State = _isQRCodeVisible + + private val _activeFilePath = mutableStateOf("") + val activeFilePath: State = _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 } diff --git a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt index c08af74..37e2c4a 100644 --- a/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt +++ b/composeApp/src/commonMain/kotlin/mg/dot/feufaro/viewmodel/SolfaScreenModel.kt @@ -19,4 +19,7 @@ class SolfaScreenModel( fun loadCustomFile() { solfa.loadFile() } + fun loadFromFile(path: String) { + solfa.parse(path) + } } \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/data/DrawerItem.kt b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/data/DrawerItem.kt new file mode 100644 index 0000000..5871ff7 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/data/DrawerItem.kt @@ -0,0 +1,281 @@ +package mg.dot.feufaro.data + +actual fun getDrawerItems(): List { + 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") + ) +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin-1.kt b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin-1.kt new file mode 100644 index 0000000..bed41be --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin-1.kt @@ -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 = mutableListOf() + private val noteOn = ShortMessage() + private val noteOff = ShortMessage() + private val lastPitch : MutableList = 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) { + 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()) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin.kt b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin.kt new file mode 100644 index 0000000..0a7eac2 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/midi/MidiWriterKotlin.kt @@ -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 = mutableListOf() + private var nextTick: MutableList = mutableListOf() + private val noteOn = ShortMessage() + private val noteOff = ShortMessage() + private val lastPitch : MutableList = 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) { + 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()) + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/ui/QRGenerator.kt b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/ui/QRGenerator.kt new file mode 100644 index 0000000..54591e5 --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/ui/QRGenerator.kt @@ -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 + } +} \ No newline at end of file diff --git a/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/ui/ScrollableDrawer.kt b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/ui/ScrollableDrawer.kt new file mode 100644 index 0000000..ab0c6ea --- /dev/null +++ b/composeApp/src/desktopMain/kotlin/mg/dot/feufaro/ui/ScrollableDrawer.kt @@ -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) + ) + } +} \ No newline at end of file