Skip to content

Commit

Permalink
Merge pull request #151 from sdercolin/develop
Browse files Browse the repository at this point in the history
Release v3.19
  • Loading branch information
sdercolin authored Sep 8, 2023
2 parents ba02252 + b2a1062 commit 4f6e157
Show file tree
Hide file tree
Showing 11 changed files with 582 additions and 5 deletions.
2 changes: 1 addition & 1 deletion src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
3 changes: 3 additions & 0 deletions src/main/kotlin/external/Resources.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
57 changes: 57 additions & 0 deletions src/main/kotlin/process/lyrics/LyricsMapping.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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 mapText: String = "",
val mapToPhonemes: Boolean = false,
) {
val isValid get() = map.isNotEmpty()

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
}.toMap()

companion object {

fun findPreset(name: String) = Presets.find { it.first == name }?.second

fun getPreset(name: String) = requireNotNull(findPreset(name))

val Presets: List<Pair<String, LyricsMappingRequest>> by lazy {
listOf(
"VX-β 日本語かな変換" to LyricsMappingRequest(
mapText = Resources.lyricsMappingVxBetaJaText,
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)
}
}
41 changes: 40 additions & 1 deletion src/main/kotlin/ui/ConfigurationEditor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -101,6 +104,16 @@ val ConfigurationEditor = scopedFC<ConfigurationEditorProps> { props, scope ->
LyricsReplacementState(true, preset)
}
}
val (lyricsMapping, setLyricsMapping) = useState {
getStateFromLocalStorage<LyricsMappingState>("lyricsMapping")?.let {
return@useState it
}
LyricsMappingState(
isOn = false,
presetName = null,
request = LyricsMappingRequest(),
)
}
val (slightRestsFilling, setSlightRestsFilling) = useState {
getStateFromLocalStorage<SlightRestsFillingState>("slightRestsFilling")?.let {
return@useState it
Expand Down Expand Up @@ -134,7 +147,7 @@ val ConfigurationEditor = scopedFC<ConfigurationEditorProps> { 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)
Expand All @@ -155,6 +168,10 @@ val ConfigurationEditor = scopedFC<ConfigurationEditorProps> { props, scope ->
initialState = lyricsReplacement
submitState = setLyricsReplacement
}
LyricsMappingBlock {
initialState = lyricsMapping
submitState = setLyricsMapping
}
SlightRestsFillingBlock {
initialState = slightRestsFilling
submitState = setSlightRestsFilling
Expand All @@ -175,6 +192,7 @@ val ConfigurationEditor = scopedFC<ConfigurationEditorProps> { props, scope ->
japaneseLyricsConversion,
chinesePinyinConversion,
lyricsReplacement,
lyricsMapping,
slightRestsFilling,
pitchConversion,
projectZoom,
Expand Down Expand Up @@ -208,6 +226,7 @@ private fun ChildrenBuilder.buildNextButton(
japaneseLyricsConversion: JapaneseLyricsConversionState,
chinesePinyinConversion: ChinesePinyinConversionState,
lyricsReplacement: LyricsReplacementState,
lyricsMapping: LyricsMappingState,
slightRestsFilling: SlightRestsFillingState,
pitchConversion: PitchConversionState,
projectZoom: ProjectZoomState,
Expand All @@ -229,6 +248,7 @@ private fun ChildrenBuilder.buildNextButton(
japaneseLyricsConversion,
chinesePinyinConversion,
lyricsReplacement,
lyricsMapping,
slightRestsFilling,
pitchConversion,
projectZoom,
Expand All @@ -247,6 +267,7 @@ private fun process(
japaneseLyricsConversion: JapaneseLyricsConversionState,
chinesePinyinConversion: ChinesePinyinConversionState,
lyricsReplacement: LyricsReplacementState,
lyricsMapping: LyricsMappingState,
slightRestsFilling: SlightRestsFillingState,
pitchConversion: PitchConversionState,
projectZoom: ProjectZoomState,
Expand All @@ -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)
}
Expand Down Expand Up @@ -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,
Expand Down
198 changes: 198 additions & 0 deletions src/main/kotlin/ui/configuration/LyricsMapping.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
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<LyricsMappingState>

val LyricsMappingBlock = subFC<LyricsMappingProps, LyricsMappingState> { _, 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<Nothing?>()
(this.unsafeCast<BaseTextFieldProps>()).variant = FormControlVariant.standard
(this.unsafeCast<StandardTextFieldProps>()).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
}
(this.unsafeCast<StandardTextFieldProps>()).InputProps = jso {
style = jso {
paddingTop = 12.px
paddingBottom = 12.px
}
}
minRows = 5
maxRows = 10
placeholder = string(Strings.LyricsMappingMapPlaceholder)
value = state.request.mapText
(this.unsafeCast<BaseTextFieldProps>()).variant = FormControlVariant.filled
(this.unsafeCast<StandardTextFieldProps>()).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,
)
}
}
}
}
}
}
Loading

0 comments on commit 4f6e157

Please sign in to comment.