From a944151e9c5fddfdf04a87516b82af28dba6ce85 Mon Sep 17 00:00:00 2001 From: sdercolin Date: Tue, 6 Jun 2023 22:21:27 +0900 Subject: [PATCH 1/5] Bump up version --- src/main/kotlin/Main.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index 71b33c6c..c9f87bfd 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -6,7 +6,7 @@ import ui.strings.Language import ui.strings.initializeI18n const val APP_NAME = "UtaFormatix" -const val APP_VERSION = "3.18.5" +const val APP_VERSION = "3.19" suspend fun main() { initializeI18n(Language.English) From 91721b01f73461f68afbff5d3c7102be84d0a62d Mon Sep 17 00:00:00 2001 From: sdercolin Date: Fri, 8 Sep 2023 23:07:49 +0900 Subject: [PATCH 2/5] Implement LyricsMappingRequest and vx-beta preset --- src/main/kotlin/external/Resources.kt | 3 + .../kotlin/process/lyrics/LyricsMapping.kt | 52 ++++ .../texts/vxbeta-japanese-mapping.txt | 222 ++++++++++++++++++ 3 files changed, 277 insertions(+) create mode 100644 src/main/kotlin/process/lyrics/LyricsMapping.kt create mode 100644 src/main/resources/texts/vxbeta-japanese-mapping.txt diff --git a/src/main/kotlin/external/Resources.kt b/src/main/kotlin/external/Resources.kt index 0b56a1bd..5f56e9b9 100644 --- a/src/main/kotlin/external/Resources.kt +++ b/src/main/kotlin/external/Resources.kt @@ -60,4 +60,7 @@ object Resources { val chineseLyricsDictionaryText: String get() = require("texts/mandarin-pinyin-dict.txt").default as String + + val lyricsMappingVxBetaJaText: String + get() = require("texts/vxbeta-japanese-mapping.txt").default as String } diff --git a/src/main/kotlin/process/lyrics/LyricsMapping.kt b/src/main/kotlin/process/lyrics/LyricsMapping.kt new file mode 100644 index 00000000..52213c0c --- /dev/null +++ b/src/main/kotlin/process/lyrics/LyricsMapping.kt @@ -0,0 +1,52 @@ +package process.lyrics + +import external.Resources +import kotlinx.serialization.Serializable +import model.Note +import model.Project +import model.Track +import process.validateNotes + +@Serializable +data class LyricsMappingRequest( + val mapLines: List, + val mapToPhonemes: Boolean, +) { + + val map = mapLines.mapNotNull { line -> + if (line.contains("=").not()) return@mapNotNull null + val from = line.substringBefore("=").trim() + val to = line.substringAfter("=").trim() + from to to + }.toMap() + + companion object { + + val Presets: List> by lazy { + listOf( + "VX-β 日本語かな -> 発音記号" to LyricsMappingRequest( + mapLines = Resources.lyricsMappingVxBetaJaText.lines(), + mapToPhonemes = false, + ), + ) + } + } +} + +fun Project.mapLyrics(request: LyricsMappingRequest) = copy( + tracks = tracks.map { it.replaceLyrics(request) }, +) + +fun Track.replaceLyrics(request: LyricsMappingRequest) = copy( + notes = notes.mapNotNull { note -> note.replaceLyrics(request).takeIf { it.lyric.isNotEmpty() } } + .validateNotes(), +) + +private fun Note.replaceLyrics(request: LyricsMappingRequest): Note { + val mappedValue = request.map[this.lyric] ?: this.lyric + return if (request.mapToPhonemes) { + this.copy(phoneme = mappedValue) + } else { + this.copy(lyric = mappedValue) + } +} diff --git a/src/main/resources/texts/vxbeta-japanese-mapping.txt b/src/main/resources/texts/vxbeta-japanese-mapping.txt new file mode 100644 index 00000000..2b82acea --- /dev/null +++ b/src/main/resources/texts/vxbeta-japanese-mapping.txt @@ -0,0 +1,222 @@ +あ=[a] +い=[i] +う=[u] +え=[e] +お=[o] + +ぁ=[a] +ぃ=[i] +ぅ=[u] +ぇ=[e] +ぉ=[o] + +ゐ=[i] +ゑ=[e] + +か=[k a] +き=[k i] +く=[k u] +け=[k e] +こ=[k o] + +きゃ=[ky a] +きゅ=[ky u] +きぇ=[ky e] +きょ=[ky o] + +くぁ=[k w a] +くぃ=[k w i] +くぅ=[k w u] +くぇ=[k w e] +くぉ=[k w o] + +が=[g a] +ぎ=[g i] +ぐ=[g u] +げ=[g e] +ご=[g o] + +ぎゃ=[gy a] +ぎゅ=[gy u] +ぎぇ=[gy e] +ぎょ=[gy o] + +ぐぁ=[g w a] +ぐぃ=[g w i] +ぐぅ=[g w u] +ぐぇ=[g w e] +ぐぉ=[g w o] + +さ=[s a] +し=[sh i] +す=[s u] +せ=[s e] +そ=[s o] + +ざ=[z a] +じ=[j i] +ず=[z u] +ぜ=[z e] +ぞ=[z o] + +しゃ=[sh a] +しゅ=[sh u] +しぇ=[sh e] +しょ=[sh o] + +すぃ=[s i] + +じゃ=[j a] +じゅ=[j u] +じぇ=[j e] +じょ=[j o] + +ずぃ=[z i] + +た=[t a] +ち=[ch i] +つ=[ts u] +て=[t e] +と=[t o] + +ちゃ=[ch a] +ちゅ=[ch u] +ちぇ=[ch e] +ちょ=[ch o] + +つぁ=[ts a] +つぃ=[ts i] +つぇ=[ts e] +つぉ=[ts o] + +てゃ=[ty a] +てぃ=[t i] +てゅ=[ty u] +てぇ=[ty e] +てょ=[ty o] + +とぅ=[t u] + +だ=[d a] +ぢ=[j i] +づ=[z u] +で=[d e] +ど=[d o] + +ぢゃ=[j a] +ぢゅ=[j u] +ぢぇ=[j e] +ぢょ=[j o] + +でゃ=[dy a] +でぃ=[d i] +でゅ=[dy u] +でぇ=[dy e] +でょ=[dy o] + +どぅ=[d u] + +な=[n a] +に=[n i] +ぬ=[n u] +ね=[n e] +の=[n o] + +にゃ=[ny a] +にゅ=[ny u] +にぇ=[ny e] +にょ=[ny o] + +は=[h a] +ひ=[h i] +ふ=[f u] +へ=[h e] +ほ=[h o] + +ひゃ=[hy a] +ひゅ=[hy u] +ひぇ=[hy e] +ひょ=[hy o] + +ふぁ=[f a] +ふぃ=[f i] +ふぇ=[f e] +ふぉ=[f o] + +ふゃ=[f y a] +ふゅ=[f y i] + +ば=[b a] +び=[b i] +ぶ=[b u] +べ=[b e] +ぼ=[b o] + +びゃ=[by a] +びゅ=[by u] +びぇ=[by e] +びょ=[by o] + +ぶぁ=[b w a] +ぶぃ=[b w i] +ぶぇ=[b w e] +ぶぉ=[b w o] + +ヴぁ=[v a] +ヴぃ=[v i] +ヴ=[v u] +ヴぇ=[v e] +ヴぉ=[v o] + +ヴゃ=[v y a] +ヴゅ=[v y i] + +ぱ=[p a] +ぴ=[p i] +ぷ=[p u] +ぺ=[p e] +ぽ=[p o] + +ぴゃ=[py a] +ぴゅ=[py u] +ぴぇ=[py e] +ぴょ=[py o] + +ぷぁ=[p w a] +ぷぃ=[p w i] +ぷぇ=[p w e] +ぷぉ=[p w o] + +ま=[m a] +み=[m i] +む=[m u] +め=[m e] +も=[m o] + +みゃ=[my a] +みゅ=[my u] +みぇ=[my e] +みょ=[my o] + +や=[y a] +ゆ=[y u] +よ=[y o] + +ら=[r a] +り=[r i] +る=[r u] +れ=[r e] +ろ=[r o] + +りゃ=[ry a] +りゅ=[ry u] +りぇ=[ry e] +りょ=[ry o] + +わ=[w a] +を=[w o] +ん=[N] + +っ=[cl] +ー=[-] +息=[br] From ae51606154d6a30fc1aafcd833dc20a5c2f8dddf Mon Sep 17 00:00:00 2001 From: sdercolin Date: Sat, 9 Sep 2023 01:19:58 +0900 Subject: [PATCH 3/5] Implement LyricsMapping configuration --- .../kotlin/process/lyrics/LyricsMapping.kt | 13 +- src/main/kotlin/ui/ConfigurationEditor.kt | 41 +++- ...yricsConvertion.kt => LyricsConversion.kt} | 0 .../kotlin/ui/configuration/LyricsMapping.kt | 200 ++++++++++++++++++ src/main/kotlin/ui/strings/Strings.kt | 42 ++++ 5 files changed, 291 insertions(+), 5 deletions(-) rename src/main/kotlin/ui/configuration/{LyricsConvertion.kt => LyricsConversion.kt} (100%) create mode 100644 src/main/kotlin/ui/configuration/LyricsMapping.kt diff --git a/src/main/kotlin/process/lyrics/LyricsMapping.kt b/src/main/kotlin/process/lyrics/LyricsMapping.kt index 52213c0c..7520c1bf 100644 --- a/src/main/kotlin/process/lyrics/LyricsMapping.kt +++ b/src/main/kotlin/process/lyrics/LyricsMapping.kt @@ -9,11 +9,12 @@ import process.validateNotes @Serializable data class LyricsMappingRequest( - val mapLines: List, - val mapToPhonemes: Boolean, + val mapText: String = "", + val mapToPhonemes: Boolean = false, ) { + val isValid get() = map.isNotEmpty() - val map = mapLines.mapNotNull { line -> + val map = mapText.lines().mapNotNull { line -> if (line.contains("=").not()) return@mapNotNull null val from = line.substringBefore("=").trim() val to = line.substringAfter("=").trim() @@ -22,10 +23,14 @@ data class LyricsMappingRequest( 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( "VX-β 日本語かな -> 発音記号" to LyricsMappingRequest( - mapLines = Resources.lyricsMappingVxBetaJaText.lines(), + mapText = Resources.lyricsMappingVxBetaJaText, mapToPhonemes = false, ), ) diff --git a/src/main/kotlin/ui/ConfigurationEditor.kt b/src/main/kotlin/ui/ConfigurationEditor.kt index 0a7afcb9..8e3c8d4a 100644 --- a/src/main/kotlin/ui/ConfigurationEditor.kt +++ b/src/main/kotlin/ui/ConfigurationEditor.kt @@ -23,9 +23,11 @@ import org.w3c.dom.get import process.RESTS_FILLING_MAX_LENGTH_DENOMINATOR_DEFAULT import process.evalFractionOrNull import process.fillRests +import process.lyrics.LyricsMappingRequest import process.lyrics.LyricsReplacementRequest import process.lyrics.chinese.convertChineseLyricsToPinyin import process.lyrics.japanese.convertJapaneseLyrics +import process.lyrics.mapLyrics import process.lyrics.replaceLyrics import process.projectZoomFactorOptions import process.zoom @@ -43,6 +45,7 @@ import ui.common.scopedFC import ui.common.title import ui.configuration.ChinesePinyinConversionBlock import ui.configuration.JapaneseLyricsConversionBlock +import ui.configuration.LyricsMappingBlock import ui.configuration.LyricsReplacementBlock import ui.configuration.PitchConversionBlock import ui.configuration.ProjectZoomBlock @@ -101,6 +104,16 @@ val ConfigurationEditor = scopedFC { props, scope -> LyricsReplacementState(true, preset) } } + val (lyricsMapping, setLyricsMapping) = useState { + getStateFromLocalStorage("lyricsMapping")?.let { + return@useState it + } + LyricsMappingState( + isOn = false, + presetName = null, + request = LyricsMappingRequest(), + ) + } val (slightRestsFilling, setSlightRestsFilling) = useState { getStateFromLocalStorage("slightRestsFilling")?.let { return@useState it @@ -134,7 +147,7 @@ val ConfigurationEditor = scopedFC { props, scope -> } var dialogError by useState(DialogErrorState()) - fun isReady() = japaneseLyricsConversion.isReady && lyricsReplacement.isReady + fun isReady() = japaneseLyricsConversion.isReady && lyricsReplacement.isReady && lyricsMapping.isReady fun closeErrorDialog() { dialogError = dialogError.copy(isShowing = false) @@ -155,6 +168,10 @@ val ConfigurationEditor = scopedFC { props, scope -> initialState = lyricsReplacement submitState = setLyricsReplacement } + LyricsMappingBlock { + initialState = lyricsMapping + submitState = setLyricsMapping + } SlightRestsFillingBlock { initialState = slightRestsFilling submitState = setSlightRestsFilling @@ -175,6 +192,7 @@ val ConfigurationEditor = scopedFC { props, scope -> japaneseLyricsConversion, chinesePinyinConversion, lyricsReplacement, + lyricsMapping, slightRestsFilling, pitchConversion, projectZoom, @@ -208,6 +226,7 @@ private fun ChildrenBuilder.buildNextButton( japaneseLyricsConversion: JapaneseLyricsConversionState, chinesePinyinConversion: ChinesePinyinConversionState, lyricsReplacement: LyricsReplacementState, + lyricsMapping: LyricsMappingState, slightRestsFilling: SlightRestsFillingState, pitchConversion: PitchConversionState, projectZoom: ProjectZoomState, @@ -229,6 +248,7 @@ private fun ChildrenBuilder.buildNextButton( japaneseLyricsConversion, chinesePinyinConversion, lyricsReplacement, + lyricsMapping, slightRestsFilling, pitchConversion, projectZoom, @@ -247,6 +267,7 @@ private fun process( japaneseLyricsConversion: JapaneseLyricsConversionState, chinesePinyinConversion: ChinesePinyinConversionState, lyricsReplacement: LyricsReplacementState, + lyricsMapping: LyricsMappingState, slightRestsFilling: SlightRestsFillingState, pitchConversion: PitchConversionState, projectZoom: ProjectZoomState, @@ -272,6 +293,9 @@ private fun process( .runIf(lyricsReplacement.isOn) { replaceLyrics(lyricsReplacement.request) } + .runIf(lyricsMapping.isOn) { + mapLyrics(lyricsMapping.request) + } .runIf(slightRestsFilling.isOn) { fillRests(slightRestsFilling.excludedMaxLength) } @@ -370,6 +394,21 @@ data class LyricsReplacementState( val isReady: Boolean get() = if (isOn) request.isValid else true } +@Serializable +data class LyricsMappingState( + val isOn: Boolean, + val presetName: String?, + val request: LyricsMappingRequest, +) : SubState() { + val isReady: Boolean get() = if (isOn) request.isValid else true + + fun updatePresetName() = when { + presetName == null -> this + LyricsMappingRequest.findPreset(presetName) == request -> this + else -> copy(presetName = null) + } +} + @Serializable data class SlightRestsFillingState( val isOn: Boolean, diff --git a/src/main/kotlin/ui/configuration/LyricsConvertion.kt b/src/main/kotlin/ui/configuration/LyricsConversion.kt similarity index 100% rename from src/main/kotlin/ui/configuration/LyricsConvertion.kt rename to src/main/kotlin/ui/configuration/LyricsConversion.kt diff --git a/src/main/kotlin/ui/configuration/LyricsMapping.kt b/src/main/kotlin/ui/configuration/LyricsMapping.kt new file mode 100644 index 00000000..aa5fb897 --- /dev/null +++ b/src/main/kotlin/ui/configuration/LyricsMapping.kt @@ -0,0 +1,200 @@ +package ui.configuration + +import csstype.AlignItems +import csstype.Display +import csstype.FlexDirection +import csstype.Length +import csstype.Margin +import csstype.VerticalAlign +import csstype.WhiteSpace +import csstype.em +import csstype.px +import kotlinx.js.jso +import mui.icons.material.HelpOutline +import mui.material.BaseTextFieldProps +import mui.material.Button +import mui.material.ButtonColor +import mui.material.ButtonVariant +import mui.material.FormControl +import mui.material.FormControlMargin +import mui.material.FormControlVariant +import mui.material.FormGroup +import mui.material.FormLabel +import mui.material.MenuItem +import mui.material.Paper +import mui.material.StandardTextFieldProps +import mui.material.TextField +import mui.material.Tooltip +import mui.material.TooltipPlacement +import mui.material.Typography +import mui.material.styles.TypographyVariant +import process.lyrics.LyricsMappingRequest +import react.ChildrenBuilder +import react.create +import react.css.css +import react.dom.html.ReactHTML.div +import ui.LyricsMappingState +import ui.common.SubProps +import ui.common.configurationSwitch +import ui.common.subFC +import ui.strings.Strings +import ui.strings.string + +external interface LyricsMappingProps : SubProps + +val LyricsMappingBlock = subFC { _, state, editState -> + FormGroup { + div { + configurationSwitch( + isOn = state.isOn, + onSwitched = { editState { copy(isOn = it) } }, + labelStrings = Strings.LyricsMapping, + ) + Tooltip { + val text = string(Strings.LyricsMappingDescription) + title = div.create { + css { whiteSpace = WhiteSpace.preLine } + +text + } + placement = TooltipPlacement.right + disableInteractive = false + HelpOutline { + style = jso { + verticalAlign = VerticalAlign.middle + } + } + } + } + } + + if (state.isOn) buildLyricsMappingDetail(state, editState) +} + +private fun ChildrenBuilder.buildLyricsMappingDetail( + state: LyricsMappingState, + editState: (LyricsMappingState.() -> LyricsMappingState) -> Unit, +) { + div { + css { + margin = Margin(horizontal = 40.px, vertical = 0.px) + width = Length.maxContent + } + Paper { + elevation = 0 + div { + css { + margin = Margin( + horizontal = 24.px, + top = 16.px, + bottom = 24.px, + ) + } + FormGroup { + div { + style = jso { + display = Display.flex + flexDirection = FlexDirection.row + alignItems = AlignItems.flexEnd + } + FormControl { + margin = FormControlMargin.normal + variant = FormControlVariant.standard + focused = false + FormLabel { + focused = false + Typography { + variant = TypographyVariant.caption + +string(Strings.LyricsMappingPreset) + } + } + TextField { + style = jso { minWidth = 16.em } + select = true + value = state.presetName.orEmpty().unsafeCast() + (this.unsafeCast()).variant = FormControlVariant.standard + (this.unsafeCast()).onChange = { event -> + val value = event.target.asDynamic().value as String + editState { + copy( + presetName = value, + request = LyricsMappingRequest.getPreset(value), + ) + } + } + LyricsMappingRequest.Presets.forEach { preset -> + MenuItem { + value = preset.first + +(preset.first) + } + } + } + } + Button { + style = jso { + marginLeft = 24.px + marginBottom = 12.px + } + variant = ButtonVariant.outlined + color = ButtonColor.secondary + onClick = { + editState { + copy( + presetName = null, + request = LyricsMappingRequest(), + ) + } + } + div { + +string(Strings.LyricsMappingPresetClear) + } + } + } + + div { + TextField { + multiline = true + style = jso { + marginTop = 8.px + marginBottom = 8.px + width = 25.em + overflowY = csstype.Overflow.scroll + } + (this.unsafeCast()).InputProps = jso { + style = jso { + paddingTop = 12.px + paddingBottom = 12.px + } + } + minRows = 5 + maxRows = 10 + placeholder = string(Strings.LyricsMappingMapPlaceholder) + value = state.request.mapText + (this.unsafeCast()).variant = FormControlVariant.filled + (this.unsafeCast()).onChange = { event -> + val value = event.target.asDynamic().value as String + editState { + copy(request = request.copy(mapText = value)).updatePresetName() + } + } + } + } + div { + style = jso { + paddingTop = 12.px + paddingBottom = 16.px + } + configurationSwitch( + isOn = state.request.mapToPhonemes, + onSwitched = { + editState { + copy(request = request.copy(mapToPhonemes = it)).updatePresetName() + } + }, + labelStrings = Strings.LyricsMappingToPhonemes, + ) + } + } + } + } + } +} diff --git a/src/main/kotlin/ui/strings/Strings.kt b/src/main/kotlin/ui/strings/Strings.kt index 07cffbac..8bb5f2d6 100644 --- a/src/main/kotlin/ui/strings/Strings.kt +++ b/src/main/kotlin/ui/strings/Strings.kt @@ -271,6 +271,48 @@ enum class Strings( ru = "До", fr = "À", ), + LyricsMapping( + en = "Map lyrics to lyrics or phonemes", + ja = "歌詞を歌詞または発音記号にマッピング", + zhCN = "将歌词映射到歌词或音素", + ru = "Отобразить тексты на тексты или фонемы", + fr = "Mapper les paroles sur les paroles ou les phonèmes", + ), + LyricsMappingDescription( + en = "Only lyrics that is completely same as the key will be mapped. ", + ja = "キーと完全一致する歌詞のみマッピングされます。", + zhCN = "只有与键完全相同的歌词才会被映射。", + ru = "Будут отображены только тексты, полностью совпадающие с ключом.", + fr = "Seules les paroles qui sont complètement identiques à la clé seront mappées.", + ), + LyricsMappingPreset( + en = "Preset", + ja = "プリセット", + zhCN = "预设", + ru = "Пресет", + fr = "Préréglage", + ), + LyricsMappingPresetClear( + en = "Clear", + ja = "クリア", + zhCN = "清空", + ru = "Очистить", + fr = "Effacer", + ), + LyricsMappingToPhonemes( + en = "Write as phonemes instead", + ja = "発音記号として書き出す", + zhCN = "写入到音素", + ru = "Записать вместо фонем", + fr = "Écrire comme phonèmes", + ), + LyricsMappingMapPlaceholder( + en = "Write a mapping entry per line in the format of \"{from}={to}\".", + ja = "「{from}={to}」の形式で、一行に一つのマッピングエントリーを書き込んでください。", + zhCN = "请按照“{from}={to}”的格式,每行写入一个映射条目。", + ru = "Запишите запись отображения на строку в формате \"{from}={to}\".", + fr = "Écrivez une entrée de mappage par ligne au format \"{from}={to}\".", + ), ConvertPitchData( en = "Convert pitch parameters", ja = "ピッチパラメータを変換", From 526ddb4f1bc2b9f8210eb4a1f64af8667835fe22 Mon Sep 17 00:00:00 2001 From: sdercolin Date: Sat, 9 Sep 2023 03:10:54 +0900 Subject: [PATCH 4/5] Fix style of scrollbars --- src/main/kotlin/ui/configuration/LyricsMapping.kt | 2 -- src/main/resources/index.html | 1 + src/main/resources/link.css | 6 +++--- src/main/resources/scrollbar.css | 15 +++++++++++++++ 4 files changed, 19 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/scrollbar.css diff --git a/src/main/kotlin/ui/configuration/LyricsMapping.kt b/src/main/kotlin/ui/configuration/LyricsMapping.kt index aa5fb897..3a03171d 100644 --- a/src/main/kotlin/ui/configuration/LyricsMapping.kt +++ b/src/main/kotlin/ui/configuration/LyricsMapping.kt @@ -149,7 +149,6 @@ private fun ChildrenBuilder.buildLyricsMappingDetail( } } } - div { TextField { multiline = true @@ -157,7 +156,6 @@ private fun ChildrenBuilder.buildLyricsMappingDetail( marginTop = 8.px marginBottom = 8.px width = 25.em - overflowY = csstype.Overflow.scroll } (this.unsafeCast()).InputProps = jso { style = jso { diff --git a/src/main/resources/index.html b/src/main/resources/index.html index 1bbec453..bf0c886b 100644 --- a/src/main/resources/index.html +++ b/src/main/resources/index.html @@ -6,6 +6,7 @@ +