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 { kotlin {
linuxX64 {
binaries {
executable {
}
}
}
androidTarget { androidTarget {
@OptIn(ExperimentalKotlinGradlePluginApi::class) @OptIn(ExperimentalKotlinGradlePluginApi::class)
compilerOptions { compilerOptions {
@ -21,7 +28,20 @@ kotlin {
} }
jvm("desktop") jvm("desktop")
afterEvaluate {
afterEvaluate {
tasks.configureEach {
if (
name.startsWith("compile")
&& name.endsWith("KotlinMetadata")
) {
println("disabling $name")
enabled = false
}
}
}
}
sourceSets { sourceSets {
val desktopMain by getting val desktopMain by getting
@ -30,7 +50,11 @@ kotlin {
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.koin.android) // Koin Android-specific extensions implementation(libs.koin.android) // Koin Android-specific extensions
implementation(libs.koin.androidx.compose) 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 { commonMain.dependencies {
implementation(compose.components.resources) implementation(compose.components.resources)
@ -57,6 +81,9 @@ kotlin {
implementation(libs.cafe.voyager.koin) implementation(libs.cafe.voyager.koin)
implementation(libs.androidx.material.icons.extended) implementation(libs.androidx.material.icons.extended)
implementation(kotlin("stdlib-jdk8")) implementation(kotlin("stdlib-jdk8"))
implementation(compose.desktop.currentOs)
//implementation("org.jetbrains.compose.foundation:foundation-desktop")
//implementation(libs.ktmidi) //implementation(libs.ktmidi)
} }
commonTest.dependencies { commonTest.dependencies {
@ -65,6 +92,8 @@ kotlin {
desktopMain.dependencies { desktopMain.dependencies {
implementation(compose.desktop.currentOs) implementation(compose.desktop.currentOs)
implementation(libs.kotlinx.coroutinesSwing) 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.unit.dp
import androidx.compose.ui.window.Popup import androidx.compose.ui.window.Popup
import mg.dot.feufaro.solfa.LazyVerticalGridTUO import mg.dot.feufaro.solfa.LazyVerticalGridTUO
import mg.dot.feufaro.ui.MainScreenWithDrawer
import kotlin.math.roundToInt import kotlin.math.roundToInt
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.koinScreenModel import cafe.adriel.voyager.koin.koinScreenModel
@ -61,82 +62,105 @@ object ScreenSolfa : Screen {
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
Box( var isScanning by remember { mutableStateOf(false) }
Modifier.fillMaxSize() var qrCodeResult by remember { mutableStateOf("Aucun code scanné") }
) {
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
solfaScreenModel.loadNextInPlaylist() /* if (isScanning) {
}, modifier = Modifier.height(40.dp)) { ScannerScreen(
val nextLabel: String by sharedScreenModel.nextLabel.collectAsState() onScanComplete = { result ->
Text(nextLabel) 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( launchFilePicker(
mimeTypes = arrayOf("text/plain"), mimeTypes = arrayOf("text/plain"),
onFileSelected = { path -> onFileSelected = { path ->
@ -147,48 +171,49 @@ object ScreenSolfa : Screen {
} }
} }
)*/ )*/
solfaScreenModel.loadCustomFile() solfaScreenModel.loadCustomFile()
}) { }) {
val loadFile: String by sharedScreenModel.loadFile.collectAsState() val loadFile: String by sharedScreenModel.loadFile.collectAsState()
Text(loadFile) 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")
} }
} }
val songAuthor: String by sharedScreenModel.songAuthor.collectAsState() FlowRow(
val songComposer: String by sharedScreenModel.songComposer.collectAsState() modifier = Modifier.fillMaxWidth()
val songRhythm: String by sharedScreenModel.songRhythm.collectAsState() .windowInsetsPadding(WindowInsets.safeDrawing)
Text(text = songAuthor, fontStyle = FontStyle.Italic) .padding(start = 8.dp, end = 8.dp, top = 8.dp),
Text(text = songRhythm) horizontalArrangement = Arrangement.spacedBy(16.dp),
Text(text = songComposer) 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 package mg.dot.feufaro.midi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import mg.dot.feufaro.FileRepository import mg.dot.feufaro.FileRepository
import javax.sound.midi.*
class MidiWriterKotlin (private val fileRepository: FileRepository) { expect class MidiWriterKotlin(fileRepository: FileRepository) {
private val sequence = Sequence(Sequence.PPQ, 60) fun addNote( voiceNumber: Int, note: Int, velocity: Int, tick: Long)
private val track = sequence.createTrack() fun save(filePath: String)
private var tick: Long = 0 fun addMetaMessage(type: Int, tick: Int, nbData: Int, metaByteString: String)
private var nextTick: MutableList<Int> = mutableListOf() fun process(pitches: List<MidiPitch>)
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

@ -2,10 +2,12 @@ package mg.dot.feufaro.solfa
import SharedScreenModel import SharedScreenModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import mg.dot.feufaro.FileRepository import mg.dot.feufaro.FileRepository
import mg.dot.feufaro.getConfigDirectoryPath
import mg.dot.feufaro.launchFilePicker import mg.dot.feufaro.launchFilePicker
import mg.dot.feufaro.midi.MidiPitch import mg.dot.feufaro.midi.MidiPitch
import mg.dot.feufaro.midi.MidiWriterKotlin import mg.dot.feufaro.midi.MidiWriterKotlin
@ -144,20 +146,28 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
} }
fun loadFile() { fun loadFile() {
println("Load btn clicked") val screenModelScope= CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
launchFilePicker( screenModelScope.launch {
mimeTypes = arrayOf("text/plain"), val initialPath = fileRepository.readLastDirectoryPath()
onFileSelected = { path -> val homedir = getConfigDirectoryPath();
if (path != null) {
println("fichier $path"); launchFilePicker(
sharedScreenModel.reset() mimeTypes = arrayOf("text/plain"),
parse(path) onFileSelected = { path ->
//loadSolfa() if (path != null) {
} else { println("fichier $path");
println("Pas de dichier") sharedScreenModel.reset()
parse(path)
screenModelScope.launch {
fileRepository.saveLastDirectoryPath(path)
}
//loadSolfa()
} else {
println("Pas de dichier")
}
} }
} )
) }
} }
fun parse(sourceFile: String) { fun parse(sourceFile: String) {
@ -170,6 +180,8 @@ class Solfa(val sharedScreenModel: SharedScreenModel, private val fileRepository
println("Opening $sourceFile raised exception {${e.message}") println("Opening $sourceFile raised exception {${e.message}")
emptyList() emptyList()
} }
val contentString = fileRepository.readFileLines(sourceFile).joinToString("\n")
sharedScreenModel.setFileContent(contentString, sourceFile)
T.clear() T.clear()
N.clear() N.clear()
L.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 // 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 cafe.adriel.voyager.core.model.ScreenModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow 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.solfa.TimeUnitObject
import mg.dot.feufaro.data.getDrawerItems
class SharedScreenModel() : ScreenModel { class SharedScreenModel() : ScreenModel {
private val _nextLabel = MutableStateFlow<String>("Next ...") private val _nextLabel = MutableStateFlow<String>("Next ...")
@ -44,7 +53,44 @@ class SharedScreenModel() : ScreenModel {
var _hasMarker = MutableStateFlow<Boolean>(false) var _hasMarker = MutableStateFlow<Boolean>(false)
val hasMarker: StateFlow<Boolean> = _hasMarker.asStateFlow() 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 _nextLabel.value += otherData
} }

View file

@ -19,4 +19,7 @@ class SolfaScreenModel(
fun loadCustomFile() { fun loadCustomFile() {
solfa.loadFile() 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)
)
}
}