diff --git a/src/jsMain/kotlin/process/phonemes/PhonemesMapping.kt b/src/jsMain/kotlin/process/phonemes/PhonemesMapping.kt new file mode 100644 index 00000000..83ca5e9b --- /dev/null +++ b/src/jsMain/kotlin/process/phonemes/PhonemesMapping.kt @@ -0,0 +1,66 @@ +package process.phonemes + +import kotlinx.serialization.Serializable +import kotlinx.serialization.Transient +import model.Note +import model.Project +import model.Track +import process.validateNotes + +@Serializable +data class PhonemesMappingRequest( + val mapText: String = "", +) { + val isValid get() = map.isNotEmpty() + + @Transient + val map = mapText.lines().mapNotNull { line -> + if (line.contains("=").not()) return@mapNotNull null + val from = line.substringBefore("=").trim() + val to = line.substringAfter("=").trim() + from to to + }.sortedByDescending { it.first.split(" ").size } + + companion object { + + fun findPreset(name: String) = Presets.find { it.first == name }?.second + + fun getPreset(name: String) = requireNotNull(findPreset(name)) + + val Presets: List> by lazy { + listOf() + } + } +} + +fun Project.mapPhonemes(request: PhonemesMappingRequest) = copy( + tracks = tracks.map { it.replacePhonemes(request) }, +) + +fun Track.replacePhonemes(request: PhonemesMappingRequest) = copy( + notes = notes.mapNotNull { note -> note.replacePhonemes(request).takeIf { it.lyric.isNotEmpty() } } + .validateNotes(), +) + +fun Note.replacePhonemes(request: PhonemesMappingRequest): Note { + val input = phoneme?.split(" ") ?: return this + val output = mutableListOf() + var pos = 0 + while (pos <= input.lastIndex) { + val restInput = input.drop(pos).joinToString(" ") + var matched = false + for ((key, value) in request.map) { + if (restInput.startsWith(key)) { + output += value.split(" ") + pos += key.split(" ").size + matched = true + break + } + } + if (!matched) { + output += input[pos] + pos++ + } + } + return copy(phoneme = output.joinToString(" ")) +} diff --git a/src/jsTest/kotlin/process/phonemes/PhonemesMappingTest.kt b/src/jsTest/kotlin/process/phonemes/PhonemesMappingTest.kt new file mode 100644 index 00000000..159be377 --- /dev/null +++ b/src/jsTest/kotlin/process/phonemes/PhonemesMappingTest.kt @@ -0,0 +1,63 @@ +package process.phonemes + +import model.Note +import kotlin.test.Test +import kotlin.test.assertEquals + +class PhonemesMappingTest { + + private val request = PhonemesMappingRequest( + mapText = """ + a=A + b=B + c a=C' A + c=C + d c a=DC' A + d c=DC + """.trimIndent(), + ) + + private fun createNote(phoneme: String) = Note( + id = 0, + key = 60, + lyric = "", + tickOn = 0L, + tickOff = 480L, + phoneme = phoneme, + ) + + @Test + fun testNoMatch() { + val note = createNote("l e") + val actual = note.replacePhonemes(request).phoneme + assertEquals("l e", actual) + } + + @Test + fun testSingleMatch() { + val note = createNote("b") + val actual = note.replacePhonemes(request).phoneme + assertEquals("B", actual) + } + + @Test + fun testSingleInMultipleMatch() { + val note = createNote("l a m b n") + val actual = note.replacePhonemes(request).phoneme + assertEquals("l A m B n", actual) + } + + @Test + fun testMultipleMatch() { + val note = createNote("c a") + val actual = note.replacePhonemes(request).phoneme + assertEquals("C' A", actual) + } + + @Test + fun testMultipleInMultipleMatch() { + val note = createNote("d c a m d c") + val actual = note.replacePhonemes(request).phoneme + assertEquals("DC' A m DC", actual) + } +}