├── .editorconfig ├── .github └── workflows │ ├── deploy.yml │ └── pull-request.yml ├── .gitignore ├── .npmrc ├── LICENSE.md ├── README.md ├── build.gradle.kts ├── core ├── .npmrc ├── build.gradle.kts ├── src │ └── main │ │ ├── kotlin │ │ ├── Library.kt │ │ └── core │ │ │ ├── exception │ │ │ ├── CannotReadFileException.kt │ │ │ ├── EmptyProjectException.kt │ │ │ ├── IllegalFileException.kt │ │ │ ├── IllegalNotePositionException.kt │ │ │ ├── NotesOverlappingException.kt │ │ │ ├── UnsupportedFileFormatError.kt │ │ │ └── ValueTooLargeException.kt │ │ │ ├── external │ │ │ ├── Common.kt │ │ │ ├── Encoding.kt │ │ │ ├── FileSaver.kt │ │ │ ├── JsYaml.kt │ │ │ ├── JsZip.kt │ │ │ ├── Resources.kt │ │ │ ├── UUID.kt │ │ │ └── ValueTree.kt │ │ │ ├── io │ │ │ ├── Ccs.kt │ │ │ ├── Dv.kt │ │ │ ├── ImportParamsJson.kt │ │ │ ├── Mid.kt │ │ │ ├── MusicXml.kt │ │ │ ├── Ppsf.kt │ │ │ ├── S5p.kt │ │ │ ├── StandardMid.kt │ │ │ ├── Svp.kt │ │ │ ├── Tssln.kt │ │ │ ├── UfData.kt │ │ │ ├── Ust.kt │ │ │ ├── Ustx.kt │ │ │ ├── VocaloidMid.kt │ │ │ ├── Vpr.kt │ │ │ ├── Vsq.kt │ │ │ ├── VsqLike.kt │ │ │ └── Vsqx.kt │ │ │ ├── model │ │ │ ├── Constants.kt │ │ │ ├── ConversionParams.kt │ │ │ ├── ExportNotification.kt │ │ │ ├── ExportResult.kt │ │ │ ├── Feature.kt │ │ │ ├── Format.kt │ │ │ ├── ImportParams.kt │ │ │ ├── ImportWarning.kt │ │ │ ├── JapaneseLyricsType.kt │ │ │ ├── Note.kt │ │ │ ├── PhonemesMappingPreset.kt │ │ │ ├── Pitch.kt │ │ │ ├── Project.kt │ │ │ ├── ProjectContainer.kt │ │ │ ├── Tempo.kt │ │ │ ├── TickCounter.kt │ │ │ ├── TimeSignature.kt │ │ │ ├── Track.kt │ │ │ └── ValueTree.kt │ │ │ ├── process │ │ │ ├── Eval.kt │ │ │ ├── Interpolation.kt │ │ │ ├── LengthLimit.kt │ │ │ ├── NoteShaping.kt │ │ │ ├── ProjectZooming.kt │ │ │ ├── RdpSimplification.kt │ │ │ ├── Resampling.kt │ │ │ ├── lyrics │ │ │ │ ├── LyricsMapping.kt │ │ │ │ ├── LyricsReplacement.kt │ │ │ │ ├── chinese │ │ │ │ │ └── Conversion.kt │ │ │ │ └── japanese │ │ │ │ │ ├── Analysis.kt │ │ │ │ │ ├── Cleanup.kt │ │ │ │ │ ├── Conversion.kt │ │ │ │ │ └── Words.kt │ │ │ ├── phonemes │ │ │ │ └── PhonemesMapping.kt │ │ │ └── pitch │ │ │ │ ├── CevioPitchConversion.kt │ │ │ │ ├── DeepVocalPitchConversion.kt │ │ │ │ ├── OpenUtauPitchConversion.kt │ │ │ │ ├── PitchCalculation.kt │ │ │ │ ├── SynthVPitchConversion.kt │ │ │ │ ├── TickTimeTransformation.kt │ │ │ │ ├── TimeUnitConversion.kt │ │ │ │ ├── UtauMode1PitchConversion.kt │ │ │ │ ├── UtauMode2PitchConversion.kt │ │ │ │ ├── UtauVibratoConversion.kt │ │ │ │ └── VocaloidPitchConversion.kt │ │ │ └── util │ │ │ ├── ArrayBufferReader.kt │ │ │ ├── ByteExtension.kt │ │ │ ├── ChainCall.kt │ │ │ ├── EncodingUtil.kt │ │ │ ├── File.kt │ │ │ ├── MidiUtil.kt │ │ │ ├── Result.kt │ │ │ ├── TextUtil.kt │ │ │ └── XmlExtension.kt │ │ └── resources │ │ ├── format_templates │ │ ├── template.ccs │ │ ├── template.musicxml │ │ ├── template.s5p │ │ ├── template.svp │ │ ├── template.tssln.json │ │ ├── template.ustx │ │ ├── template.vprjson │ │ └── template.vsqx │ │ └── texts │ │ ├── OpenUtau ARPAsing to OpenUtau VCCV.txt │ │ ├── OpenUtau ARPAsing to OpenUtau X-SAMPA EN.txt │ │ ├── OpenUtau ARPAsing to SynthV EN.txt │ │ ├── OpenUtau ARPAsing to Vocaloid EN.txt │ │ ├── OpenUtau VCCV to OpenUtau ARPAsing.txt │ │ ├── OpenUtau VCCV to OpenUtau X-SAMPA EN.txt │ │ ├── OpenUtau VCCV to SynthV EN.txt │ │ ├── OpenUtau VCCV to Vocaloid EN.txt │ │ ├── OpenUtau X-SAMPA EN to OpenUtau ARPAsing.txt │ │ ├── OpenUtau X-SAMPA EN to OpenUtau VCCV.txt │ │ ├── OpenUtau X-SAMPA EN to SynthV EN.txt │ │ ├── OpenUtau X-SAMPA EN to Vocaloid EN.txt │ │ ├── SynthV EN to OpenUtau ARPAsing.txt │ │ ├── SynthV EN to OpenUtau VCCV.txt │ │ ├── SynthV EN to OpenUtau X-SAMPA EN.txt │ │ ├── SynthV EN to Vocaloid EN.txt │ │ ├── SynthV JA to Vocaloid JA.txt │ │ ├── Vocaloid EN to OpenUtau ARPAsing.txt │ │ ├── Vocaloid EN to OpenUtau VCCV.txt │ │ ├── Vocaloid EN to OpenUtau X-SAMPA EN.txt │ │ ├── Vocaloid EN to SynthV EN.txt │ │ ├── Vocaloid JA to SynthV JA.txt │ │ ├── mandarin-pinyin-dict.txt │ │ └── vxbeta-japanese-mapping.txt └── webpack.config.d │ └── resources.js ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── kotlin-js-store └── yarn.lock ├── settings.gradle.kts ├── src ├── jsMain │ ├── kotlin │ │ ├── Main.kt │ │ └── ui │ │ │ ├── App.kt │ │ │ ├── AppTheme.kt │ │ │ ├── ConfigurationEditor.kt │ │ │ ├── CustomFooter.kt │ │ │ ├── EmbeddedPage.kt │ │ │ ├── Exporter.kt │ │ │ ├── Importer.kt │ │ │ ├── LanguageSelector.kt │ │ │ ├── OutputFormatSelector.kt │ │ │ ├── Resources.kt │ │ │ ├── common │ │ │ ├── ErrorDialog.kt │ │ │ ├── FeatureSwitch.kt │ │ │ ├── MessageBar.kt │ │ │ ├── Progress.kt │ │ │ ├── ScopedFC.kt │ │ │ ├── SubFC.kt │ │ │ ├── Title.kt │ │ │ └── WarningDialog.kt │ │ │ ├── configuration │ │ │ ├── ChinesePinyinConversion.kt │ │ │ ├── LyricsConversion.kt │ │ │ ├── LyricsMapping.kt │ │ │ ├── LyricsReplacement.kt │ │ │ ├── PhonemesConversion.kt │ │ │ ├── PitchConversion.kt │ │ │ ├── ProjectSplit.kt │ │ │ ├── ProjectZoom.kt │ │ │ └── SlightRestFilling.kt │ │ │ ├── external │ │ │ ├── Cookies.kt │ │ │ └── react │ │ │ │ ├── FileDrop.kt │ │ │ │ └── Markdown.kt │ │ │ ├── model │ │ │ ├── Stage.kt │ │ │ └── StageInfo.kt │ │ │ └── strings │ │ │ ├── I18n.kt │ │ │ ├── Language.kt │ │ │ └── Strings.kt │ └── resources │ │ ├── fileDrop.css │ │ ├── images │ │ ├── cevio.png │ │ ├── dv.png │ │ ├── midi.png │ │ ├── openutau.png │ │ ├── svr1.png │ │ ├── svr2.png │ │ ├── ufdata.png │ │ ├── utau.png │ │ ├── vocaloid1.png │ │ ├── vocaloid2.png │ │ ├── vocaloid4.png │ │ ├── vocaloid5.png │ │ └── voisona.png │ │ ├── index.html │ │ ├── link.css │ │ └── scrollbar.css └── jsTest │ └── kotlin │ └── process │ ├── lyrics │ └── LyricsReplacementTest.kt │ ├── phonemes │ └── PhonemesMappingTest.kt │ └── pitch │ ├── PitchCalculationTest.kt │ └── TickTimeTransformerTest.kt └── webpack.config.d ├── global.js └── resources.js /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: deploy 2 | 3 | on: 4 | push: 5 | branches: [ master, develop ] 6 | 7 | concurrency: 8 | group: "pages" 9 | cancel-in-progress: true 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | ref: master 19 | 20 | - name: Set up JDK 21 21 | uses: actions/setup-java@v3 22 | with: 23 | java-version: '21' 24 | distribution: 'corretto' 25 | 26 | - name: Setup Gradle 27 | uses: gradle/actions/setup-gradle@v4 28 | 29 | - name: Build master branch 30 | run: | 31 | mkdir -p build/out 32 | ./gradlew jsBrowserDistribution 33 | mv build/dist/js/productionExecutable/* ./build/out 34 | 35 | - name: Check out develop branch 36 | uses: actions/checkout@v4 37 | with: 38 | ref: develop 39 | path: beta 40 | clean: false 41 | 42 | - name: Build develop branch 43 | run: | 44 | cd beta 45 | ./gradlew jsBrowserDistribution 46 | mkdir ../build/out/beta 47 | mv build/dist/js/productionExecutable/* ../build/out/beta 48 | 49 | - name: Upload artifact 50 | uses: actions/upload-pages-artifact@v3 51 | with: 52 | path: './build/out' 53 | 54 | deploy: 55 | needs: build 56 | 57 | permissions: 58 | pages: write 59 | id-token: write 60 | 61 | environment: 62 | name: github-pages 63 | url: ${{ steps.deployment.outputs.page_url }} 64 | 65 | runs-on: ubuntu-latest 66 | steps: 67 | - name: Deploy to GitHub Pages 68 | id: deployment 69 | uses: actions/deploy-pages@v4 70 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | name: pull-request-builder 2 | 3 | on: 4 | pull_request: 5 | branches: [ master, develop ] 6 | types: [ opened, synchronize, reopened ] 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | 15 | - name: Set up JDK 21 16 | uses: actions/setup-java@v1 17 | with: 18 | java-version: 21 19 | 20 | - name: Grant execute permission for gradlew 21 | run: chmod +x gradlew 22 | 23 | - name: Build with Gradle 24 | run: | 25 | ./gradlew build ktlintCheck 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build 3 | .idea 4 | .kotlin 5 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | @jsr:registry=https://npm.jsr.io 2 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright 2020 sdercolin 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UtaFormatix3 2 | 3 | [![Discord](https://img.shields.io/discord/984044285584359444?style=for-the-badge&label=discord&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/TyEcQ6P73y) 4 | 5 | UtaFormatix is an application for converting projects among singing voice synthesizer softwares. 6 | 7 | The current version `3.x` is built with [Kotlin for JavaScript](https://kotlinlang.org/docs/js-overview.html) 8 | and [React](https://github.com/facebook/react). 9 | 10 | ## Features 11 | 12 | - Supported importing 13 | formats: `.vsqx(3/4)`, `.vpr`, `.vsq`, `.mid(VOCALOID)`, `.mid(standard)`, `.ust`, `.ustx`, `.ccs`,`.xml(MusicXML)` 14 | , `.musicxml`, `.svp`, `.s5p`, `.dv`, `.ppsf(NT)`, `.tssln`, `.ufdata` 15 | - Supported exporting 16 | formats: `.vsqx(4)`, `.vpr`, `.vsq`, `.mid(VOCALOID)`, `.mid(standard)`, `.ust`, `.ustx`, `.ccs`, `.xml(MusicXML)` 17 | , `.svp`, `.s5p`, `.dv`, `.tssln`, `.ufdata` 18 | - Keep information including: tracks, notes, tempo labels, time signatures 19 | - Detect and convert Japanese lyrics types 20 | - between CV and VCV 21 | - between Kana and Romaji 22 | - Find/Replace texts in the lyrics 23 | - Lyrics mapping with customized dictionaries 24 | - Convert phonemes of the notes with or without mapping with customized dictionaries (only available for VOCALOID 25 | projects, `.ustx`, `.svp` and `.ufdata`.) 26 | - Project zooming, changing tempo and time signatures without changing the actual time duration of the contents 27 | - Project splitting with a max track count in each project for SVP export 28 | - Convert pitch for the following supported formats 29 | 30 | | Format | Pitch import | Vibrato import | Pitch export | 31 | |----------------------|--------------|----------------|--------------| 32 | | VSQ/VSQX/VPR/MID(V1) | ✓ | | ✓ | 33 | | UST(mode2) | ✓ | ✓ | ✓ | 34 | | UST(mode1) | ✓ | N/A | ✓ | 35 | | USTX | ✓ | ✓ | ✓ | 36 | | CCS | ✓ | | ✓ | 37 | | SVP | ✓ | ✓ | ✓ | 38 | | S5P | ✓ | | ✓ | 39 | | DV | ✓ | ✓ | ✓ | 40 | 41 | ## Open format published (.ufdata) 42 | 43 | We have published the internal data format of UtaFormatix 44 | to [UtaFormatix Data](https://github.com/sdercolin/utaformatix-data). 45 | 46 | If you are developing OSS projects related to singing voice synthesis, you may find it useful. 47 | 48 | ## Contributors 49 | 50 | [sdercolin](https://github.com/sdercolin), [ghosrt](https://github.com/ghosrt), [shine5402](https://github.com/shine5402), [adlez27](https://github.com/adlez27), [ 51 | General Nuisance](https://github.com/GeneralNuisance0), [sevenc-nanashi](https://github.com/sevenc-nanashi) 52 | 53 | ## Localization help 54 | 55 | [時雨ゆん](https://twitter.com/Yun_Shigure), [KagamineP](https://github.com/KagamineP) 56 | , [Exorcism0666](https://github.com/Exorcism0666) 57 | 58 | ## Get started for development 59 | 60 | 1. Install [IntelliJ IDEA](https://www.jetbrains.com/idea/) 61 | 2. Clone and import as a Gradle project 62 | 3. Configure IDEA's Gradle settings with `JDK 21` and `Use Gradle Wrapper` 63 | 4. Run by `./gradlew jsBrowserDevelopmentRun` or Gradle Task `other/jsBrowserDevelopmentRun` 64 | 5. Optionally, run by `./gradlew jsBrowserDevelopmentRun --continuous` with live reloading enabled 65 | 66 | ## Contribution 67 | 68 | Code contribution is welcomed. Basically, please cut your branch from `develop` and make Pull Requests towards `develop` 69 | branch. 70 | 71 | #### Adding a format support 72 | 73 | Please check [Format.kt](core/src/main/kotlin/core/model/Format.kt) and its usages. 74 | 75 | #### Adding a Language 76 | 77 | Please check [Strings.kt](src/jsMain/kotlin/ui/strings/Strings.kt). 78 | 79 | #### Adding a configurable process 80 | 81 | Please check [ConfigurationEditor.kt](src/jsMain/kotlin/ui/ConfigurationEditor.kt) 82 | about how the existing processes work. 83 | 84 | #### Build/Format check 85 | 86 | Pull requests require build check to be merged. Besides normal building of the project, a format check is conducted. 87 | Please confirm that the `build` and `ktlintCheck` Gradle tasks pass before submitting your code. 88 | 89 | You may find `ktlintFormat` task helpful, which helps fix most format problems. 90 | 91 | If your IDE's formatter is conflicting with `ktlint`, please import format settings 92 | from [.editorconfig](.editorconfig) (IntelliJ IDEA uses it by default). 93 | 94 | ## License 95 | 96 | [Apache License, Version 2.0](LICENSE.md) 97 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("multiplatform") version "2.1.20" 3 | kotlin("plugin.serialization") version "2.1.20" 4 | id("org.jlleitschuh.gradle.ktlint") version "12.2.0" 5 | } 6 | 7 | group = "com.sdercolin.utaformatix" 8 | 9 | repositories { 10 | mavenLocal() 11 | mavenCentral() 12 | } 13 | 14 | allprojects { 15 | apply(plugin = "org.jlleitschuh.gradle.ktlint") 16 | configure { 17 | version.set("1.5.0") 18 | } 19 | } 20 | 21 | fun kotlinw(target: String): String = "org.jetbrains.kotlin-wrappers:kotlin-$target" 22 | 23 | kotlin { 24 | js { 25 | binaries.executable() 26 | browser { 27 | commonWebpackConfig { 28 | devServer = devServer?.copy(port = 33221) 29 | } 30 | testTask { 31 | useKarma { 32 | useChromeHeadless() 33 | } 34 | } 35 | } 36 | } 37 | sourceSets { 38 | val jsMain by getting { 39 | dependencies { 40 | implementation(project(":core")) 41 | implementation("com.sdercolin.utaformatix:utaformatix-data:1.1.0") 42 | 43 | // Kotlin 44 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.6.4") 45 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") 46 | 47 | // React, React DOM + Wrappers 48 | implementation(enforcedPlatform("org.jetbrains.kotlin-wrappers:kotlin-wrappers-bom:1.0.0-pre.430")) 49 | implementation(kotlinw("emotion")) 50 | implementation(kotlinw("extensions")) 51 | implementation(kotlinw("react")) 52 | implementation(kotlinw("react-dom")) 53 | implementation(kotlinw("mui")) 54 | implementation(kotlinw("mui-icons")) 55 | 56 | // React components 57 | implementation(npm("react-file-drop", "3.1.2")) 58 | implementation(npm("react-markdown", "5.0.3")) 59 | 60 | // Localization 61 | implementation(npm("i18next", "19.8.7")) 62 | implementation(npm("react-i18next", "11.8.5")) 63 | implementation(npm("i18next-browser-languagedetector", "6.0.1")) 64 | 65 | // Others 66 | implementation(npm("stream-browserify", "3.0.0")) 67 | implementation(npm("buffer", "6.0.3")) 68 | implementation(npm("file-saver", "2.0.5")) 69 | implementation(npm("raw-loader", "4.0.2")) 70 | implementation(npm("file-loader", "6.2.0")) 71 | implementation(npm("js-cookie", "2.2.1")) 72 | } 73 | } 74 | val jsTest by getting { 75 | dependencies { 76 | implementation(kotlin("test")) 77 | } 78 | } 79 | } 80 | } 81 | 82 | val copyCoreResources by tasks.register("copyCoreResources") { 83 | from("core/src/main/resources") { 84 | include("**/*.*") 85 | } 86 | into("build/js/packages/utaformatix/kotlin/") 87 | mustRunAfter("jsProductionExecutableCompileSync") 88 | mustRunAfter("jsDevelopmentExecutableCompileSync") 89 | } 90 | tasks.named("jsBrowserProductionWebpack") { 91 | dependsOn(copyCoreResources) 92 | } 93 | tasks.named("jsBrowserDevelopmentRun") { 94 | dependsOn(copyCoreResources) 95 | } 96 | 97 | val copyJsResourcesForTests by tasks.register("copyJsResourcesForTests") { 98 | from("core/src/main/resources") { 99 | include("**/*.*") 100 | } 101 | from("src/jsMain/resources") { 102 | include("**/*.*") 103 | } 104 | into("build/js/packages/utaformatix-test/kotlin") 105 | mustRunAfter("jsTestTestDevelopmentExecutableCompileSync") 106 | } 107 | tasks.named("jsBrowserTest") { 108 | dependsOn(copyJsResourcesForTests) 109 | } 110 | 111 | val cleanDistributedResources by tasks.register("cleanDistributedResources") { 112 | listOf("format_templates", "images", "texts").forEach { 113 | delete("build/distributions/$it") 114 | } 115 | mustRunAfter("jsBrowserDistribution") 116 | } 117 | tasks.named("build") { 118 | dependsOn(cleanDistributedResources) 119 | } 120 | -------------------------------------------------------------------------------- /core/.npmrc: -------------------------------------------------------------------------------- 1 | @jsr:registry=https://npm.jsr.io 2 | -------------------------------------------------------------------------------- /core/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | kotlin("js") 3 | kotlin("plugin.serialization") 4 | } 5 | 6 | group = "com.sdercolin.utaformatix" 7 | 8 | repositories { 9 | mavenLocal() 10 | mavenCentral() 11 | } 12 | 13 | kotlin { 14 | js(IR) { 15 | binaries.library() 16 | browser() 17 | generateTypeScriptDefinitions() 18 | useEsModules() 19 | dependencies { 20 | implementation("com.sdercolin.utaformatix:utaformatix-data:1.1.0") 21 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.6.4") 22 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0") 23 | implementation(npm("jszip", "3.5.0")) 24 | implementation(npm("encoding-japanese", "1.0.30")) 25 | implementation(npm("uuid", "8.3.2")) 26 | implementation(npm("midi-file", "1.2.4")) 27 | implementation(npm("js-yaml", "4.1.0")) 28 | implementation( 29 | npm("@sevenc-nanashi/valuetree-ts", "npm:@jsr/sevenc-nanashi__valuetree-ts@0.2.1"), 30 | ) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/exception/CannotReadFileException.kt: -------------------------------------------------------------------------------- 1 | package core.exception 2 | 3 | @OptIn(ExperimentalJsExport::class) 4 | @JsExport 5 | class CannotReadFileException : Throwable() 6 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/exception/EmptyProjectException.kt: -------------------------------------------------------------------------------- 1 | package core.exception 2 | 3 | @OptIn(ExperimentalJsExport::class) 4 | @JsExport 5 | class EmptyProjectException : Throwable("This format could not take en empty project.") 6 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/exception/IllegalFileException.kt: -------------------------------------------------------------------------------- 1 | package core.exception 2 | 3 | @OptIn(ExperimentalJsExport::class) 4 | @JsExport 5 | sealed class IllegalFileException( 6 | message: String, 7 | ) : Throwable(message) { 8 | class UnknownVsqVersion : IllegalFileException("Cannot identify the version of the loaded vsqx file.") 9 | 10 | class XmlRootNotFound : IllegalFileException("The root element is not found in the xml file.") 11 | 12 | class XmlElementNotFound( 13 | name: String, 14 | ) : IllegalFileException("The required element <$name> is not found in the xml file.") 15 | 16 | class XmlElementValueIllegal( 17 | name: String, 18 | ) : IllegalFileException("The required element <$name> has an illegal value.") 19 | 20 | class XmlElementAttributeValueIllegal( 21 | attribute: String, 22 | elementName: String, 23 | ) : IllegalFileException( 24 | "The required attribute \"$attribute\" in element <$elementName> is missing or has in illegal value.", 25 | ) 26 | 27 | class IllegalMidiFile : IllegalFileException("Cannot parse this file as a MIDI file.") 28 | 29 | class IllegalTsslnFile : IllegalFileException("Cannot parse this file as a tssln file.") 30 | } 31 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/exception/IllegalNotePositionException.kt: -------------------------------------------------------------------------------- 1 | package core.exception 2 | 3 | import core.model.Note 4 | 5 | @OptIn(ExperimentalJsExport::class) 6 | @JsExport 7 | class IllegalNotePositionException( 8 | @Suppress("NON_EXPORTABLE_TYPE") note: Note, 9 | trackIndex: Int, 10 | ) : Throwable( 11 | "Failed to import because note with illegal position(${note.tickOn}) exists in Track No.${trackIndex + 1}", 12 | ) 13 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/exception/NotesOverlappingException.kt: -------------------------------------------------------------------------------- 1 | package core.exception 2 | 3 | @OptIn(ExperimentalJsExport::class) 4 | @JsExport 5 | class NotesOverlappingException : 6 | Throwable( 7 | "Failed to process because there are notes overlapping with each other.", 8 | ) 9 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/exception/UnsupportedFileFormatError.kt: -------------------------------------------------------------------------------- 1 | package core.exception 2 | 3 | @OptIn(ExperimentalJsExport::class) 4 | @JsExport 5 | open class UnsupportedFileFormatError : Exception() 6 | 7 | @OptIn(ExperimentalJsExport::class) 8 | @JsExport 9 | class UnsupportedLegacyPpsfError : UnsupportedFileFormatError() 10 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/exception/ValueTooLargeException.kt: -------------------------------------------------------------------------------- 1 | package core.exception 2 | 3 | @OptIn(ExperimentalJsExport::class) 4 | @JsExport 5 | class ValueTooLargeException( 6 | value: String, 7 | maxValue: String, 8 | ) : Throwable( 9 | "Given value $value is larger than the maximum: $maxValue.", 10 | ) 11 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/external/Common.kt: -------------------------------------------------------------------------------- 1 | package core.external 2 | 3 | external fun require(module: String): dynamic 4 | 5 | external fun structuredClone(obj: T): T 6 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/external/Encoding.kt: -------------------------------------------------------------------------------- 1 | package core.external 2 | 3 | @JsModule("encoding-japanese") 4 | @JsNonModule 5 | external class Encoding { 6 | companion object { 7 | fun convert( 8 | data: Array, 9 | to: String, 10 | from: String = definedExternally, 11 | ): Array 12 | 13 | fun detect(data: Array): String 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/external/FileSaver.kt: -------------------------------------------------------------------------------- 1 | package core.external 2 | 3 | import org.w3c.files.Blob 4 | 5 | @JsModule("file-saver") 6 | @JsNonModule 7 | external fun saveAs( 8 | blob: Blob, 9 | name: String, 10 | ) 11 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/external/JsYaml.kt: -------------------------------------------------------------------------------- 1 | package core.external 2 | 3 | @JsModule("js-yaml") 4 | @JsNonModule 5 | external object JsYaml { 6 | fun load(text: String): dynamic 7 | 8 | fun dump(`object`: dynamic): String 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/external/JsZip.kt: -------------------------------------------------------------------------------- 1 | package core.external 2 | 3 | import kotlin.js.Promise 4 | 5 | @JsModule("jszip") 6 | @JsNonModule 7 | external class JsZip { 8 | fun loadAsync(data: dynamic): Promise 9 | 10 | fun file(name: String): JsZipObject? 11 | 12 | fun file( 13 | name: String, 14 | data: dynamic, 15 | ): JsZip 16 | 17 | fun generateAsync(option: JsZipOption): Promise 18 | } 19 | 20 | @JsName("ZipObject") 21 | external class JsZipObject { 22 | fun async(type: String): Promise 23 | } 24 | 25 | @JsName("Option") 26 | external class JsZipOption { 27 | var type: String 28 | var mimeType: String = definedExternally 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/external/Resources.kt: -------------------------------------------------------------------------------- 1 | package core.external 2 | 3 | object Resources { 4 | val vsqxTemplate: String 5 | get() = require("./format_templates/template.vsqx").default as String 6 | 7 | val vprTemplate: String 8 | get() = require("./format_templates/template.vprjson").default as String 9 | 10 | val ccsTemplate: String 11 | get() = require("./format_templates/template.ccs").default as String 12 | 13 | val musicXmlTemplate: String 14 | get() = require("./format_templates/template.musicxml").default as String 15 | 16 | val tsslnTemplate: Array 17 | get() = 18 | require("./format_templates/template.tssln.json") as Array 19 | val svpTemplate: String 20 | get() = require("./format_templates/template.svp").default as String 21 | 22 | val s5pTemplate: String 23 | get() = require("./format_templates/template.s5p").default as String 24 | 25 | val ustxTemplate: String 26 | get() = require("./format_templates/template.ustx").default as String 27 | 28 | val chineseLyricsDictionaryText: String 29 | get() = require("./texts/mandarin-pinyin-dict.txt").default as String 30 | 31 | val lyricsMappingVxBetaJaText: String 32 | get() = require("./texts/vxbeta-japanese-mapping.txt").default as String 33 | } 34 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/external/UUID.kt: -------------------------------------------------------------------------------- 1 | @file:JsModule("uuid") 2 | @file:JsNonModule 3 | 4 | package core.external 5 | 6 | @JsName("v4") 7 | external fun generateUUID(): String 8 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/external/ValueTree.kt: -------------------------------------------------------------------------------- 1 | @file:JsModule("@sevenc-nanashi/valuetree-ts") 2 | 3 | package core.external 4 | 5 | import org.khronos.webgl.Uint8Array 6 | 7 | external interface ValueTree { 8 | var type: String 9 | var attributes: dynamic 10 | var children: Array 11 | } 12 | 13 | external fun parseValueTree(text: Uint8Array): ValueTree 14 | 15 | external fun dumpValueTree(tree: ValueTree): Uint8Array 16 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/io/ImportParamsJson.kt: -------------------------------------------------------------------------------- 1 | package core.io 2 | 3 | import core.model.ImportParams 4 | import kotlinx.serialization.json.Json 5 | 6 | object ImportParamsJson { 7 | fun parse(content: String): ImportParams = jsonSerializer.decodeFromString(ImportParams.serializer(), content) 8 | 9 | fun generate(config: ImportParams): String = jsonSerializer.encodeToString(ImportParams.serializer(), config) 10 | 11 | private val jsonSerializer = 12 | Json { 13 | encodeDefaults = true 14 | isLenient = true 15 | ignoreUnknownKeys = true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/io/VocaloidMid.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("SpellCheckingInspection") 2 | 3 | package core.io 4 | 5 | import core.model.ExportResult 6 | import core.model.FeatureConfig 7 | import core.model.Format 8 | import core.model.ImportParams 9 | import core.model.Project 10 | import core.model.contains 11 | import org.w3c.files.File 12 | 13 | object VocaloidMid { 14 | suspend fun parse( 15 | file: File, 16 | params: ImportParams, 17 | ): Project = VsqLike.parse(file, format, params) 18 | 19 | fun generate( 20 | project: Project, 21 | features: List, 22 | ): ExportResult = VsqLike.generate(project, features, format) 23 | 24 | private val format = Format.VocaloidMid 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/io/Vsq.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("SpellCheckingInspection") 2 | 3 | package core.io 4 | 5 | import core.model.ExportResult 6 | import core.model.FeatureConfig 7 | import core.model.Format 8 | import core.model.ImportParams 9 | import core.model.Project 10 | import core.model.contains 11 | import org.w3c.files.File 12 | 13 | object Vsq { 14 | suspend fun parse( 15 | file: File, 16 | params: ImportParams, 17 | ): Project = VsqLike.parse(file, format, params) 18 | 19 | fun generate( 20 | project: Project, 21 | features: List, 22 | ): ExportResult = VsqLike.generate(project, features, format) 23 | 24 | private val format = Format.Vsq 25 | } 26 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/Constants.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | const val TICKS_IN_BEAT = 480 4 | const val TICKS_IN_FULL_NOTE = TICKS_IN_BEAT * 4 5 | const val KEY_IN_OCTAVE = 12 6 | const val DEFAULT_LYRIC = "あ" 7 | const val DEFAULT_BPM = 120.0 8 | const val DEFAULT_METER_HIGH = 4 9 | const val DEFAULT_METER_LOW = 4 10 | const val DEFAULT_KEY = 60 11 | const val KEY_CENTER_C = 60.0 12 | const val LOG_FRQ_CENTER_C = 5.566914341 13 | const val LOG_FRQ_DIFF_ONE_KEY = 0.05776226505 14 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/ConversionParams.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | @OptIn(ExperimentalJsExport::class) 4 | @JsExport 5 | data class ConversionParams( 6 | val convertPitch: Boolean = false, 7 | ) 8 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/ExportNotification.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | @OptIn(ExperimentalJsExport::class) 4 | @JsExport 5 | sealed class ExportNotification { 6 | object PhonemeResetRequiredVSQ : ExportNotification() 7 | 8 | object PhonemeResetRequiredV4 : ExportNotification() 9 | 10 | object PhonemeResetRequiredV5 : ExportNotification() 11 | 12 | object TimeSignatureIgnored : ExportNotification() 13 | 14 | object PitchDataExported : ExportNotification() 15 | 16 | object DataOverLengthLimitIgnored : ExportNotification() 17 | } 18 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/ExportResult.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | import org.w3c.files.Blob 4 | 5 | @OptIn(ExperimentalJsExport::class) 6 | @JsExport 7 | class ExportResult( 8 | val blob: Blob, 9 | val fileName: String, 10 | val notifications: List, 11 | ) 12 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/Feature.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | /** 4 | * Features that are applied during format-specific generation. 5 | * 6 | * @property isAvailable Whether this feature is available for the project. 7 | */ 8 | enum class Feature( 9 | val isAvailable: (Project) -> Boolean, 10 | ) { 11 | ConvertPitch( 12 | isAvailable = { project -> 13 | project.tracks.any { it.pitch != null } 14 | }, 15 | ), 16 | SplitProject( 17 | isAvailable = { true }, 18 | ), 19 | ConvertPhonemes( 20 | isAvailable = { project -> 21 | project.tracks.any { track -> track.notes.any { note -> note.phoneme != null } } 22 | }, 23 | ), 24 | } 25 | 26 | sealed class FeatureConfig( 27 | val type: Feature, 28 | ) { 29 | object ConvertPitch : FeatureConfig(Feature.ConvertPitch) 30 | 31 | data class SplitProject( 32 | val maxTrackCount: Int, 33 | ) : FeatureConfig(Feature.SplitProject) { 34 | companion object { 35 | fun getDefault(format: Format): SplitProject = 36 | when (format) { 37 | Format.Svp -> SplitProject(3) 38 | else -> SplitProject(1) 39 | } 40 | } 41 | } 42 | } 43 | 44 | fun List.contains(feature: Feature) = any { it.type == feature } 45 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/ImportParams.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @OptIn(ExperimentalJsExport::class) 6 | @Serializable 7 | @JsExport 8 | data class ImportParams( 9 | val simpleImport: Boolean = false, 10 | val multipleMode: Boolean = false, 11 | val defaultLyric: String = "あ", 12 | ) 13 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/ImportWarning.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | import org.w3c.files.File 4 | 5 | sealed class ImportWarning { 6 | object TempoNotFound : ImportWarning() 7 | 8 | class TempoIgnoredInFile( 9 | val file: File, 10 | val tempo: Tempo, 11 | ) : ImportWarning() 12 | 13 | class TempoIgnoredInTrack( 14 | val track: Track, 15 | val tempo: Tempo, 16 | ) : ImportWarning() 17 | 18 | class TempoIgnoredInPreMeasure( 19 | val tempo: Tempo, 20 | ) : ImportWarning() 21 | 22 | class DefaultTempoFixed( 23 | val originalBpm: Double, 24 | ) : ImportWarning() 25 | 26 | object TimeSignatureNotFound : ImportWarning() 27 | 28 | class TimeSignatureIgnoredInTrack( 29 | val track: Track, 30 | val timeSignature: TimeSignature, 31 | ) : ImportWarning() 32 | 33 | class TimeSignatureIgnoredInPreMeasure( 34 | val timeSignature: TimeSignature, 35 | ) : ImportWarning() 36 | 37 | class IncompatibleFormatSerializationVersion( 38 | val currentVersion: String, 39 | val dataVersion: String, 40 | ) : ImportWarning() 41 | } 42 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/JapaneseLyricsType.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | @OptIn(ExperimentalJsExport::class) 4 | @JsExport 5 | enum class JapaneseLyricsType( 6 | val isRomaji: Boolean, 7 | val isCV: Boolean, 8 | ) { 9 | Unknown(false, false), 10 | RomajiCv(true, true), 11 | RomajiVcv(true, false), 12 | KanaCv(false, true), 13 | KanaVcv(false, false), 14 | ; 15 | 16 | fun findBestConversionTargetIn( 17 | @Suppress("NON_EXPORTABLE_TYPE") outputFormat: Format, 18 | ): JapaneseLyricsType? { 19 | outputFormat.suggestedLyricType?.let { 20 | return it 21 | } 22 | val options = outputFormat.possibleLyricsTypes 23 | if (options.contains(this)) return this 24 | options.find { it.isRomaji == this.isRomaji }?.let { 25 | return it 26 | } 27 | options.find { it.isCV == this.isCV }?.let { 28 | return it 29 | } 30 | return options.firstOrNull() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/Note.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | import core.process.RichNote 4 | 5 | data class Note( 6 | val id: Int, 7 | val key: Int, 8 | val lyric: String, 9 | val tickOn: Long, 10 | val tickOff: Long, 11 | val phoneme: String? = null, 12 | ) : RichNote { 13 | val length get() = tickOff - tickOn 14 | 15 | override val note: Note 16 | get() = this 17 | 18 | override fun copyWithNote(note: Note) = note 19 | } 20 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/PhonemesMappingPreset.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | import core.process.phonemes.PhonemesMappingRequest 4 | 5 | class PhonemesMappingPreset( 6 | val sourceFormats: List, 7 | val targetFormats: List, 8 | val name: String, 9 | val phonemesMap: PhonemesMappingRequest, 10 | ) 11 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/Pitch.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | /** 4 | * This class contains the pitch data defined by UtaFormatix Data Format. 5 | * @see [com.sdercolin.utaformatix.data.Pitch] 6 | */ 7 | data class Pitch( 8 | val data: List>, 9 | val isAbsolute: Boolean, 10 | ) 11 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/Project.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | import core.exception.IllegalNotePositionException 4 | import core.process.lyrics.japanese.analyseJapaneseLyricsTypeForProject 5 | import org.w3c.files.File 6 | 7 | data class Project( 8 | val format: Format, 9 | val inputFiles: List, 10 | val name: String, 11 | val tracks: List, 12 | val timeSignatures: List, 13 | val tempos: List, 14 | val measurePrefix: Int, 15 | val importWarnings: List, 16 | val japaneseLyricsType: JapaneseLyricsType = JapaneseLyricsType.Unknown, 17 | ) { 18 | fun lyricsTypeAnalysed() = 19 | copy( 20 | japaneseLyricsType = 21 | analyseJapaneseLyricsTypeForProject(this) 22 | .takeIf { format.possibleLyricsTypes.contains(it) } 23 | ?: JapaneseLyricsType.Unknown, 24 | ) 25 | 26 | fun withoutEmptyTracks() = 27 | copy( 28 | tracks = 29 | tracks 30 | .filter { it.notes.isNotEmpty() } 31 | .mapIndexed { index, track -> track.copy(id = index) }, 32 | ).takeIf { it.tracks.isNotEmpty() } 33 | 34 | val hasXSampaData get() = tracks.any { track -> track.notes.any { it.phoneme != null } } 35 | 36 | fun requireValid() = 37 | this.also { 38 | tracks.forEachIndexed { index, track -> 39 | val firstNote = track.notes.firstOrNull() ?: return@forEachIndexed 40 | if (firstNote.tickOn < 0L) { 41 | throw IllegalNotePositionException(firstNote, index) 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/ProjectContainer.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | /** 4 | * Container for a Project object for better typing (otherwise Document will be any in .d.ts) 5 | */ 6 | @OptIn(ExperimentalJsExport::class) 7 | @JsExport 8 | class ProjectContainer internal constructor( 9 | internal val project: Project, 10 | ) 11 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/Tempo.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | data class Tempo( 4 | val tickPosition: Long, 5 | val bpm: Double, 6 | ) { 7 | companion object { 8 | val default get() = Tempo(0, DEFAULT_BPM) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/TickCounter.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | class TickCounter( 4 | private val tickRate: Double = 1.0, 5 | private val ticksInFullNote: Long = TICKS_IN_FULL_NOTE.toLong(), 6 | ) { 7 | var tick = 0L 8 | private set 9 | 10 | val outputTick get() = (tick * tickRate).toLong() 11 | 12 | var measure = 0 13 | private set 14 | 15 | var numerator = DEFAULT_METER_HIGH 16 | private set 17 | 18 | var denominator = DEFAULT_METER_LOW 19 | private set 20 | 21 | val ticksInMeasure get() = ticksInFullNote * numerator / denominator 22 | 23 | fun goToTick( 24 | newTick: Long, 25 | newNumerator: Int? = null, 26 | newDenominator: Int? = null, 27 | ) { 28 | val normalizedNewTick = newTick / tickRate 29 | val tickDiff = normalizedNewTick - tick 30 | val measureDiff = tickDiff / ticksInMeasure 31 | measure += measureDiff.toInt() 32 | tick = normalizedNewTick.toLong() 33 | numerator = newNumerator ?: numerator 34 | denominator = newDenominator ?: denominator 35 | } 36 | 37 | fun goToMeasure(timeSignature: TimeSignature) = 38 | goToMeasure(timeSignature.measurePosition, timeSignature.numerator, timeSignature.denominator) 39 | 40 | fun goToMeasure( 41 | newMeasure: Int, 42 | newNumerator: Int? = null, 43 | newDenominator: Int? = null, 44 | ) { 45 | val measureDiff = newMeasure - measure 46 | val tickDiff = measureDiff * ticksInMeasure 47 | tick += tickDiff 48 | measure = newMeasure 49 | newNumerator?.let { numerator = it } 50 | newDenominator?.let { denominator = it } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/TimeSignature.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | data class TimeSignature( 4 | val measurePosition: Int, 5 | val numerator: Int, 6 | val denominator: Int, 7 | ) { 8 | val displayValue get() = "$numerator/$denominator" 9 | val ticksInMeasure get() = TICKS_IN_FULL_NOTE * numerator / denominator 10 | 11 | companion object { 12 | val default get() = TimeSignature(0, DEFAULT_METER_HIGH, DEFAULT_METER_LOW) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/Track.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | data class Track( 4 | val id: Int, 5 | val name: String, 6 | val notes: List, 7 | val pitch: Pitch? = null, 8 | ) 9 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/model/ValueTree.kt: -------------------------------------------------------------------------------- 1 | package core.model 2 | 3 | import core.external.ValueTree 4 | import org.khronos.webgl.Uint8Array 5 | 6 | fun createValueTree(): ValueTree = js("{type: '', attributes: {}, children: []}").unsafeCast() 7 | 8 | fun baseVariantType(): dynamic = js("({type: '', value: undefined})") 9 | 10 | fun String.toVariantType(): dynamic { 11 | val value = baseVariantType() 12 | value.type = "string" 13 | value.value = this 14 | 15 | return value 16 | } 17 | 18 | fun Int.toVariantType(): dynamic { 19 | val value = baseVariantType() 20 | value.type = "int" 21 | value.value = this 22 | 23 | return value 24 | } 25 | 26 | fun Double.toVariantType(): dynamic { 27 | val value = baseVariantType() 28 | value.type = "double" 29 | value.value = this 30 | 31 | return value 32 | } 33 | 34 | fun Boolean.toVariantType(): dynamic { 35 | val value = baseVariantType() 36 | if (this) { 37 | value.type = "boolTrue" 38 | value.value = true 39 | } else { 40 | value.type = "boolFalse" 41 | value.value = false 42 | } 43 | 44 | return value 45 | } 46 | 47 | fun Uint8Array.toVariantType(): dynamic { 48 | val value = baseVariantType() 49 | value.type = "binary" 50 | value.value = this 51 | 52 | return value 53 | } 54 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/Eval.kt: -------------------------------------------------------------------------------- 1 | package core.process 2 | 3 | fun String.evalFractionOrNull(): Double? { 4 | toDoubleOrNull()?.let { return it } 5 | val fractionIndex = indexOf("/").takeIf { it > 0 } ?: return null 6 | val left = substring(0, fractionIndex).toIntOrNull() ?: return null 7 | val right = substring(fractionIndex + 1).toIntOrNull() ?: return null 8 | return left.toDouble() / right 9 | } 10 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/Interpolation.kt: -------------------------------------------------------------------------------- 1 | package core.process 2 | 3 | fun List>.interpolateLinear(samplingIntervalTick: Long) = 4 | this.interpolate(samplingIntervalTick) { start, end, indexes -> 5 | val (x0, y0) = start 6 | val (x1, y1) = end 7 | indexes.map { x -> 8 | val y = y0 + (x - x0) * (y1 - y0) / (x1 - x0) 9 | x to y 10 | } 11 | } 12 | 13 | fun List>.interpolateCosineEaseInOut(samplingIntervalTick: Long) = 14 | this.interpolate(samplingIntervalTick) { start, end, indexes -> 15 | val (x0, y0) = start 16 | val (x1, y1) = end 17 | val xOffset = x0 18 | val yOffset = (y0 + y1) / 2 19 | val aFreq = kotlin.math.PI / (x1 - x0) 20 | val amp = (y0 - y1) / 2 21 | indexes.map { x -> 22 | val y = amp * kotlin.math.cos(aFreq * (x - xOffset)) + yOffset 23 | x to y 24 | } 25 | } 26 | 27 | fun List>.interpolateCosineEaseIn(samplingIntervalTick: Long) = 28 | this.interpolate(samplingIntervalTick) { start, end, indexes -> 29 | val (x0, y0) = start 30 | val (x1, y1) = end 31 | val xOffset = x0 32 | val yOffset = y1 33 | val aFreq = kotlin.math.PI / (x1 - x0) / 2 34 | val amp = y0 - y1 35 | indexes.map { x -> 36 | val y = amp * kotlin.math.cos(aFreq * (x - xOffset)) + yOffset 37 | x to y 38 | } 39 | } 40 | 41 | fun List>.interpolateCosineEaseOut(samplingIntervalTick: Long) = 42 | this.interpolate(samplingIntervalTick) { start, end, indexes -> 43 | val (x0, y0) = start 44 | val (x1, y1) = end 45 | val xOffset = x0 46 | val yOffset = y0 47 | val aFreq = kotlin.math.PI / (x1 - x0) / 2 48 | val amp = y0 - y1 49 | val phase = kotlin.math.PI / 2 50 | indexes.map { x -> 51 | val y = amp * kotlin.math.cos(aFreq * (x - xOffset) + phase) + yOffset 52 | x to y 53 | } 54 | } 55 | 56 | private fun List>.interpolate( 57 | samplingIntervalTick: Long, 58 | mapping: (start: Pair, end: Pair, input: List) -> List>, 59 | ) = this 60 | .takeIf { it.isNotEmpty() } 61 | ?.zipWithNext() 62 | ?.flatMap { (start, end) -> 63 | val indexes = 64 | ((start.first + 1) until end.first) 65 | .filter { (it - start.first) % samplingIntervalTick == 0L } 66 | val points = mapping(start, end, indexes) 67 | listOf(start) + points 68 | }?.plus(this.last()) 69 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/LengthLimit.kt: -------------------------------------------------------------------------------- 1 | package core.process 2 | 3 | import core.model.Project 4 | import core.model.TickCounter 5 | import core.model.TimeSignature 6 | import core.model.Track 7 | 8 | fun Project.lengthLimited(maxLength: Long): Project { 9 | val tracks = this.tracks.map { it.lengthLimited(maxLength) } 10 | val tickCounter = TickCounter() 11 | val timeSignatures = mutableListOf() 12 | this.timeSignatures.forEach { 13 | tickCounter.goToMeasure(it) 14 | if (tickCounter.tick <= maxLength) timeSignatures.add(it) 15 | } 16 | val tempos = this.tempos.filter { it.tickPosition <= maxLength } 17 | return copy( 18 | tracks = tracks, 19 | timeSignatures = timeSignatures, 20 | tempos = tempos, 21 | ) 22 | } 23 | 24 | private fun Track.lengthLimited(maxLength: Long): Track { 25 | val notes = 26 | this.notes 27 | .filter { it.tickOff <= maxLength } 28 | .mapIndexed { index, note -> note.copy(id = index) } 29 | val pitch = this.pitch?.copy(data = this.pitch.data.filter { it.first <= maxLength }) 30 | 31 | return copy( 32 | notes = notes, 33 | pitch = pitch, 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/NoteShaping.kt: -------------------------------------------------------------------------------- 1 | package core.process 2 | 3 | import core.model.Note 4 | import core.model.Project 5 | import core.model.Track 6 | import kotlin.math.min 7 | 8 | interface RichNote { 9 | val note: Note 10 | 11 | fun copyWithNote(note: Note): T 12 | } 13 | 14 | fun > List.validateNotes(): List { 15 | if (isEmpty()) return this 16 | return this 17 | .sortedBy { it.note.tickOn } 18 | .let { list -> 19 | list 20 | .asSequence() 21 | .zipWithNext() 22 | .map { (current, next) -> 23 | current.copyWithNote(current.note.copy(tickOff = min(current.note.tickOff, next.note.tickOn))) 24 | }.filter { it.note.length > 0 } 25 | .plus(list.last()) 26 | .mapIndexed { index, richNote -> 27 | richNote.copyWithNote(richNote.note.copy(id = index)) 28 | }.toList() 29 | } 30 | } 31 | 32 | fun Track.validateNotes() = copy(notes = notes.validateNotes()) 33 | 34 | fun Project.fillRests(excludedMaxLength: Long) = copy(tracks = tracks.map { it.fillRests(excludedMaxLength) }) 35 | 36 | private fun Track.fillRests(excludedMaxLength: Long) = 37 | if (notes.isEmpty()) { 38 | this 39 | } else { 40 | this.copy( 41 | notes = 42 | notes.let { 43 | it 44 | .zipWithNext() 45 | .map { (note, nextNote) -> 46 | if (nextNote.tickOn - note.tickOff < excludedMaxLength) { 47 | note.copy(tickOff = nextNote.tickOn) 48 | } else { 49 | note 50 | } 51 | }.plus(it.last()) 52 | }, 53 | ) 54 | } 55 | 56 | const val RESTS_FILLING_MAX_LENGTH_DENOMINATOR_DEFAULT = 64 57 | val restsFillingMaxLengthDenominatorOptions = listOf(8, 16, 32, 64, 128) 58 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/ProjectZooming.kt: -------------------------------------------------------------------------------- 1 | package core.process 2 | 3 | import core.model.Project 4 | import core.model.TimeSignature 5 | import core.model.Track 6 | import kotlin.math.ceil 7 | import kotlin.math.roundToInt 8 | import kotlin.math.roundToLong 9 | 10 | fun Project.needWarningZoom(factor: Double): Boolean = 11 | timeSignatures.any { 12 | val newPosition = it.measurePosition * factor 13 | ceil(newPosition) != newPosition 14 | } 15 | 16 | fun Project.zoom(factor: Double): Project { 17 | val tracks = tracks.map { it.zoom(factor) } 18 | val tempos = 19 | tempos.map { 20 | it.copy(tickPosition = (it.tickPosition * factor).roundToLong(), bpm = it.bpm * factor) 21 | } 22 | val timeSignatures = 23 | timeSignatures 24 | .map { it.copy(measurePosition = (it.measurePosition * factor).roundToInt()) } 25 | .fold(listOf()) { acc, timeSignature -> 26 | if (acc.isEmpty()) { 27 | listOf(timeSignature) 28 | } else { 29 | val prev = acc.last() 30 | if (prev.measurePosition == timeSignature.measurePosition) { 31 | acc 32 | } else { 33 | acc + timeSignature 34 | } 35 | } 36 | } 37 | 38 | return copy(tracks = tracks, tempos = tempos, timeSignatures = timeSignatures) 39 | } 40 | 41 | private fun Track.zoom(factor: Double): Track { 42 | val notes = 43 | notes.map { 44 | it.copy( 45 | tickOn = (it.tickOn * factor).roundToLong(), 46 | tickOff = (it.tickOff * factor).roundToLong(), 47 | ) 48 | } 49 | val pitch = 50 | pitch?.copy( 51 | data = 52 | pitch.data.map { (tick, value) -> 53 | (tick * factor).roundToLong() to value 54 | }, 55 | ) 56 | return copy(notes = notes, pitch = pitch) 57 | } 58 | 59 | val projectZoomFactorOptions = listOf("2", "5/3", "3/2", "4/3", "6/5", "4/5", "3/4", "3/5", "1/2") 60 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/RdpSimplification.kt: -------------------------------------------------------------------------------- 1 | package core.process 2 | 3 | /* 4 | * The Ramer–Douglas–Peucker algorithm is a line simplification algorithm 5 | * for reducing the number of points used to define its shape. 6 | * 7 | * Wikipedia: https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm 8 | * Implementation reference: https://rosettacode.org/wiki/Ramer-Douglas-Peucker_line_simplification 9 | * */ 10 | 11 | // As UtaFormatix use Pair to describe pitch point, so... 12 | private typealias Point = Pair 13 | 14 | private fun perpendicularDistance( 15 | pt: Point, 16 | lineStart: Point, 17 | lineEnd: Point, 18 | ): Double { 19 | var dx = (lineEnd.first - lineStart.first).toDouble() 20 | var dy = lineEnd.second - lineStart.second 21 | 22 | // Normalize 23 | val mag = kotlin.math.hypot(dx, dy) 24 | if (mag > 0.0) { 25 | dx /= mag 26 | dy /= mag 27 | } 28 | val pvx = pt.first - lineStart.first 29 | val pvy = pt.second - lineStart.second 30 | 31 | // Get dot product (project pv onto normalized direction) 32 | val pvdot = dx * pvx + dy * pvy 33 | 34 | // Scale line direction vector and subtract it from pv 35 | val ax = pvx - pvdot * dx 36 | val ay = pvy - pvdot * dy 37 | 38 | return kotlin.math.hypot(ax, ay) 39 | } 40 | 41 | /** 42 | * The RDP(Ramer–Douglas–Peucker) simplification algorithm for line based shape. 43 | * Slightly modified from rosettacode.org's implementation. 44 | * @param pointList Points that form the shape, connected by lines. 45 | * @param epsilon Defines how much the algorithm should simplify. Bigger value leads to more point to drop. 46 | */ 47 | fun simplifyShape( 48 | pointList: List, 49 | epsilon: Double, 50 | ): List { 51 | if (pointList.size < 2) return pointList 52 | 53 | // Find the point with the maximum distance from line between start and end 54 | var dmax = 0.0 55 | var index = 0 56 | val end = pointList.size - 1 57 | for (i in 1 until end) { 58 | val d = perpendicularDistance(pointList[i], pointList[0], pointList[end]) 59 | if (d > dmax) { 60 | index = i 61 | dmax = d 62 | } 63 | } 64 | 65 | // If max distance is greater than epsilon, recursively simplify 66 | return if (dmax > epsilon) { 67 | val firstLine = pointList.take(index + 1) 68 | val lastLine = pointList.drop(index) 69 | val recResults1 = simplifyShape(firstLine, epsilon) 70 | val recResults2 = simplifyShape(lastLine, epsilon) 71 | 72 | // build the result list 73 | listOf(recResults1.take(recResults1.size - 1), recResults2).flatten() 74 | } else { 75 | // Just return start and end points 76 | listOf(pointList.first(), pointList.last()) 77 | } 78 | } 79 | 80 | /** 81 | * Simplify given shape, which should later be described by [maxPointCount] (or less) points. 82 | * Note that this function will simplify with a small epsilon anyway even your input count is satisfied. 83 | * Using [simplifyShape], which implements The RDP(Ramer–Douglas–Peucker) algorithm. 84 | * */ 85 | fun simplifyShapeTo( 86 | pointList: List, 87 | maxPointCount: Long, 88 | ): List { 89 | /* As sometimes we will have pit data that is less than 50 points, but way too dense for mode2 ust 90 | So we commented this check here to make sure data will be simplified least with epsilon = step. */ 91 | val step = 0.05 92 | var epsilon = step 93 | while (true) { 94 | val result = simplifyShape(pointList, epsilon) 95 | if (result.count() < maxPointCount) return result 96 | epsilon += step 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/Resampling.kt: -------------------------------------------------------------------------------- 1 | package core.process 2 | 3 | fun List>.resampled( 4 | interval: Long, 5 | interpolateMethod: ( 6 | prev: Pair?, 7 | next: Pair?, 8 | pos: Long, 9 | ) -> Double?, 10 | ): List> { 11 | val result: MutableList> = mutableListOf() 12 | val leftBound = this.minOfOrNull { it.first } ?: 0 13 | val rightBound = this.maxOfOrNull { it.first } ?: 0 14 | for (current in leftBound..rightBound step interval) { 15 | val prev = this.lastOrNull { it.first <= current } 16 | val next = this.firstOrNull { it.first >= current } 17 | result.add(Pair(current, interpolateMethod(prev, next, current))) 18 | } 19 | return result.toList() 20 | } 21 | 22 | /** 23 | * A shorthand for pitch represented in dot. 24 | * Its interpolateMethod simply copy the value from prev, or next if prev not exists. 25 | */ 26 | fun List>.dotResampled(interval: Long) = 27 | resampled(interval) { prev, next, _ -> 28 | prev?.second ?: next?.second 29 | } 30 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/lyrics/LyricsMapping.kt: -------------------------------------------------------------------------------- 1 | package core.process.lyrics 2 | 3 | import core.model.Note 4 | import core.model.Project 5 | import core.model.Track 6 | import core.process.validateNotes 7 | import kotlinx.serialization.Serializable 8 | import kotlinx.serialization.Transient 9 | 10 | @Serializable 11 | data class LyricsMappingRequest( 12 | val mapText: String = "", 13 | val mapToPhonemes: Boolean = false, 14 | ) { 15 | val isValid get() = map.isNotEmpty() 16 | 17 | @Transient 18 | val map = 19 | mapText 20 | .lines() 21 | .mapNotNull { line -> 22 | if (line.contains("=").not()) return@mapNotNull null 23 | val from = line.substringBefore("=").trim() 24 | val to = line.substringAfter("=").trim() 25 | from to to 26 | }.toMap() 27 | 28 | companion object { 29 | fun findPreset(name: String) = Presets.find { it.first == name }?.second 30 | 31 | fun getPreset(name: String) = requireNotNull(findPreset(name)) 32 | 33 | val Presets: List> by lazy { 34 | listOf( 35 | "VX-β 日本語かな変換" to 36 | LyricsMappingRequest( 37 | mapText = core.external.Resources.lyricsMappingVxBetaJaText, 38 | mapToPhonemes = false, 39 | ), 40 | ) 41 | } 42 | } 43 | } 44 | 45 | fun Project.mapLyrics(request: LyricsMappingRequest) = 46 | copy( 47 | tracks = tracks.map { it.replaceLyrics(request) }, 48 | ) 49 | 50 | private fun Track.replaceLyrics(request: LyricsMappingRequest) = 51 | copy( 52 | notes = 53 | notes 54 | .mapNotNull { note -> note.replaceLyrics(request).takeIf { it.lyric.isNotEmpty() } } 55 | .validateNotes(), 56 | ) 57 | 58 | private fun Note.replaceLyrics(request: LyricsMappingRequest): Note { 59 | val mappedValue = request.map[this.lyric] ?: this.lyric 60 | return if (request.mapToPhonemes) { 61 | this.copy(phoneme = mappedValue) 62 | } else { 63 | this.copy(lyric = mappedValue) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/lyrics/chinese/Conversion.kt: -------------------------------------------------------------------------------- 1 | package core.process.lyrics.chinese 2 | 3 | import core.external.Resources 4 | import core.model.Project 5 | 6 | fun convertChineseLyricsToPinyin(project: Project): Project { 7 | val tracks = 8 | project.tracks.map { track -> 9 | track.copy( 10 | notes = 11 | track.notes.map { note -> 12 | note.copy(lyric = note.lyric.toPinyin()) 13 | }, 14 | ) 15 | } 16 | return project.copy(tracks = tracks) 17 | } 18 | 19 | private fun String.toPinyin(): String = dictionary[this] ?: this 20 | 21 | private val dictionary by lazy { 22 | Resources.chineseLyricsDictionaryText 23 | .lines() 24 | .map { it.split(",") } 25 | .associate { it[0] to it[1] } 26 | } 27 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/lyrics/japanese/Analysis.kt: -------------------------------------------------------------------------------- 1 | package core.process.lyrics.japanese 2 | 3 | import core.model.JapaneseLyricsType 4 | import core.model.JapaneseLyricsType.KanaCv 5 | import core.model.JapaneseLyricsType.KanaVcv 6 | import core.model.JapaneseLyricsType.RomajiCv 7 | import core.model.JapaneseLyricsType.RomajiVcv 8 | import core.model.JapaneseLyricsType.Unknown 9 | import core.model.Note 10 | import core.model.Project 11 | import core.model.Track 12 | 13 | fun analyseJapaneseLyricsTypeForProject(project: Project): JapaneseLyricsType { 14 | if (project.tracks.isEmpty()) return Unknown 15 | 16 | val maxNoteCountInAllTracks = 17 | project.tracks 18 | .maxByOrNull { it.notes.count() }!! 19 | .notes 20 | .count() 21 | val minNoteCountForAvailableTrack = maxNoteCountInAllTracks * MIN_NOTE_RATIO_FOR_AVAILABLE_TRACK 22 | 23 | val availableResults = 24 | project.tracks 25 | .filter { it.notes.count() >= minNoteCountForAvailableTrack } 26 | .map { analyseLyricsTypeForTrack(it) } 27 | .distinct() 28 | .filter { it != Unknown } 29 | 30 | return if (availableResults.count() > 1) { 31 | Unknown 32 | } else { 33 | availableResults.firstOrNull() ?: Unknown 34 | } 35 | } 36 | 37 | private fun analyseLyricsTypeForTrack(track: Track): JapaneseLyricsType { 38 | val total = track.notes.count() 39 | val types = track.notes.map { checkNoteType(it) } 40 | val typePercentages = 41 | JapaneseLyricsType 42 | .values() 43 | .map { type -> type to types.count { it == type } } 44 | .map { it.first to (it.second.toDouble() / total) } 45 | return typePercentages 46 | .find { it.second > MIN_RELIABLE_PERCENTAGE } 47 | ?.first 48 | ?: Unknown 49 | } 50 | 51 | private fun checkNoteType(note: Note): JapaneseLyricsType { 52 | var lyric = note.lyric 53 | // Suffix (e.g. "_B4") 54 | if (lyric.contains("_")) { 55 | lyric = lyric.substring(0, lyric.indexOf("_")) 56 | } 57 | if (lyric.contains(" ")) { 58 | val mainLyric = lyric.substring(lyric.indexOf(" ") + 1) 59 | if (mainLyric.isKana) return KanaVcv 60 | if (mainLyric.isRomaji) return RomajiVcv 61 | } else { 62 | if (lyric.isKana) return KanaCv 63 | if (lyric.isRomaji) return RomajiCv 64 | } 65 | return Unknown 66 | } 67 | 68 | private const val MIN_RELIABLE_PERCENTAGE = 0.7 69 | private const val MIN_NOTE_RATIO_FOR_AVAILABLE_TRACK = 0.1 70 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/lyrics/japanese/Cleanup.kt: -------------------------------------------------------------------------------- 1 | package core.process.lyrics.japanese 2 | 3 | import core.model.JapaneseLyricsType 4 | import core.model.JapaneseLyricsType.KanaCv 5 | import core.model.JapaneseLyricsType.KanaVcv 6 | import core.model.JapaneseLyricsType.RomajiCv 7 | import core.model.JapaneseLyricsType.RomajiVcv 8 | import core.model.JapaneseLyricsType.Unknown 9 | import core.model.Track 10 | 11 | fun cleanupJapaneseLyrics( 12 | tracks: List, 13 | type: JapaneseLyricsType, 14 | ) = when (type) { 15 | Unknown -> tracks 16 | RomajiCv -> tracks.cleanupJapaneseLyrics { it.cleanupAsRomajiCV() } 17 | RomajiVcv -> tracks.cleanupJapaneseLyrics { it.cleanupAsRomajiVCV() } 18 | KanaCv -> tracks.cleanupJapaneseLyrics { it.cleanupAsKanaCV() } 19 | KanaVcv -> tracks.cleanupJapaneseLyrics { it.cleanupAsKanaVCV() } 20 | } 21 | 22 | private fun String.cleanupAsRomajiCV(): String { 23 | if (this.isEmpty()) return this 24 | 25 | var result = this.lowercase() 26 | result = result.trim() 27 | result = result.trimStart('?') 28 | 29 | val maxLength = romajis.maxOfOrNull { it.length } ?: 0 30 | for (length in maxLength downTo 1) { 31 | val text = result.take(length) 32 | if (text.isRomaji) result = text 33 | break 34 | } 35 | 36 | return result 37 | } 38 | 39 | private fun String.cleanupAsRomajiVCV(): String { 40 | if (this.isEmpty()) return this 41 | 42 | var result = this.lowercase() 43 | result = result.trim() 44 | 45 | if (!result.contains(" ")) { 46 | return this.cleanupAsRomajiCV() 47 | } 48 | 49 | val blankPos = result.indexOf(" ") 50 | var body = "" 51 | 52 | val maxLength = romajis.maxOfOrNull { it.length } ?: 0 53 | for (length in 1..maxLength) { 54 | val startPos = blankPos + 1 55 | val endPos = startPos + length 56 | if (result.lastIndex < endPos - 1) break 57 | val text = result.substring(startPos, endPos) 58 | if (text.isRomaji) body = text 59 | break 60 | } 61 | 62 | val prefixChar = result[blankPos - 1] 63 | if (body.isNotEmpty() && prefixChar.isRomajiTail()) { 64 | result = "$prefixChar $body" 65 | } 66 | 67 | return result 68 | } 69 | 70 | private fun String.cleanupAsKanaCV(): String { 71 | if (this.isEmpty()) return this 72 | 73 | var result = this.trim() 74 | 75 | var text: String 76 | for (index in 0..result.lastIndex) { 77 | if (index + 2 <= result.lastIndex + 1) { 78 | text = result.substring(index, index + 2) 79 | if (!text.isKana) { 80 | text = result.substring(index, index + 1) 81 | } 82 | } else { 83 | text = result.substring(index, index + 1) 84 | } 85 | if (text.isKana) { 86 | result = text 87 | break 88 | } 89 | } 90 | 91 | return result 92 | } 93 | 94 | private fun String.cleanupAsKanaVCV(): String { 95 | if (this.isEmpty()) return this 96 | 97 | var result = this.trim() 98 | 99 | if (!result.contains(" ")) { 100 | return this.cleanupAsKanaCV() 101 | } 102 | 103 | val blankPos = result.indexOf(" ") 104 | val startPos = blankPos + 1 105 | var body: String 106 | 107 | if (startPos + 2 <= result.lastIndex + 1) { 108 | body = result.substring(startPos, startPos + 2) 109 | if (!body.isKana) { 110 | body = result.substring(startPos, startPos + 1) 111 | } 112 | } else { 113 | body = result.substring(startPos, startPos + 1) 114 | } 115 | 116 | val prefixChar = result[blankPos - 1] 117 | if (body.isKana && prefixChar.isRomajiTail()) { 118 | result = "$prefixChar $body" 119 | } 120 | 121 | return result 122 | } 123 | 124 | private fun List.cleanupJapaneseLyrics(noteCleanup: (String) -> String) = 125 | map { track -> 126 | track.copy( 127 | notes = 128 | track.notes.map { note -> 129 | note.copy(lyric = noteCleanup(note.lyric)) 130 | }, 131 | ) 132 | } 133 | 134 | private val romajiTails = listOf('a', 'i', 'u', 'e', 'o', 'n', '-') 135 | 136 | private fun Char.isRomajiTail(): Boolean = romajiTails.contains(this) 137 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/lyrics/japanese/Conversion.kt: -------------------------------------------------------------------------------- 1 | package core.process.lyrics.japanese 2 | 3 | import core.model.Format 4 | import core.model.JapaneseLyricsType 5 | import core.model.Note 6 | import core.model.Project 7 | import core.model.Track 8 | 9 | fun convertJapaneseLyrics( 10 | project: Project, 11 | targetType: JapaneseLyricsType, 12 | targetFormat: Format, 13 | ): Project { 14 | val sourceType = project.japaneseLyricsType 15 | var tracks = cleanupJapaneseLyrics(project.tracks, sourceType) 16 | when { 17 | sourceType.isRomaji && !targetType.isRomaji -> tracks = convertRomajiToKana(tracks) 18 | !sourceType.isRomaji && targetType.isRomaji -> tracks = convertKanaToRomaji(tracks) 19 | } 20 | when { 21 | sourceType.isCV && !targetType.isCV -> tracks = convertCVToVCV(tracks) 22 | !sourceType.isCV && targetType.isCV -> tracks = convertVCVToCV(tracks) 23 | } 24 | if (targetFormat == Format.Ust) tracks = convertVowelConnections(tracks, targetType) 25 | return project.copy(tracks = tracks) 26 | } 27 | 28 | private fun convertRomajiToKana(tracks: List) = 29 | tracks.convertBetweenRomajiAndKana { 30 | it.toKana() 31 | } 32 | 33 | private fun convertKanaToRomaji(tracks: List) = 34 | tracks.convertBetweenRomajiAndKana { 35 | it.toRomaji() 36 | } 37 | 38 | private fun List.convertBetweenRomajiAndKana(conversion: (String) -> String) = 39 | convertJapaneseLyrics { notes -> 40 | notes.map { note -> 41 | val lyric = note.lyric 42 | if (lyric.contains(" ")) { 43 | val blankPos = lyric.indexOf(" ") 44 | val head = lyric.substring(0, blankPos) 45 | val body = lyric.substring(blankPos + 1) 46 | note.copy(lyric = "$head ${conversion(body)}") 47 | } else { 48 | note.copy(lyric = conversion(lyric)) 49 | } 50 | } 51 | } 52 | 53 | private fun convertCVToVCV(tracks: List) = 54 | tracks.convertJapaneseLyrics { notes -> 55 | val result = notes.toMutableList() 56 | var lastTail = "-" 57 | for (i in result.indices) { 58 | var tail = lastTail 59 | if (i > 0 && result[i].tickOn > result[i - 1].tickOff) { 60 | tail = "-" 61 | } 62 | when { 63 | result[i].lyric.isKana -> { 64 | lastTail = result[i].lyric.toRomaji().takeLast(1) 65 | result[i] = result[i].copy(lyric = "$tail ${result[i].lyric}") 66 | } 67 | result[i].lyric.isRomaji -> { 68 | lastTail = result[i].lyric.takeLast(1) 69 | result[i] = result[i].copy(lyric = "$tail ${result[i].lyric}") 70 | } 71 | else -> { 72 | lastTail = "-" 73 | } 74 | } 75 | } 76 | result.toList() 77 | } 78 | 79 | private fun convertVCVToCV(tracks: List) = 80 | tracks.convertJapaneseLyrics { notes -> 81 | notes.map { note -> 82 | val lyric = note.lyric 83 | if (!lyric.contains(" ")) return@map note 84 | 85 | val blankPos = lyric.indexOf(" ") 86 | val body = lyric.substring(blankPos + 1) 87 | if (body.isKana || body.isRomaji) { 88 | note.copy(lyric = body) 89 | } else { 90 | note 91 | } 92 | } 93 | } 94 | 95 | private fun List.convertJapaneseLyrics(conversion: (List) -> List) = 96 | map { track -> 97 | track.copy(notes = conversion(track.notes)) 98 | } 99 | 100 | private fun convertVowelConnections( 101 | tracks: List, 102 | targetType: JapaneseLyricsType, 103 | ): List { 104 | return tracks.map { track -> 105 | val lyrics = track.notes.map { it.lyric }.toMutableList() 106 | if (lyrics.size < 2) return@map track 107 | 108 | for (index in 1 until lyrics.size) { 109 | val currentLyric = lyrics[index] 110 | if (currentLyric !in listOf("-", "ー")) continue 111 | val previousLyric = lyrics[index - 1] 112 | val newCurrentLyric = 113 | if (targetType.isRomaji) { 114 | if (previousLyric.isRomaji) { 115 | previousLyric.takeLast(1) 116 | } else { 117 | null 118 | } 119 | } else { 120 | if (previousLyric.isKana) { 121 | findVowelKana(previousLyric) 122 | } else { 123 | null 124 | } 125 | } 126 | newCurrentLyric?.let { lyrics[index] = it } 127 | } 128 | val notes = track.notes.mapIndexed { index, note -> note.copy(lyric = lyrics[index]) } 129 | track.copy(notes = notes) 130 | } 131 | } 132 | 133 | private fun String.toRomaji() = 134 | kanaToRomaji 135 | .find { it.first == this } 136 | ?.second 137 | ?: this 138 | 139 | private fun String.toKana() = 140 | kanaToRomaji 141 | .find { it.second == this } 142 | ?.first 143 | ?: this 144 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/lyrics/japanese/Words.kt: -------------------------------------------------------------------------------- 1 | package core.process.lyrics.japanese 2 | 3 | val String.isKana get() = findKanaIndex(this) != null 4 | val String.isRomaji get() = findRomajiIndex(this) != null 5 | 6 | fun findKanaIndex(text: String) = kanas.indexOf(text).takeIf { it >= 0 } 7 | 8 | fun findRomajiIndex(text: String) = romajis.indexOf(text).takeIf { it >= 0 } 9 | 10 | fun findVowelKana(kana: String): String? { 11 | val romaji = kanaToRomaji.find { it.first == kana }?.second ?: return null 12 | val vowelRomaji = romaji.takeLast(1) 13 | return kanaToRomaji.find { it.second == vowelRomaji }?.first 14 | } 15 | 16 | val kanaToRomaji = 17 | listOf( 18 | "あ" to "a", 19 | "い" to "i", 20 | "いぇ" to "ye", 21 | "う" to "u", 22 | "わ" to "wa", 23 | "うぁ" to "wa", 24 | "うぁ" to "ua", 25 | "うぃ" to "wi", 26 | "うぃ" to "ui", 27 | "うぇ" to "we", 28 | "え" to "e", 29 | "お" to "o", 30 | "か" to "ka", 31 | "が" to "ga", 32 | "き" to "ki", 33 | "きぇ" to "kye", 34 | "きゃ" to "kya", 35 | "きゅ" to "kyu", 36 | "きょ" to "kyo", 37 | "ぎ" to "gi", 38 | "ぎぇ" to "gye", 39 | "ぎゃ" to "gya", 40 | "ぎゅ" to "gyu", 41 | "ぎょ" to "gyo", 42 | "く" to "ku", 43 | "くぁ" to "kua", 44 | "くぃ" to "kui", 45 | "くぇ" to "kue", 46 | "くぉ" to "kuo", 47 | "ぐ" to "gu", 48 | "ぐぁ" to "gua", 49 | "ぐぃ" to "gui", 50 | "ぐぇ" to "gue", 51 | "ぐぉ" to "guo", 52 | "け" to "ke", 53 | "げ" to "ge", 54 | "こ" to "ko", 55 | "ご" to "go", 56 | "さ" to "sa", 57 | "ざ" to "za", 58 | "し" to "shi", 59 | "し" to "si", 60 | "しぇ" to "she", 61 | "しぇ" to "sye", 62 | "しゃ" to "sha", 63 | "しゃ" to "sya", 64 | "しゅ" to "shu", 65 | "しゅ" to "syu", 66 | "しょ" to "sho", 67 | "しょ" to "syo", 68 | "じ" to "ji", 69 | "じぇ" to "je", 70 | "じぇ" to "jye", 71 | "じゃ" to "ja", 72 | "じゃ" to "jya", 73 | "じゅ" to "ju", 74 | "じゅ" to "jyu", 75 | "じょ" to "jo", 76 | "じょ" to "jyo", 77 | "す" to "su", 78 | "すぁ" to "sua", 79 | "すぃ" to "sui", 80 | "すぇ" to "sue", 81 | "すぉ" to "suo", 82 | "ず" to "zu", 83 | "ずぁ" to "zua", 84 | "ずぃ" to "zui", 85 | "ずぇ" to "zue", 86 | "ずぉ" to "zuo", 87 | "せ" to "se", 88 | "ぜ" to "ze", 89 | "そ" to "so", 90 | "ぞ" to "zo", 91 | "た" to "ta", 92 | "だ" to "da", 93 | "ち" to "chi", 94 | "ちぇ" to "che", 95 | "ちゃ" to "cha", 96 | "ちゅ" to "chu", 97 | "ちょ" to "cho", 98 | "つ" to "tsu", 99 | "つ" to "tu", 100 | "つぁ" to "tsa", 101 | "つぁ" to "tua", 102 | "つぃ" to "tsi", 103 | "つぃ" to "tui", 104 | "つぇ" to "tse", 105 | "つぇ" to "tue", 106 | "つぉ" to "tso", 107 | "つぉ" to "tuo", 108 | "て" to "te", 109 | "てぃ" to "ti", 110 | "てゅ" to "tyu", 111 | "で" to "de", 112 | "でぃ" to "di", 113 | "でゅ" to "dyu", 114 | "と" to "to", 115 | "とぅ" to "tu", 116 | "とぅ" to "twu", 117 | "ど" to "do", 118 | "どぅ" to "du", 119 | "どぅ" to "dwu", 120 | "な" to "na", 121 | "に" to "ni", 122 | "にぇ" to "nye", 123 | "にゃ" to "nya", 124 | "にゅ" to "nyu", 125 | "にょ" to "nyo", 126 | "ぬ" to "nu", 127 | "ぬぁ" to "nua", 128 | "ぬぃ" to "nui", 129 | "ぬぇ" to "nue", 130 | "ぬぉ" to "nuo", 131 | "ね" to "ne", 132 | "の" to "no", 133 | "は" to "ha", 134 | "ば" to "ba", 135 | "ぱ" to "pa", 136 | "ひ" to "hi", 137 | "ひぇ" to "hye", 138 | "ひゃ" to "hya", 139 | "ひゅ" to "hyu", 140 | "ひょ" to "hyo", 141 | "び" to "bi", 142 | "びぇ" to "bye", 143 | "びゃ" to "bya", 144 | "びゅ" to "byu", 145 | "びょ" to "byo", 146 | "ぴ" to "pi", 147 | "ぴぇ" to "pye", 148 | "ぴゃ" to "pya", 149 | "ぴゅ" to "pyu", 150 | "ぴょ" to "pyo", 151 | "ふ" to "fu", 152 | "ふぁ" to "fa", 153 | "ふぃ" to "fi", 154 | "ふぇ" to "fe", 155 | "ふぉ" to "fo", 156 | "ぶ" to "bu", 157 | "ぶぁ" to "bua", 158 | "ぶぃ" to "bui", 159 | "ぶぇ" to "bue", 160 | "ぶぉ" to "buo", 161 | "ぷ" to "pu", 162 | "ぷぁ" to "pua", 163 | "ぷぃ" to "pui", 164 | "ぷぇ" to "pue", 165 | "ぷぉ" to "puo", 166 | "へ" to "he", 167 | "べ" to "be", 168 | "ぺ" to "pe", 169 | "ほ" to "ho", 170 | "ぼ" to "bo", 171 | "ぽ" to "po", 172 | "ま" to "ma", 173 | "み" to "mi", 174 | "みぇ" to "mye", 175 | "みゃ" to "mya", 176 | "みゅ" to "myu", 177 | "みょ" to "myo", 178 | "む" to "mu", 179 | "むぁ" to "mua", 180 | "むぃ" to "mui", 181 | "むぇ" to "mue", 182 | "むぉ" to "muo", 183 | "め" to "me", 184 | "も" to "mo", 185 | "や" to "ya", 186 | "ゆ" to "yu", 187 | "よ" to "yo", 188 | "ら" to "ra", 189 | "り" to "ri", 190 | "りぇ" to "rye", 191 | "りゃ" to "rya", 192 | "りゅ" to "ryu", 193 | "りょ" to "ryo", 194 | "る" to "ru", 195 | "るぁ" to "rua", 196 | "るぃ" to "rui", 197 | "るぇ" to "rue", 198 | "るぉ" to "ruo", 199 | "れ" to "re", 200 | "ろ" to "ro", 201 | "わ" to "wa", 202 | "を" to "o", 203 | "うぉ" to "wo", 204 | "ん" to "n", 205 | "ー" to "-", 206 | ) 207 | 208 | val kanas = kanaToRomaji.map { it.first } 209 | val romajis = kanaToRomaji.map { it.second } 210 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/pitch/TickTimeTransformation.kt: -------------------------------------------------------------------------------- 1 | package core.process.pitch 2 | 3 | import core.model.TICKS_IN_BEAT 4 | import core.model.Tempo 5 | 6 | fun Double.bpmToSecPerTick() = 60.0 / TICKS_IN_BEAT / this 7 | 8 | class TickTimeTransformer( 9 | tempos: List, 10 | ) { 11 | private class Segment( 12 | val range: LongRange, 13 | val offset: Double, 14 | val secPerTick: Double, 15 | ) 16 | 17 | // a piecewise linear transformation from tick to sec 18 | // simplify the calculation here for every usage to reduce cost 19 | private val segments = 20 | (tempos.zipWithNext() + (tempos.last() to null)) 21 | .fold(listOf()) { acc, (thisTempo, nextTempo) -> 22 | val range = thisTempo.tickPosition until (nextTempo?.tickPosition ?: Long.MAX_VALUE) 23 | val rate = thisTempo.bpm.bpmToSecPerTick() 24 | val thisResult = 25 | if (acc.isEmpty()) { 26 | Segment(range, 0.0, rate) 27 | } else { 28 | val lastParams = acc.last() 29 | val offset = 30 | lastParams.offset + 31 | (lastParams.range.last + 1 - lastParams.range.first) * lastParams.secPerTick 32 | Segment(range, offset, rate) 33 | } 34 | acc + thisResult 35 | } 36 | 37 | fun tickToSec(tick: Long) = 38 | segments 39 | .firstOrNull { tick in it.range } 40 | .let { it ?: segments.first() } 41 | .let { it.offset + (tick - it.range.first) * it.secPerTick } 42 | 43 | fun tickToMilliSec(tick: Long) = tickToSec(tick) * 1000 44 | 45 | fun tickDistanceToSec( 46 | tickStart: Long, 47 | tickEnd: Long, 48 | ) = (tickToSec(tickEnd) - tickToSec(tickStart)) 49 | 50 | fun tickDistanceToMilliSec( 51 | tickStart: Long, 52 | tickEnd: Long, 53 | ) = tickDistanceToSec(tickStart, tickEnd) * 1000 54 | 55 | fun secToTick(sec: Double) = 56 | segments 57 | .lastOrNull { it.offset <= sec } 58 | .let { it ?: segments.first() } 59 | .let { ((sec - it.offset) / it.secPerTick).toLong() + it.range.first } 60 | 61 | fun milliSecToTick(milliSec: Double) = secToTick(milliSec / 1000.0) 62 | } 63 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/pitch/TimeUnitConversion.kt: -------------------------------------------------------------------------------- 1 | package core.process.pitch 2 | 3 | import core.model.TICKS_IN_BEAT 4 | 5 | fun milliSecFromTick( 6 | tick: Long, 7 | bpm: Double, 8 | ): Double = tick * 60000 / (bpm * TICKS_IN_BEAT) 9 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/pitch/UtauMode1PitchConversion.kt: -------------------------------------------------------------------------------- 1 | package core.process.pitch 2 | 3 | import core.io.Ust 4 | import core.model.Note 5 | import core.model.Pitch 6 | import core.process.dotResampled 7 | 8 | data class UtauMode1TrackPitchData( 9 | val notes: List, 10 | ) 11 | 12 | /** This class contains Utau Pitch Data in Mode1. 13 | * However, it ignores some parameters which are contained in the ust(mode1) file in purpose: 14 | * 15 | * PBType: it indicates which data format this file uses. It will always be treated as "5" 16 | * since UTAU only uses "OldData" in very early versions. 17 | * 18 | * PBStart: it indicates when the pitch data begins, and it will be negative when pitch data 19 | * begins from PreUtterance of the note, otherwise, it will be 0. Since Only old versions of utau 20 | * produce this kind of data, and it will not save this value in the ust (this field is introduced 21 | * in version 0.4.0), presuming it as 0 will make things much easier. 22 | * 23 | * Please notice that UTAU save its pitch data by cent, not semitone. This class will keep this behaviour. 24 | */ 25 | data class UtauMode1NotePitchData( 26 | val pitchPoints: List?, 27 | ) 28 | 29 | fun pitchFromUtauMode1Track( 30 | pitchData: UtauMode1TrackPitchData?, 31 | notes: List, 32 | ): Pitch? { 33 | pitchData ?: return null 34 | val notePitches = notes.zip(pitchData.notes) 35 | val pitchPoints = mutableListOf>() 36 | for ((note, notePitch) in notePitches) { 37 | notePitch?.pitchPoints?.let { data -> 38 | pitchPoints.addAll( 39 | data.mapIndexed { index, value -> 40 | Pair(note.tickOn + index * Ust.MODE1_PITCH_SAMPLING_INTERVAL_TICK, value / 100) 41 | }, 42 | ) 43 | } 44 | } 45 | return Pitch(pitchPoints, false).getAbsoluteData(notes)?.let { Pitch(it, true) } 46 | } 47 | 48 | fun pitchToUtauMode1Track( 49 | pitch: Pitch?, 50 | notes: List, 51 | ): UtauMode1TrackPitchData? { 52 | pitch ?: return null 53 | return UtauMode1TrackPitchData( 54 | notes.map { note -> 55 | UtauMode1NotePitchData( 56 | pitch 57 | .getAbsoluteData(notes) 58 | ?.filter { it.first >= note.tickOn && it.first < note.tickOff } 59 | ?.dotResampled(Ust.MODE1_PITCH_SAMPLING_INTERVAL_TICK) 60 | ?.map { Pair(it.first, it.second ?: note.key.toDouble()) } 61 | ?.map { (it.second - note.key) * 100 }, 62 | ) 63 | }, 64 | ) 65 | } 66 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/pitch/UtauVibratoConversion.kt: -------------------------------------------------------------------------------- 1 | package core.process.pitch 2 | 3 | import core.model.Note 4 | import kotlin.math.PI 5 | import kotlin.math.sin 6 | 7 | /** 8 | * Shared by processes for OpenUtau and Utau mode2 9 | */ 10 | 11 | data class UtauNoteVibratoParams( 12 | val length: Double, // percentage of the note's length 13 | val period: Double, // milliSec 14 | val depth: Double, // cent 15 | val fadeIn: Double, // percentage of the vibrato's length 16 | val fadeOut: Double, // percentage of the vibrato's length 17 | val phaseShift: Double, // percentage of period 18 | val shift: Double, // percentage of depth 19 | ) 20 | 21 | fun List>.appendUtauNoteVibrato( 22 | vibratoParams: UtauNoteVibratoParams?, 23 | thisNote: Note, 24 | tickTimeTransformer: TickTimeTransformer, 25 | sampleIntervalTick: Long, 26 | ): List> { 27 | vibratoParams ?: return this 28 | 29 | // x-axis: milliSec, y-axis: 100cents 30 | val noteLength = tickTimeTransformer.tickDistanceToMilliSec(tickStart = thisNote.tickOn, tickEnd = thisNote.tickOff) 31 | val vibratoLength = noteLength * vibratoParams.length / 100 32 | if (vibratoLength <= 0) return this 33 | val frequency = 1.0 / vibratoParams.period 34 | if (frequency.isFinite().not()) return this 35 | val depth = vibratoParams.depth / 100 36 | if (depth <= 0) return this 37 | val easeInLength = noteLength * vibratoParams.fadeIn / 100 38 | val easeOutLength = noteLength * vibratoParams.fadeOut / 100 39 | val phase = vibratoParams.phaseShift / 100 40 | val shift = vibratoParams.shift / 100 41 | 42 | val start = noteLength - vibratoLength 43 | val vibrato = { t: Double -> 44 | if (t < start) { 45 | 0.0 46 | } else { 47 | val easeInFactor = 48 | ((t - start) / easeInLength) 49 | .coerceIn(0.0..1.0) 50 | .takeIf { it.isFinite() } ?: 1.0 51 | val easeOutFactor = 52 | ((noteLength - t) / easeOutLength) 53 | .coerceIn(0.0..1.0) 54 | .takeIf { it.isFinite() } ?: 1.0 55 | val x = 2 * PI * (frequency * (t - start) - phase) 56 | depth * easeInFactor * easeOutFactor * (sin(x) + shift) 57 | } 58 | } 59 | 60 | val noteStartInMillis = tickTimeTransformer.tickToMilliSec(thisNote.tickOn) 61 | 62 | // get approximate interval for interpolation 63 | val sampleIntervalInMillis = 64 | tickTimeTransformer.tickDistanceToMilliSec( 65 | tickStart = thisNote.tickOn, 66 | tickEnd = thisNote.tickOn + sampleIntervalTick, 67 | ) 68 | 69 | return map { (tickTimeTransformer.tickToMilliSec(it.first) - noteStartInMillis) to it.second } 70 | .fold(listOf>()) { acc, inputPoint -> 71 | val lastPoint = acc.lastOrNull() 72 | val newPoint = inputPoint.let { it.first to (it.second + vibrato(it.first)) } 73 | if (lastPoint == null) { 74 | acc + newPoint 75 | } else { 76 | val interpolatedXs = mutableListOf() 77 | var pos = lastPoint.first + sampleIntervalInMillis 78 | while (pos < newPoint.first) { 79 | interpolatedXs.add(pos) 80 | pos += sampleIntervalInMillis 81 | } 82 | val interpolatedPoints = interpolatedXs.map { x -> x to (lastPoint.second + vibrato(x)) } 83 | acc + interpolatedPoints + newPoint 84 | } 85 | }.map { (milliSecFromNoteStart, value) -> 86 | val tick = tickTimeTransformer.milliSecToTick(milliSecFromNoteStart + noteStartInMillis) 87 | tick to value 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/process/pitch/VocaloidPitchConversion.kt: -------------------------------------------------------------------------------- 1 | package core.process.pitch 2 | 3 | import core.model.Note 4 | import core.model.Pitch 5 | import core.process.pitch.VocaloidPartPitchData.Event 6 | import kotlin.math.abs 7 | import kotlin.math.ceil 8 | import kotlin.math.roundToInt 9 | 10 | data class VocaloidPartPitchData( 11 | val startPos: Long, 12 | val pit: List, 13 | val pbs: List, 14 | ) { 15 | data class Event( 16 | val pos: Long, 17 | val value: Int, 18 | ) { 19 | companion object { 20 | fun fromPair(pair: Pair) = Event(pair.first, pair.second) 21 | } 22 | } 23 | } 24 | 25 | private const val PITCH_MAX_VALUE = 8191 26 | private const val DEFAULT_PITCH_BEND_SENSITIVITY = 2 27 | private const val MIN_BREAK_LENGTH_BETWEEN_PITCH_SECTIONS = 480L 28 | private const val BORDER_APPEND_RADIUS = 5L 29 | 30 | fun pitchFromVocaloidParts(dataByParts: List): Pitch? { 31 | val pitchRawDataByPart = 32 | dataByParts.map { part -> 33 | val pit = part.pit 34 | val pbs = part.pbs 35 | val pitMultipliedByPbs = mutableMapOf() 36 | var pitIndex = 0 37 | var pbsCurrentValue = DEFAULT_PITCH_BEND_SENSITIVITY 38 | for (pbsEvent in pbs) { 39 | for (i in pitIndex..pit.lastIndex) { 40 | val pitEvent = pit[i] 41 | if (pitEvent.pos < pbsEvent.pos) { 42 | pitMultipliedByPbs[pitEvent.pos] = pitEvent.value * pbsCurrentValue 43 | if (i == pit.lastIndex) pitIndex = i 44 | } else { 45 | pitIndex = i 46 | break 47 | } 48 | } 49 | pbsCurrentValue = pbsEvent.value 50 | } 51 | if (pitIndex < pit.lastIndex) { 52 | for (i in pitIndex..pit.lastIndex) { 53 | val pitEvent = pit[i] 54 | pitMultipliedByPbs[pitEvent.pos] = pitEvent.value * pbsCurrentValue 55 | } 56 | } 57 | pitMultipliedByPbs.mapKeys { it.key + part.startPos } 58 | } 59 | val pitchRawData = 60 | pitchRawDataByPart 61 | .map { it.toList() } 62 | .fold(listOf>()) { accumulator, element -> 63 | val firstPos = element.firstOrNull()?.first 64 | if (firstPos == null) { 65 | accumulator 66 | } else { 67 | val firstInvalidIndexInPrevious = 68 | accumulator.indexOfFirst { it.first >= firstPos }.takeIf { it >= 0 } 69 | if (firstInvalidIndexInPrevious == null) { 70 | accumulator + element 71 | } else { 72 | accumulator.take(firstInvalidIndexInPrevious) + element 73 | } 74 | } 75 | } 76 | val data = 77 | pitchRawData.map { (pos, value) -> 78 | pos to value.toDouble() / PITCH_MAX_VALUE 79 | } 80 | return Pitch(data, isAbsolute = false).takeIf { it.data.isNotEmpty() } 81 | } 82 | 83 | fun Pitch.generateForVocaloid(notes: List): VocaloidPartPitchData? { 84 | val data = this.getRelativeData(notes, borderAppendRadius = BORDER_APPEND_RADIUS) ?: return null 85 | val pitchSectioned = mutableListOf>>() 86 | var currentPos = 0L 87 | for (pitchEvent in data) { 88 | when { 89 | pitchSectioned.isEmpty() -> pitchSectioned.add(mutableListOf(pitchEvent)) 90 | pitchEvent.first - currentPos >= MIN_BREAK_LENGTH_BETWEEN_PITCH_SECTIONS -> { 91 | pitchSectioned.add(mutableListOf(pitchEvent)) 92 | } 93 | else -> { 94 | pitchSectioned.last().add(pitchEvent) 95 | } 96 | } 97 | currentPos = pitchEvent.first 98 | } 99 | val pit = mutableListOf() 100 | val pbs = mutableListOf() 101 | for (section in pitchSectioned) { 102 | val maxAbsValue = section.maxOfOrNull { abs(it.second) } ?: 0.0 103 | var pbsForThisSection = ceil(abs(maxAbsValue)).toInt() 104 | if (pbsForThisSection > DEFAULT_PITCH_BEND_SENSITIVITY) { 105 | pbs.add(Event(section.first().first, pbsForThisSection)) 106 | pbs.add( 107 | Event( 108 | section.last().first + MIN_BREAK_LENGTH_BETWEEN_PITCH_SECTIONS / 2, 109 | DEFAULT_PITCH_BEND_SENSITIVITY, 110 | ), 111 | ) 112 | } else { 113 | pbsForThisSection = DEFAULT_PITCH_BEND_SENSITIVITY 114 | } 115 | section.forEach { (pitchPos, pitchValue) -> 116 | pit.add( 117 | Event( 118 | pitchPos, 119 | (pitchValue * PITCH_MAX_VALUE / pbsForThisSection) 120 | .roundToInt() 121 | .coerceIn(-PITCH_MAX_VALUE, PITCH_MAX_VALUE), 122 | ), 123 | ) 124 | } 125 | } 126 | return VocaloidPartPitchData( 127 | startPos = 0, 128 | pit = pit, 129 | pbs = pbs, 130 | ) 131 | } 132 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/util/ArrayBufferReader.kt: -------------------------------------------------------------------------------- 1 | package core.util 2 | 3 | import org.khronos.webgl.ArrayBuffer 4 | import org.khronos.webgl.Int32Array 5 | import org.khronos.webgl.Uint8Array 6 | import org.khronos.webgl.get 7 | 8 | class ArrayBufferReader( 9 | private val buffer: ArrayBuffer, 10 | ) { 11 | var index = 0 12 | private set 13 | 14 | fun skip(length: Int) { 15 | index += length 16 | } 17 | 18 | fun readInt(): Int = 19 | Int32Array(buffer.slice(index, index + 4))[0].also { 20 | index += 4 21 | } 22 | 23 | fun readBytes(): Uint8Array { 24 | val length = readInt() 25 | return Uint8Array(buffer.slice(index, index + length)).also { 26 | index += length 27 | } 28 | } 29 | 30 | fun readString(): String { 31 | val bytes = readBytes() 32 | return ByteArray(bytes.byteLength) { bytes[it] }.decodeToString() 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/util/ByteExtension.kt: -------------------------------------------------------------------------------- 1 | package core.util 2 | 3 | import core.exception.ValueTooLargeException 4 | import org.khronos.webgl.ArrayBuffer 5 | import org.khronos.webgl.DataView 6 | import org.khronos.webgl.Uint8Array 7 | import org.khronos.webgl.get 8 | 9 | fun MutableList.addBlock( 10 | block: List, 11 | littleEndian: Boolean = true, 12 | lengthInVariableLength: Boolean = false, 13 | ) { 14 | if (lengthInVariableLength) { 15 | addIntVariableLengthBigEndian(block.count()) 16 | } else { 17 | addInt(block.count(), littleEndian) 18 | } 19 | addAll(block) 20 | } 21 | 22 | fun MutableList.addList( 23 | list: List>, 24 | littleEndian: Boolean = true, 25 | lengthInVariableLength: Boolean = false, 26 | ) { 27 | if (lengthInVariableLength) { 28 | addIntVariableLengthBigEndian(list.count()) 29 | } else { 30 | addInt(list.count(), littleEndian) 31 | } 32 | addAll(list.flatten()) 33 | } 34 | 35 | fun MutableList.addListBlock( 36 | list: List>, 37 | littleEndian: Boolean = true, 38 | lengthInVariableLength: Boolean = false, 39 | ) { 40 | val block = 41 | mutableListOf().apply { 42 | if (lengthInVariableLength) { 43 | addIntVariableLengthBigEndian(list.count()) 44 | } else { 45 | addInt(list.count(), littleEndian) 46 | } 47 | addAll(list.flatten()) 48 | } 49 | addBlock(block, littleEndian, lengthInVariableLength) 50 | } 51 | 52 | fun MutableList.addInt( 53 | int: Int, 54 | littleEndian: Boolean = true, 55 | ) { 56 | val view = DataView(ArrayBuffer(Int.SIZE_BYTES)) 57 | view.setInt32(0, int, littleEndian = littleEndian) 58 | addArrayBuffer(view.buffer) 59 | } 60 | 61 | fun MutableList.addIntVariableLengthBigEndian(int: Int) { 62 | val maximum = 268435455 // 2^28 - 1 63 | if (int >= maximum) { 64 | throw ValueTooLargeException(int.toString(), maximum.toString()) 65 | } 66 | if (int == 0) { 67 | addAll(listOf(0x00)) 68 | return 69 | } 70 | val bytes = mutableListOf() 71 | var rest = int 72 | while (rest > 0) { 73 | bytes.add(0, (rest % 128).toByte()) 74 | rest /= 128 75 | } 76 | for (i in 0 until bytes.count() - 1) { 77 | bytes[i] = (bytes[i] + 128).toByte() 78 | } 79 | addAll(bytes) 80 | } 81 | 82 | fun MutableList.addShort( 83 | short: Short, 84 | littleEndian: Boolean = true, 85 | ) { 86 | val view = DataView(ArrayBuffer(Short.SIZE_BYTES)) 87 | view.setInt16(0, short, littleEndian = littleEndian) 88 | addArrayBuffer(view.buffer) 89 | } 90 | 91 | fun MutableList.addString( 92 | string: String, 93 | littleEndian: Boolean = true, 94 | lengthInVariableLength: Boolean = false, 95 | ) { 96 | val bytes = string.encodeToByteArray().toList() 97 | if (lengthInVariableLength) { 98 | addIntVariableLengthBigEndian(bytes.count()) 99 | } else { 100 | addInt(bytes.count(), littleEndian) 101 | } 102 | addAll(bytes) 103 | } 104 | 105 | private fun MutableList.addArrayBuffer(buffer: ArrayBuffer) { 106 | val view = Uint8Array(buffer) 107 | (0 until view.length).forEach { 108 | add(view[it]) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/util/ChainCall.kt: -------------------------------------------------------------------------------- 1 | package core.util 2 | 3 | inline fun T.runIf( 4 | condition: Boolean, 5 | block: T.() -> T, 6 | ): T = if (condition) block(this) else this 7 | 8 | inline fun T.runIf( 9 | condition: T.() -> Boolean, 10 | block: T.() -> T, 11 | ): T = if (condition(this)) block(this) else this 12 | 13 | inline fun T.runIfAllNotNull( 14 | parameter1: R1?, 15 | parameter2: R2?, 16 | block: T.(R1, R2) -> T, 17 | ): T = if (parameter1 != null && parameter2 != null) block(parameter1, parameter2) else this 18 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/util/EncodingUtil.kt: -------------------------------------------------------------------------------- 1 | package core.util 2 | 3 | import core.external.Encoding 4 | 5 | fun String.asByteTypedArray() = indices.map { i -> this.asDynamic().charCodeAt(i) as Byte }.toTypedArray() 6 | 7 | fun String.encode(encoding: String) = Encoding.convert(asByteTypedArray(), encoding) 8 | 9 | fun Array.decode(encoding: String) = 10 | this.let { bytes -> 11 | val convertedBytes = Encoding.convert(bytes, "UTF8", encoding) 12 | ByteArray(convertedBytes.size) { convertedBytes[it] }.decodeToString() 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/util/File.kt: -------------------------------------------------------------------------------- 1 | package core.util 2 | 3 | import core.exception.CannotReadFileException 4 | import kotlinx.browser.document 5 | import org.khronos.webgl.ArrayBuffer 6 | import org.w3c.dom.HTMLInputElement 7 | import org.w3c.files.File 8 | import org.w3c.files.FileList 9 | import org.w3c.files.FileReader 10 | import org.w3c.files.get 11 | import kotlin.coroutines.resume 12 | import kotlin.coroutines.resumeWithException 13 | import kotlin.coroutines.suspendCoroutine 14 | 15 | val File.nameWithoutExtension: String 16 | get() { 17 | val name = this.name 18 | val lastPointIndex = name.lastIndexOf(".").takeIf { it > 0 } ?: return name 19 | return name.substring(0, lastPointIndex) 20 | } 21 | 22 | val File.extensionName: String 23 | get() { 24 | return if (!name.contains('.')) { 25 | "" 26 | } else { 27 | name.split(".").last().lowercase() 28 | } 29 | } 30 | 31 | suspend fun waitFileSelection( 32 | accept: String, 33 | multiple: Boolean, 34 | ): List = 35 | suspendCoroutine { cont -> 36 | val input = document.createElement("input") as HTMLInputElement 37 | input.type = "file" 38 | input.multiple = multiple 39 | input.accept = accept 40 | input.onchange = { 41 | cont.resume(input.files?.toList().orEmpty()) 42 | } 43 | input.click() 44 | } 45 | 46 | suspend fun File.readText(encoding: String? = null): String = 47 | suspendCoroutine { cont -> 48 | val fileReader = FileReader() 49 | fileReader.onloadend = { 50 | val text = fileReader.result as String 51 | cont.resume(text) 52 | } 53 | 54 | fileReader.onerror = { 55 | cont.resumeWithException(CannotReadFileException()) 56 | } 57 | if (encoding == null) { 58 | fileReader.readAsText(this) 59 | } else { 60 | fileReader.readAsText(this, encoding) 61 | } 62 | } 63 | 64 | suspend fun File.readBinary() = 65 | suspendCoroutine> { cont -> 66 | val fileReader = FileReader() 67 | fileReader.onloadend = { 68 | cont.resume(fileReader.result) 69 | } 70 | fileReader.onerror = { 71 | cont.resumeWithException(CannotReadFileException()) 72 | } 73 | fileReader.readAsBinaryString(this) 74 | } 75 | 76 | suspend fun File.readAsArrayBuffer() = 77 | suspendCoroutine { cont -> 78 | val fileReader = FileReader() 79 | fileReader.onload = { 80 | cont.resume(fileReader.result) 81 | } 82 | fileReader.onerror = { 83 | cont.resumeWithException(CannotReadFileException()) 84 | } 85 | fileReader.readAsArrayBuffer(this) 86 | } 87 | 88 | fun FileList.toList(): List = (0 until length).mapNotNull { get(it) } 89 | 90 | fun getSafeFileName(name: String) = name.replace(Regex("[\\\\/:*?\"<>|]"), "") 91 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/util/MidiUtil.kt: -------------------------------------------------------------------------------- 1 | package core.util 2 | 3 | object MidiUtil { 4 | private const val STANDARD_TIME_DIVISION = 480 5 | 6 | fun convertInputTimeToStandardTime( 7 | inputTime: Int, 8 | timeDivision: Int, 9 | ): Int = inputTime * STANDARD_TIME_DIVISION / timeDivision 10 | 11 | enum class EventType( 12 | val value: Byte, 13 | ) { 14 | NoteOff(0x08), 15 | NoteOn(0x09), 16 | ; 17 | 18 | fun getStatusByte(channel: Int) = ((value.toInt() shl 4) or channel).toByte() 19 | } 20 | 21 | enum class MetaType( 22 | val value: Byte, 23 | ) { 24 | Text(0x01), 25 | TrackName(0x03), 26 | Lyric(0x05), 27 | Tempo(0x51), 28 | TimeSignature(0x58), 29 | EndOfTrack(0x2f), 30 | ; 31 | 32 | val eventHeaderBytes get() = listOf(0xff.toByte(), value) 33 | } 34 | 35 | fun convertMidiTempoToBpm(midiTempo: Int) = 36 | ((1000 * 1000 * 60 / midiTempo.toDouble()) * 100).toInt().toDouble() / 100 37 | 38 | fun convertBpmToMidiTempo(bpm: Double) = (1000 * 1000 * 60 / bpm).toInt() 39 | 40 | fun generateMidiTimeSignatureBytes( 41 | numerator: Int, 42 | denominator: Int, 43 | ): List = 44 | listOf( 45 | numerator.toByte(), 46 | kotlin.math 47 | .log2(denominator.toDouble()) 48 | .toInt() 49 | .toByte(), 50 | 0x18, 51 | 0x08, 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/util/Result.kt: -------------------------------------------------------------------------------- 1 | package core.util 2 | 3 | import kotlinx.coroutines.CancellationException 4 | 5 | inline fun T.runCatchingCancellable(block: T.() -> R): Result = 6 | try { 7 | Result.success(block()) 8 | } catch (e: Throwable) { 9 | if (e is CancellationException) { 10 | throw e 11 | } 12 | Result.failure(e) 13 | } 14 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/util/TextUtil.kt: -------------------------------------------------------------------------------- 1 | package core.util 2 | 3 | /** 4 | * Expose JavaScript Number.toFixed() 5 | * https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number/toFixed 6 | */ 7 | fun Double.toFixed(count: Int) = asDynamic().toFixed(count) as String 8 | 9 | fun Int.padStartZero(length: Int) = toString().padStart(length, '0') 10 | 11 | fun String.linesNotBlank() = this.lines().filter { it.isNotBlank() } 12 | 13 | fun String.splitFirst(separator: String): Pair { 14 | val index = indexOf(separator).takeIf { it >= 0 } ?: return this to "" 15 | return this.take(index) to this.drop(index + 1) 16 | } 17 | -------------------------------------------------------------------------------- /core/src/main/kotlin/core/util/XmlExtension.kt: -------------------------------------------------------------------------------- 1 | package core.util 2 | 3 | import core.exception.IllegalFileException 4 | import org.w3c.dom.Document 5 | import org.w3c.dom.Element 6 | import org.w3c.dom.Node 7 | import org.w3c.dom.get 8 | 9 | fun Element.getElementListByTagName( 10 | name: String, 11 | allowEmpty: Boolean = true, 12 | ) = getElementsByTagName(name).let { 13 | if (!allowEmpty && it.length == 0) { 14 | throw IllegalFileException.XmlElementNotFound(name) 15 | } else { 16 | (0 until it.length).map { index -> it[index]!! } 17 | } 18 | } 19 | 20 | fun Element.getSingleElementByTagName(name: String) = 21 | getElementsByTagName(name)[0] ?: throw IllegalFileException.XmlElementNotFound(name) 22 | 23 | fun Element.getSingleElementByTagNameOrNull(name: String) = getElementsByTagName(name)[0] 24 | 25 | fun Element.getRequiredAttributeAsInteger(attribute: String) = 26 | getAttribute(attribute)?.toIntOrNull() 27 | ?: throw IllegalFileException.XmlElementAttributeValueIllegal(attribute, tagName) 28 | 29 | fun Element.getRequiredAttributeAsLong(attribute: String) = 30 | getAttribute(attribute)?.toLongOrNull() 31 | ?: throw IllegalFileException.XmlElementAttributeValueIllegal(attribute, tagName) 32 | 33 | fun Element.getRequiredAttribute(attribute: String) = 34 | getAttribute(attribute) ?: throw IllegalFileException.XmlElementAttributeValueIllegal(attribute, tagName) 35 | 36 | val Element.innerValue 37 | get() = 38 | try { 39 | firstChild!!.nodeValue!! 40 | } catch (t: Throwable) { 41 | throw IllegalFileException.XmlElementValueIllegal(this.tagName) 42 | } 43 | 44 | val Element.innerValueOrNull get() = firstChild?.nodeValue 45 | 46 | fun Element.setSingleChildValue( 47 | name: String, 48 | value: Any, 49 | ) { 50 | getSingleElementByTagName(name).firstChild!!.nodeValue = value.toString() 51 | } 52 | 53 | fun Element.insertAfterThis(child: Element) = insertAdjacentElement("afterend", child) 54 | 55 | fun Element.clone() = cloneNode(true) as Element 56 | 57 | fun Document.appendNewChildTo( 58 | node: Node, 59 | localName: String, 60 | handler: (Element) -> Unit, 61 | ) = node.appendChild( 62 | createElement(localName).also(handler), 63 | ) 64 | -------------------------------------------------------------------------------- /core/src/main/resources/format_templates/template.ccs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | 27 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /core/src/main/resources/format_templates/template.musicxml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | Sinsy 8 | 9 | 10 | 11 | 12 | MusicXML Part 13 | 14 | 15 | 16 | 17 | 18 | 960 19 | 20 | 21 | 22 | 0 23 | major 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /core/src/main/resources/format_templates/template.s5p: -------------------------------------------------------------------------------- 1 | { 2 | "version": 7, 3 | "meter": [ 4 | { 5 | "measure": 0, 6 | "beatPerMeasure": 4, 7 | "beatGranularity": 4 8 | } 9 | ], 10 | "tempo": [ 11 | { 12 | "position": 0, 13 | "beatPerMinute": 120.0 14 | } 15 | ], 16 | "tracks": [ 17 | { 18 | "name": "Unnamed Track", 19 | "dbName": "", 20 | "color": "15e879", 21 | "displayOrder": 0, 22 | "dbDefaults": {}, 23 | "notes": [ 24 | { 25 | "onset": 0, 26 | "duration": 705600000, 27 | "lyric": "la", 28 | "comment": "", 29 | "pitch": 60 30 | } 31 | ], 32 | "gsEvents": null, 33 | "mixer": { 34 | "gainDecibel": 0.0, 35 | "pan": 0.0, 36 | "muted": false, 37 | "solo": false, 38 | "engineOn": true, 39 | "display": true 40 | }, 41 | "parameters": { 42 | "interval": 5512500, 43 | "pitchDelta": [ 44 | 0, 45 | 0 46 | ], 47 | "vibratoEnv": [ 48 | 0, 49 | 0 50 | ], 51 | "loudness": [ 52 | 0, 53 | 0 54 | ], 55 | "tension": [ 56 | 0, 57 | 0 58 | ], 59 | "breathiness": [ 60 | 0, 61 | 0 62 | ], 63 | "voicing": [ 64 | 0, 65 | 0 66 | ], 67 | "gender": [ 68 | 0, 69 | 0 70 | ] 71 | } 72 | } 73 | ], 74 | "instrumental": { 75 | "filename": "", 76 | "offset": 0.0 77 | }, 78 | "mixer": { 79 | "gainInstrumentalDecibel": 0.0, 80 | "gainVocalMasterDecibel": 0.0, 81 | "instrumentalMuted": false, 82 | "vocalMasterMuted": false 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /core/src/main/resources/format_templates/template.svp: -------------------------------------------------------------------------------- 1 | { 2 | "version":113, 3 | "time":{ 4 | "meter":[ 5 | { 6 | "index":0, 7 | "numerator":4, 8 | "denominator":4 9 | } 10 | ], 11 | "tempo":[ 12 | { 13 | "position":0, 14 | "bpm":120.0 15 | } 16 | ] 17 | }, 18 | "library":[ 19 | 20 | ], 21 | "tracks":[ 22 | { 23 | "name":"Track", 24 | "dispColor":"ff7db235", 25 | "dispOrder":0, 26 | "renderEnabled":false, 27 | "mixer":{ 28 | "gainDecibel":0.0, 29 | "pan":0.0, 30 | "mute":false, 31 | "solo":false, 32 | "display":true 33 | }, 34 | "mainGroup":{ 35 | "name":"main", 36 | "uuid":"6d21a9d4-1f0c-4534-ba57-bf8f1033b0e9", 37 | "parameters":{ 38 | "pitchDelta":{ 39 | "mode":"cubic", 40 | "points":[ 41 | 42 | ] 43 | }, 44 | "vibratoEnv":{ 45 | "mode":"cubic", 46 | "points":[ 47 | 48 | ] 49 | }, 50 | "loudness":{ 51 | "mode":"cubic", 52 | "points":[ 53 | 54 | ] 55 | }, 56 | "tension":{ 57 | "mode":"cubic", 58 | "points":[ 59 | 60 | ] 61 | }, 62 | "breathiness":{ 63 | "mode":"cubic", 64 | "points":[ 65 | 66 | ] 67 | }, 68 | "voicing":{ 69 | "mode":"cubic", 70 | "points":[ 71 | 72 | ] 73 | }, 74 | "gender":{ 75 | "mode":"cubic", 76 | "points":[ 77 | 78 | ] 79 | } 80 | }, 81 | "notes":[ 82 | { 83 | "onset":0, 84 | "duration":705600000, 85 | "lyrics":"la", 86 | "phonemes":"", 87 | "pitch":60, 88 | "attributes":{ 89 | 90 | } 91 | } 92 | ] 93 | }, 94 | "mainRef":{ 95 | "groupID":"6d21a9d4-1f0c-4534-ba57-bf8f1033b0e9", 96 | "blickOffset":0, 97 | "pitchOffset":0, 98 | "isInstrumental":false, 99 | "database":{ 100 | "name":"", 101 | "language":"", 102 | "phoneset":"" 103 | }, 104 | "audio":{ 105 | "filename":"", 106 | "duration":0.0 107 | }, 108 | "dictionary":"", 109 | "voice":{ 110 | 111 | } 112 | }, 113 | "groups":[ 114 | 115 | ] 116 | } 117 | ], 118 | "renderConfig":{ 119 | "destination":"./", 120 | "filename":"untitled", 121 | "numChannels":1, 122 | "aspirationFormat":"noAspiration", 123 | "bitDepth":16, 124 | "sampleRate":44100, 125 | "exportMixDown":true 126 | } 127 | } -------------------------------------------------------------------------------- /core/src/main/resources/format_templates/template.tssln.json: -------------------------------------------------------------------------------- 1 | [84,83,83,111,108,117,116,105,111,110,0,1,1,86,101,114,115,105,111,110,79,102,65,112,112,70,105,108,101,83,97,118,101,100,0,1,10,5,49,46,49,48,46,49,46,48,0,1,3,80,108,97,121,67,111,110,116,114,111,108,0,1,4,76,111,111,112,0,1,1,3,76,111,111,112,83,116,97,114,116,0,1,9,4,0,0,0,0,0,0,0,0,76,111,111,112,69,110,100,0,1,9,4,0,0,0,0,0,0,0,0,80,108,97,121,80,111,115,105,116,105,111,110,0,1,9,4,0,0,0,0,0,0,0,0,0,84,114,97,99,107,115,0,0,1,1,84,114,97,99,107,0,1,6,84,121,112,101,0,1,5,1,0,0,0,0,78,97,109,101,0,1,9,5,83,105,110,103,101,114,49,0,83,116,97,116,101,0,1,5,1,0,0,0,0,86,111,108,117,109,101,0,1,9,4,0,0,0,0,0,0,0,0,80,97,110,0,1,9,4,0,0,0,0,0,0,0,0,80,108,117,103,105,110,68,97,116,97,0,2,50,4,8,83,116,97,116,101,73,110,102,111,114,109,97,116,105,111,110,0,1,1,86,101,114,115,105,111,110,79,102,65,112,112,70,105,108,101,83,97,118,101,100,0,1,10,5,49,46,49,48,46,49,46,48,0,1,10,83,111,110,103,69,100,105,116,111,114,0,1,2,69,100,105,116,111,114,87,105,100,116,104,0,1,5,1,14,5,0,0,69,100,105,116,111,114,72,101,105,103,104,116,0,1,5,1,232,1,0,0,0,67,111,110,116,114,111,108,80,97,110,101,108,83,116,97,116,117,115,0,1,4,81,117,97,110,116,105,122,97,116,105,111,110,0,1,5,1,8,0,0,0,82,101,99,111,114,100,78,111,116,101,0,1,1,2,82,101,99,111,114,100,84,101,109,112,111,0,1,1,2,69,100,105,116,84,111,111,108,0,1,5,1,2,0,0,0,0,65,100,106,117,115,116,84,111,111,108,66,97,114,83,116,97,116,117,115,0,1,2,77,97,105,110,80,97,110,101,108,0,1,5,1,255,255,255,255,83,117,98,80,97,110,101,108,0,1,5,1,1,0,0,0,0,77,97,105,110,80,97,110,101,108,83,116,97,116,117,115,0,1,4,83,99,97,108,101,88,95,86,50,0,1,9,4,1,0,0,0,0,0,20,64,83,99,97,108,101,89,0,1,9,4,0,0,0,0,0,0,224,63,83,99,114,111,108,108,88,0,1,9,4,0,0,0,0,0,0,128,61,83,99,114,111,108,108,89,0,1,9,4,42,70,13,127,155,98,180,191,0,80,97,110,101,108,67,111,110,116,114,111,108,108,101,114,83,116,97,116,117,115,0,1,4,84,101,109,112,111,80,97,110,101,108,0,1,1,2,66,101,97,116,80,97,110,101,108,0,1,1,3,75,101,121,80,97,110,101,108,0,1,1,2,68,121,110,97,109,105,99,115,80,97,110,101,108,0,1,1,2,0,86,111,105,99,101,73,110,102,111,114,109,97,116,105,111,110,0,1,5,67,104,97,114,97,99,116,101,114,78,97,109,101,0,1,8,5,67,104,105,115,45,65,0,86,111,105,99,101,70,105,108,101,78,97,109,101,0,1,36,5,110,105,116,101,99,104,45,106,112,95,106,97,95,74,80,95,102,48,48,56,95,115,118,115,115,46,116,115,110,118,111,105,99,101,0,76,97,110,103,117,97,103,101,0,1,7,5,106,97,95,74,80,0,86,111,105,99,101,86,101,114,115,105,111,110,0,1,7,5,50,46,48,46,48,0,65,99,116,105,118,101,65,102,116,101,114,84,104,105,115,86,101,114,115,105,111,110,0,1,9,5,49,46,55,46,49,46,50,0,1,2,69,109,111,116,105,111,110,76,105,115,116,0,0,1,1,69,109,111,116,105,111,110,0,1,2,76,97,98,101,108,0,1,8,5,78,111,114,109,97,108,0,82,97,116,105,111,0,1,9,4,0,0,0,0,0,0,240,63,0,78,101,117,114,97,108,86,111,99,111,100,101,114,76,105,115,116,0,0,0,71,108,111,98,97,108,80,97,114,97,109,101,116,101,114,115,0,1,5,71,108,111,98,97,108,84,117,110,101,0,1,9,4,0,0,0,0,0,0,0,0,71,108,111,98,97,108,86,105,98,65,109,112,0,1,9,4,0,0,0,0,0,0,240,63,71,108,111,98,97,108,86,105,98,70,114,113,0,1,9,4,0,0,0,0,0,0,240,63,71,108,111,98,97,108,65,108,112,104,97,0,1,9,4,0,0,0,0,0,0,0,0,71,108,111,98,97,108,72,117,115,107,121,0,1,9,4,0,0,0,0,0,0,0,0,0,83,111,110,103,0,0,1,3,84,101,109,112,111,0,0,1,1,83,111,117,110,100,0,1,2,67,108,111,99,107,0,1,5,1,0,0,0,0,84,101,109,112,111,0,1,9,4,0,0,0,0,0,0,94,64,0,66,101,97,116,0,0,1,1,84,105,109,101,0,1,3,67,108,111,99,107,0,1,5,1,0,0,0,0,66,101,97,116,115,0,1,5,1,4,0,0,0,66,101,97,116,84,121,112,101,0,1,5,1,4,0,0,0,0,83,99,111,114,101,0,0,1,2,75,101,121,0,1,3,67,108,111,99,107,0,1,5,1,0,0,0,0,70,105,102,116,104,115,0,1,5,1,0,0,0,0,77,111,100,101,0,1,5,1,0,0,0,0,0,68,121,110,97,109,105,99,115,0,1,2,67,108,111,99,107,0,1,5,1,0,0,0,0,86,97,108,117,101,0,1,5,1,5,0,0,0,0,80,97,114,97,109,101,116,101,114,0,1,1,86,111,99,111,100,101,114,76,111,103,70,48,0,1,9,4,0,0,0,0,0,0,0,0,0,83,105,103,110,101,114,67,111,110,102,105,103,0,0,0,0,71,85,73,83,116,97,116,117,115,0,1,3,83,99,97,108,101,88,0,1,9,4,0,0,0,0,0,0,89,64,83,99,97,108,101,89,0,1,9,4,1,0,0,0,0,0,224,63,71,114,105,100,77,111,100,101,0,1,5,1,1,0,0,0,0] -------------------------------------------------------------------------------- /core/src/main/resources/format_templates/template.ustx: -------------------------------------------------------------------------------- 1 | name: New Project 2 | comment: '' 3 | output_dir: Vocal 4 | cache_dir: UCache 5 | ustx_version: 0.6 6 | resolution: 480 7 | bpm: 120 8 | beat_per_bar: 4 9 | beat_unit: 4 10 | time_signatures: 11 | - bar_position: 0 12 | beat_per_bar: 4 13 | beat_unit: 4 14 | tempos: 15 | - position: 0 16 | bpm: 120 17 | expressions: 18 | dyn: 19 | name: dynamics (curve) 20 | abbr: dyn 21 | type: Curve 22 | min: -240 23 | max: 120 24 | default_value: 0 25 | is_flag: false 26 | flag: '' 27 | pitd: 28 | name: pitch deviation (curve) 29 | abbr: pitd 30 | type: Curve 31 | min: -1200 32 | max: 1200 33 | default_value: 0 34 | is_flag: false 35 | flag: '' 36 | clr: 37 | name: voice color 38 | abbr: clr 39 | type: Options 40 | min: 0 41 | max: -1 42 | default_value: 0 43 | is_flag: false 44 | options: [] 45 | eng: 46 | name: resampler engine 47 | abbr: eng 48 | type: Options 49 | min: 0 50 | max: 1 51 | default_value: 0 52 | is_flag: false 53 | options: 54 | - '' 55 | - worldline 56 | vel: 57 | name: velocity 58 | abbr: vel 59 | type: Numerical 60 | min: 0 61 | max: 200 62 | default_value: 100 63 | is_flag: false 64 | flag: '' 65 | vol: 66 | name: volume 67 | abbr: vol 68 | type: Numerical 69 | min: 0 70 | max: 200 71 | default_value: 100 72 | is_flag: false 73 | flag: '' 74 | atk: 75 | name: attack 76 | abbr: atk 77 | type: Numerical 78 | min: 0 79 | max: 200 80 | default_value: 100 81 | is_flag: false 82 | flag: '' 83 | dec: 84 | name: decay 85 | abbr: dec 86 | type: Numerical 87 | min: 0 88 | max: 100 89 | default_value: 0 90 | is_flag: false 91 | flag: '' 92 | gen: 93 | name: gender 94 | abbr: gen 95 | type: Numerical 96 | min: -100 97 | max: 100 98 | default_value: 0 99 | is_flag: true 100 | flag: g 101 | genc: 102 | name: gender (curve) 103 | abbr: genc 104 | type: Curve 105 | min: -100 106 | max: 100 107 | default_value: 0 108 | is_flag: false 109 | flag: '' 110 | bre: 111 | name: breath 112 | abbr: bre 113 | type: Numerical 114 | min: 0 115 | max: 100 116 | default_value: 0 117 | is_flag: true 118 | flag: B 119 | brec: 120 | name: breathiness (curve) 121 | abbr: brec 122 | type: Curve 123 | min: -100 124 | max: 100 125 | default_value: 0 126 | is_flag: false 127 | flag: '' 128 | lpf: 129 | name: lowpass 130 | abbr: lpf 131 | type: Numerical 132 | min: 0 133 | max: 100 134 | default_value: 0 135 | is_flag: true 136 | flag: H 137 | mod: 138 | name: modulation 139 | abbr: mod 140 | type: Numerical 141 | min: 0 142 | max: 100 143 | default_value: 0 144 | is_flag: false 145 | flag: '' 146 | alt: 147 | name: alternate 148 | abbr: alt 149 | type: Numerical 150 | min: 0 151 | max: 16 152 | default_value: 0 153 | is_flag: false 154 | flag: '' 155 | shft: 156 | name: tone shift 157 | abbr: shft 158 | type: Numerical 159 | min: -36 160 | max: 36 161 | default_value: 0 162 | is_flag: false 163 | flag: '' 164 | shfc: 165 | name: tone shift (curve) 166 | abbr: shfc 167 | type: Curve 168 | min: -1200 169 | max: 1200 170 | default_value: 0 171 | is_flag: false 172 | flag: '' 173 | tenc: 174 | name: tension (curve) 175 | abbr: tenc 176 | type: Curve 177 | min: -100 178 | max: 100 179 | default_value: 0 180 | is_flag: false 181 | flag: '' 182 | voic: 183 | name: voicing (curve) 184 | abbr: voic 185 | type: Curve 186 | min: 0 187 | max: 100 188 | default_value: 100 189 | is_flag: false 190 | flag: '' 191 | tracks: 192 | - phonemizer: OpenUtau.Core.DefaultPhonemizer 193 | mute: false 194 | solo: false 195 | volume: 0 196 | voice_parts: 197 | - name: New Part 198 | comment: '' 199 | track_no: 0 200 | position: 1920 201 | notes: 202 | - position: 480 203 | duration: 480 204 | tone: 60 205 | lyric: a 206 | pitch: 207 | data: 208 | - {x: -1, y: 0, shape: io} 209 | - {x: 1, y: 0, shape: io} 210 | snap_first: true 211 | vibrato: {length: 0, period: 175, depth: 25, in: 10, out: 10, shift: 0, drift: 0} 212 | phoneme_expressions: [] 213 | phoneme_overrides: [] 214 | - position: 960 215 | duration: 480 216 | tone: 62 217 | lyric: a 218 | pitch: 219 | data: 220 | - {x: -1, y: -20, shape: io} 221 | - {x: 1, y: 0, shape: io} 222 | snap_first: true 223 | vibrato: {length: 0, period: 175, depth: 25, in: 10, out: 10, shift: 0, drift: 0} 224 | phoneme_expressions: [] 225 | phoneme_overrides: [] 226 | - position: 1440 227 | duration: 240 228 | tone: 62 229 | lyric: a 230 | pitch: 231 | data: 232 | - {x: -1, y: 0, shape: io} 233 | - {x: 1, y: 0, shape: io} 234 | snap_first: true 235 | vibrato: {length: 0, period: 175, depth: 25, in: 10, out: 10, shift: 0, drift: 0} 236 | phoneme_expressions: [] 237 | phoneme_overrides: [] 238 | curves: [] 239 | wave_parts: [] 240 | -------------------------------------------------------------------------------- /core/src/main/resources/format_templates/template.vprjson: -------------------------------------------------------------------------------- 1 | { 2 | "version": { 3 | "major": 5, 4 | "minor": 0, 5 | "revision": 0 6 | }, 7 | "vender": "Yamaha Corporation", 8 | "title": "title", 9 | "masterTrack": { 10 | "samplingRate": 44100, 11 | "loop": { 12 | "isEnabled": true, 13 | "begin": 0, 14 | "end": 7680 15 | }, 16 | "tempo": { 17 | "isFolded": false, 18 | "height": 0.0, 19 | "global": { 20 | "isEnabled": false, 21 | "value": 12000 22 | }, 23 | "events": [ 24 | { 25 | "pos": 0, 26 | "value": 12000 27 | } 28 | ] 29 | }, 30 | "timeSig": { 31 | "isFolded": false, 32 | "events": [ 33 | { 34 | "bar": 0, 35 | "numer": 4, 36 | "denom": 4 37 | } 38 | ] 39 | }, 40 | "volume": { 41 | "isFolded": false, 42 | "height": 0.0, 43 | "events": [ 44 | { 45 | "pos": 0, 46 | "value": 0 47 | } 48 | ] 49 | } 50 | }, 51 | "voices": [ 52 | { 53 | "compID": "BCXDC6CZLSZHZCB4", 54 | "name": "VY2V3" 55 | } 56 | ], 57 | "tracks": [ 58 | { 59 | "type": 0, 60 | "name": "1 VOCALOID", 61 | "color": 0, 62 | "busNo": 0, 63 | "isFolded": false, 64 | "height": 0.0, 65 | "volume": { 66 | "isFolded": true, 67 | "height": 40.0, 68 | "events": [ 69 | { 70 | "pos": 0, 71 | "value": 0 72 | } 73 | ] 74 | }, 75 | "panpot": { 76 | "isFolded": true, 77 | "height": 40.0, 78 | "events": [ 79 | { 80 | "pos": 0, 81 | "value": 0 82 | } 83 | ] 84 | }, 85 | "isMuted": false, 86 | "isSoloMode": false, 87 | "parts": [ 88 | { 89 | "pos": 0, 90 | "duration": 1920, 91 | "styleName": "No Effect", 92 | "voice": { 93 | "compID": "BCXDC6CZLSZHZCB4", 94 | "langID": 0 95 | }, 96 | "midiEffects": [ 97 | { 98 | "id": "SingingSkill", 99 | "isBypassed": true, 100 | "isFolded": false, 101 | "parameters": [ 102 | { 103 | "name": "Amount", 104 | "value": 5 105 | }, 106 | { 107 | "name": "Name", 108 | "value": "75F04D2B-D8E4-44b8-939B-41CD101E08FD" 109 | }, 110 | { 111 | "name": "Skill", 112 | "value": 5 113 | } 114 | ] 115 | }, 116 | { 117 | "id": "VoiceColor", 118 | "isBypassed": true, 119 | "isFolded": false, 120 | "parameters": [ 121 | { 122 | "name": "Air", 123 | "value": 0 124 | }, 125 | { 126 | "name": "Breathiness", 127 | "value": 0 128 | }, 129 | { 130 | "name": "Character", 131 | "value": 0 132 | }, 133 | { 134 | "name": "Exciter", 135 | "value": 0 136 | }, 137 | { 138 | "name": "Growl", 139 | "value": 0 140 | }, 141 | { 142 | "name": "Mouth", 143 | "value": 0 144 | } 145 | ] 146 | }, 147 | { 148 | "id": "RobotVoice", 149 | "isBypassed": true, 150 | "isFolded": false, 151 | "parameters": [ 152 | { 153 | "name": "Mode", 154 | "value": 1 155 | } 156 | ] 157 | }, 158 | { 159 | "id": "DefaultLyric", 160 | "isBypassed": true, 161 | "isFolded": false, 162 | "parameters": [ 163 | { 164 | "name": "CHS", 165 | "value": "a" 166 | }, 167 | { 168 | "name": "ENG", 169 | "value": "Ooh" 170 | }, 171 | { 172 | "name": "ESP", 173 | "value": "a" 174 | }, 175 | { 176 | "name": "JPN", 177 | "value": "あ" 178 | }, 179 | { 180 | "name": "KOR", 181 | "value": "아" 182 | } 183 | ] 184 | }, 185 | { 186 | "id": "Breath", 187 | "isBypassed": true, 188 | "isFolded": false, 189 | "parameters": [ 190 | { 191 | "name": "Exhalation", 192 | "value": 5 193 | }, 194 | { 195 | "name": "Mode", 196 | "value": 1 197 | }, 198 | { 199 | "name": "Type", 200 | "value": 0 201 | } 202 | ] 203 | } 204 | ], 205 | "notes": [ 206 | { 207 | "lyric": "あ", 208 | "phoneme": "a", 209 | "isProtected": false, 210 | "pos": 480, 211 | "duration": 480, 212 | "number": 54, 213 | "velocity": 64, 214 | "exp": { 215 | "opening": 127 216 | }, 217 | "singingSkill": { 218 | "duration": 158, 219 | "weight": { 220 | "pre": 64, 221 | "post": 64 222 | } 223 | }, 224 | "vibrato": { 225 | "type": 0, 226 | "duration": 0 227 | } 228 | } 229 | ] 230 | } 231 | ] 232 | } 233 | ] 234 | } 235 | -------------------------------------------------------------------------------- /core/src/main/resources/format_templates/template.vsqx: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 0 9 | 0 10 | 11 | 12 | 13 | 0 14 | 0 15 | 0 16 | 0 17 | 0 18 | 19 | 20 | 21 | 22 | 23 | 0 24 | 0 25 | 0 26 | 27 | 28 | 0 29 | 0 30 | -898 31 | 0 32 | 0 33 | 0 34 | 64 35 | 0 36 | 37 | 38 | 0 39 | -898 40 | 0 41 | 0 42 | 0 43 | 64 44 | 0 45 | 46 | 47 | 0 48 | 0 49 | 0 50 | -129 51 | 52 | 53 | 54 | 55 | 56 | 480 57 | 4 58 | 59 | 0 60 | 4 61 | 4 62 | 63 | 64 | 0 65 | 12000 66 | 67 | 68 | 69 | 0 70 | 71 | 72 | 73 | 7680 74 | 61440 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 50 84 | 8 85 | 0 86 | 50 87 | 0 88 | 127 89 | 0 90 | 91 | 92 | 0 93 | 0 94 | 0 95 | 96 | 97 | 0 98 | 0 99 | 100 | 101 | 240 102 | 480 103 | 69 104 | 64 105 | 106 |

107 | 108 | 50 109 | 0 110 | 0 111 | 50 112 | 0 113 | 127 114 | 0 115 | 0 116 | 0 117 | 118 |
119 | 0 120 |
121 |
122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 |
131 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau ARPAsing to OpenUtau VCCV.txt: -------------------------------------------------------------------------------- 1 | aa=a 2 | ae=@ 3 | ah=u 4 | ao r=0 r 5 | ao=9 6 | aw=8 7 | ay=I 8 | eh=e 9 | er=3 10 | ey=A 11 | hh iy=hh E 12 | hh y=hh y 13 | hh=h 14 | ih=i 15 | iy=E 16 | jh=j 17 | ow l=0 l 18 | ow=O 19 | oy=Q 20 | uh=6 21 | uw=o 22 | ae n=& n 23 | ae m=& m 24 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau ARPAsing to OpenUtau X-SAMPA EN.txt: -------------------------------------------------------------------------------- 1 | aa=A 2 | ae={ 3 | ah=V 4 | ao=O 5 | aw=aU 6 | ay=aI 7 | ch=tS 8 | dh=D 9 | eh=E 10 | er=3 11 | ey=eI 12 | hh=h 13 | ih=I 14 | iy=i 15 | jh=dZ 16 | ng=N 17 | ow=oU 18 | oy=OI 19 | sh=S 20 | th=T 21 | uh=U 22 | uw=u 23 | zh=Z 24 | y=j 25 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau ARPAsing to SynthV EN.txt: -------------------------------------------------------------------------------- 1 | d r=dr 2 | t r=tr 3 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau ARPAsing to Vocaloid EN.txt: -------------------------------------------------------------------------------- 1 | aa r=Q@ 2 | aa=Q 3 | ae={ 4 | ah=V 5 | ao r=O@ 6 | ao=O: 7 | eh r=e@ 8 | eh=e 9 | er=@r 10 | ih r=I@ 11 | ih=I 12 | iy r=I@ 13 | iy=i: 14 | uh r=U@ 15 | uh=U 16 | uw r=U@ 17 | uw=u: 18 | aw=aU 19 | ay=aI 20 | ey r=e@ 21 | ey=eI 22 | ow r=O@ 23 | ow=@U 24 | oy=OI 25 | ch=tS 26 | dh=D 27 | hh=h 28 | jh=dZ 29 | ng=N 30 | sh=S 31 | th=T 32 | y=j 33 | z=z 34 | zh=Z 35 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau VCCV to OpenUtau ARPAsing.txt: -------------------------------------------------------------------------------- 1 | &=ae 2 | 0 l=ow l 3 | 0=ao 4 | 1=ih 5 | 3=er 6 | 6=uh 7 | 8=aw 8 | 9=ao 9 | @=ae 10 | A=ey 11 | E=iy 12 | I=ay 13 | L=ah l 14 | O=ow 15 | Q=oy 16 | a=aa 17 | dd=d 18 | e=eh 19 | h=hh 20 | i=ih 21 | j=jh 22 | o=uw 23 | sk=s k 24 | sm=s m 25 | sn=s n 26 | sp=s p 27 | st=s t 28 | u=ah 29 | W=aw 30 | x=ah 31 | Y=ay 32 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau VCCV to OpenUtau X-SAMPA EN.txt: -------------------------------------------------------------------------------- 1 | &=E @ 2 | 0 l=oU l 3 | 0=O 4 | 1=I 5 | 6=U 6 | 8=aU 7 | 9=O 8 | @={ 9 | A=eI 10 | E=i 11 | I=aI 12 | O=oU 13 | Q=OI 14 | a=A 15 | ch=tS 16 | dd=4 17 | dh=D 18 | e=E 19 | hh=h 20 | i=I 21 | j=dZ 22 | L=@ l 23 | ng=N 24 | o=u 25 | sh=S 26 | sk=s k 27 | sm=s m 28 | sn=s n 29 | sp=s p 30 | st=s t 31 | th=T 32 | u=V 33 | W=VU 34 | x=@ 35 | y=j 36 | Y=VI 37 | zh=Z 38 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau VCCV to SynthV EN.txt: -------------------------------------------------------------------------------- 1 | &=ae 2 | 0 l=ow l 3 | 0=ao 4 | 1=ih 5 | 3=er 6 | 6=uh 7 | 8=aw 8 | 9=ao 9 | @=ae 10 | A=ey 11 | E=iy 12 | I=ay 13 | O=ow 14 | Q=oy 15 | a=aa 16 | d r=dr 17 | dd=dx 18 | e=eh 19 | h=hh 20 | i=ih 21 | j=jh 22 | L=ah l 23 | o=uw 24 | sk=s k 25 | sm=s m 26 | sn=s n 27 | sp=s p 28 | st=s t 29 | t r=tr 30 | u=ah 31 | W=aw 32 | x=ax 33 | Y=ay 34 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau VCCV to Vocaloid EN.txt: -------------------------------------------------------------------------------- 1 | &=e@0 2 | 0 l=@U l 3 | 0 r=O@ 4 | 0=O: 5 | 1=I 6 | 3=@r 7 | 6 r=U@ 8 | 6=U 9 | 8=aU 10 | 9=O: 11 | @={ 12 | A r=e@ 13 | A=eI 14 | E r=I@ 15 | E=i: 16 | I=aI 17 | O=@U 18 | Q=OI 19 | a r=Q@ 20 | a=Q 21 | ch=tS 22 | dd=4 23 | dh=D 24 | e r=e@ 25 | hh=h 26 | i r=I@ 27 | i=I 28 | j=dZ 29 | L=@l 30 | ng=N 31 | o r=U@ 32 | o=u: 33 | sh=S 34 | sk=s k 35 | sm=s m 36 | sn=s n 37 | sp=s p 38 | st=s t 39 | th=T 40 | u=V 41 | W=aU 42 | x=@ 43 | Y=aI 44 | y=j 45 | zh=Z 46 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau X-SAMPA EN to OpenUtau ARPAsing.txt: -------------------------------------------------------------------------------- 1 | ・= 2 | {~=ae ng 3 | {l=ae l 4 | @\=er 5 | @`=er 6 | @l=ah l 7 | @m=ah m 8 | @n=ah n 9 | @N=ah ng 10 | @r=er 11 | @u=ow 12 | @U=ow 13 | 3\=er 14 | 3`=er 15 | 3r=er 16 | A@=aa r 17 | A`=aa r 18 | aI@=ay r 19 | aI`=ay r 20 | ai=ay 21 | aI=ay 22 | aIl=ay l 23 | aIr=ay r 24 | al=aa l 25 | Al=aa l 26 | ar=aa r 27 | Ar=aa r 28 | aU@=aw r 29 | aU`=aw r 30 | au=aw 31 | aU=aw 32 | aU~=aw n 33 | aUn=aw n 34 | aUr=aw r 35 | dZ=jh 36 | e@=eh r 37 | E@=eh r 38 | e@0=eh ah 39 | e@m=eh ah m 40 | e@n=eh ah n 41 | E`=eh r 42 | e~=ae ng 43 | E~=ae ng 44 | ei=ey 45 | eI=ey 46 | El=eh l 47 | eN=ae ng 48 | Er=eh r 49 | i:=iy 50 | i@=iy r 51 | I@=ih r 52 | I\=ih 53 | i`=iy r 54 | I`=ih r 55 | I~=ih ng 56 | il=iy l 57 | Il=ih l 58 | IN=ih ng 59 | ir=iy r 60 | Ir=ih r 61 | ll=ah l 62 | mm=ah m 63 | NN=ah ng 64 | nn=ah n 65 | O:=ao 66 | O@=ao r 67 | O`=ao r 68 | Oi=oy 69 | OI=oy 70 | Ol=ow l 71 | or=ao r 72 | Or=ao r 73 | ou=ow 74 | oU=ow 75 | Ou=ow 76 | Q@=aa r 77 | Q`=aa r 78 | Ql=ao l 79 | Qr=aa r 80 | r\=r 81 | t_}=t 82 | tS=ch 83 | u:=uw 84 | u@=uw r 85 | U@=uh r 86 | U\=uh 87 | u`=uw r 88 | U`=uh r 89 | ul=uw l 90 | Ul=uh l 91 | ur=uw r 92 | Ur=uh r 93 | VI=ay 94 | VU=ow 95 | _= 96 | {=ae 97 | }=uw 98 | @=ah 99 | &=aa 100 | 1=ih 101 | 2=uh 102 | 3=er 103 | 4=d 104 | 6=ah 105 | 7=ah 106 | 8=uh 107 | 9=uh 108 | a=aa 109 | A=aa 110 | D=dh 111 | e=eh 112 | E=eh 113 | h=hh 114 | i=iy 115 | I=ih 116 | j=y 117 | M=uw 118 | N=ng 119 | o=ao 120 | O=ao 121 | Q=aa 122 | S=sh 123 | T=th 124 | u=uw 125 | U=uh 126 | V=ah 127 | W=w 128 | y=uw 129 | Y=uh 130 | Z=zh 131 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau X-SAMPA EN to OpenUtau VCCV.txt: -------------------------------------------------------------------------------- 1 | h i=hh E 2 | h j=hh y 3 | ・= 4 | {~=A ng 5 | {l=@ l 6 | @\=3 7 | @`=3 8 | @l=6 l 9 | @m=6 m 10 | @n=6 n 11 | @N=6 ng 12 | @r=3 13 | @u=O 14 | @U=O 15 | 3\=3 16 | 3`=3 17 | 3r=3 18 | A@=a r 19 | A`=a r 20 | aI@=I r 21 | aI`=I r 22 | ai=I 23 | aI=I 24 | aIl=I l 25 | aIr=I r 26 | al=a l 27 | Al=a l 28 | ar=a r 29 | Ar=a r 30 | aU@=8 r 31 | aU`=8 r 32 | au=8 33 | aU=8 34 | aU~=8 n 35 | aUn=8 n 36 | aUr=8 r 37 | dZ=j 38 | e@=A r 39 | E@=A r 40 | e@0=& 41 | e@m=& m 42 | e@n=& n 43 | E`=A r 44 | e~=A ng 45 | E~=A ng 46 | ei=A 47 | eI=A 48 | El=e l 49 | eN=A ng 50 | Er=A r 51 | i:=E 52 | i@=E r 53 | I@=i r 54 | I\=i 55 | i`=E r 56 | I`=i r 57 | I~=1 ng 58 | il=E l 59 | Il=i l 60 | IN=1 ng 61 | ir=E r 62 | Ir=i r 63 | ll=x l 64 | mm=x m 65 | NN=x ng 66 | nn=x n 67 | O:=9 68 | O@=0 r 69 | O`=0 r 70 | Oi=Q 71 | OI=Q 72 | Ol=0 l 73 | or=0 r 74 | Or=0 r 75 | ou=O 76 | oU=O 77 | Ou=O 78 | Q@=a r 79 | Q`=a r 80 | Ql=9 l 81 | Qr=a r 82 | r\=r 83 | t_}=t 84 | tS=ch 85 | u:=o 86 | u@=o r 87 | U@=6 r 88 | U\=6 89 | u`=o r 90 | U`=6 r 91 | ul=o l 92 | Ul=6 l 93 | ur=o r 94 | Ur=6 r 95 | VI=I 96 | VU=O 97 | _= 98 | {=@ 99 | }=o 100 | @=x 101 | &=a 102 | 1=i 103 | 2=6 104 | 4=dd 105 | 6=u 106 | 7=u 107 | 8=6 108 | 9=6 109 | A=a 110 | D=dh 111 | E=e 112 | i=E 113 | I=i 114 | j=y 115 | M=o 116 | N=ng 117 | o=9 118 | O=9 119 | Q=a 120 | S=sh 121 | T=th 122 | u=o 123 | U=6 124 | V=u 125 | W=w 126 | y=o 127 | Y=6 128 | Z=zh 129 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau X-SAMPA EN to SynthV EN.txt: -------------------------------------------------------------------------------- 1 | t r\=tr 2 | t r=tr 3 | d r\=dr 4 | d r=dr 5 | ・=cl 6 | {~=ae ng 7 | {l=ae l 8 | @\=er 9 | @`=er 10 | @l=ax l 11 | @m=ax m 12 | @n=ax n 13 | @N=ax ng 14 | @r=er 15 | @u=ow 16 | @U=ow 17 | 3\=er 18 | 3`=er 19 | 3r=er 20 | A@=aa r 21 | A`=aa r 22 | aI@=ay r 23 | aI`=ay r 24 | ai=ay 25 | aI=ay 26 | aIl=ay l 27 | aIr=ay r 28 | al=aa l 29 | Al=aa l 30 | ar=aa r 31 | Ar=aa r 32 | aU@=aw r 33 | aU`=aw r 34 | au=aw 35 | aU=aw 36 | aU~=aw n 37 | aUn=aw n 38 | aUr=aw r 39 | dZ=jh 40 | e@=eh r 41 | E@=eh r 42 | e@0=eh ax 43 | e@m=ae m 44 | e@n=ae n 45 | E`=eh r 46 | e~=ae ng 47 | E~=ae ng 48 | ei=ey 49 | eI=ey 50 | El=eh l 51 | eN=ae ng 52 | Er=eh r 53 | i:=iy 54 | i@=iy r 55 | I@=ih r 56 | I\=ih 57 | i`=iy r 58 | I`=ih r 59 | I~=ih ng 60 | il=iy l 61 | Il=ih l 62 | IN=ih ng 63 | ir=iy r 64 | Ir=ih r 65 | ll=ax l 66 | mm=ax m 67 | NN=ax ng 68 | nn=ax n 69 | O:=ao 70 | O@=ao r 71 | O`=ao r 72 | Oi=oy 73 | OI=oy 74 | Ol=ow l 75 | or=ao r 76 | Or=ao r 77 | ou=ow 78 | oU=ow 79 | Ou=ow 80 | Q@=aa r 81 | Q`=aa r 82 | Ql=ao l 83 | Qr=aa r 84 | r\=r 85 | t_}=t 86 | tS=ch 87 | u:=uw 88 | u@=uw r 89 | U@=uh r 90 | U\=uh 91 | u`=uw r 92 | U`=uh r 93 | ul=uw l 94 | Ul=uh l 95 | ur=uw r 96 | Ur=uh r 97 | VI=ay 98 | VU=ow 99 | _=cl 100 | {=ae 101 | }=uw 102 | @=ax 103 | &=aa 104 | 1=ih 105 | 2=uh 106 | 3=er 107 | 4=dx 108 | 6=ah 109 | 7=ah 110 | 8=uh 111 | 9=uh 112 | a=aa 113 | A=aa 114 | D=dh 115 | e=eh 116 | E=eh 117 | h=hh 118 | i=iy 119 | I=ih 120 | j=y 121 | M=uw 122 | N=ng 123 | o=ao 124 | O=ao 125 | Q=aa 126 | S=sh 127 | T=th 128 | u=uw 129 | U=uh 130 | V=ah 131 | W=w 132 | y=uw 133 | Y=uh 134 | Z=zh 135 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/OpenUtau X-SAMPA EN to Vocaloid EN.txt: -------------------------------------------------------------------------------- 1 | ・=Sil 2 | {~={ N 3 | {l={ l 4 | @\=@r 5 | @`=@r 6 | @l=U l 7 | @m=@ m 8 | @n=@ n 9 | @N=@ N 10 | @u=@U 11 | 3\=@r 12 | 3`=@r 13 | 3r=@r 14 | A@=Q@ 15 | A`=Q@ 16 | aI@=aI r 17 | aI`=aI r 18 | ai=aI 19 | aIl=aI l 20 | aIr=aI r 21 | al=Q l 22 | Al=Q l 23 | ar=Q@ 24 | Ar=Q@ 25 | aU@=aU r 26 | aU`=aU r 27 | au=aU 28 | aU~=aU n 29 | aUn=aU n 30 | aUr=aU r 31 | E@=e@ 32 | e@0=e@0 33 | e@m=e@0 m 34 | e@n=e@0 n 35 | E`=e@ 36 | e~=e N 37 | E~=e N 38 | ei=eI 39 | El=e l 40 | eN=e N 41 | Er=e@ 42 | i@=I@ 43 | I\=I 44 | i`=I@ 45 | I`=I@ 46 | I~=I N 47 | il=i: l 48 | Il=I l 49 | IN=I N 50 | ir=I@ 51 | Ir=I@ 52 | ll=@ l 53 | mm=@ m 54 | NN=@ N 55 | nn=@ n 56 | O`=O@ 57 | Oi=OI 58 | Ol=@U l 59 | or=O@ 60 | Or=O@ 61 | ou=@U 62 | oU=@U 63 | Ou=@U 64 | Q`=Q@ 65 | Ql=O: l 66 | Qr=Q@ 67 | r\=r 68 | t_}=t 69 | u@=U@ 70 | U\=U 71 | u`=U@ 72 | U`=U@ 73 | ul=u: l 74 | Ul=U l 75 | ur=U@ 76 | Ur=U@ 77 | VI=aI 78 | VU=@U 79 | _=Sil 80 | }=u: 81 | &=Q 82 | 1=I 83 | 2=U 84 | 3=@r 85 | 6=V 86 | 7=V 87 | 8=U 88 | 9=U 89 | a=Q 90 | A=Q 91 | E=e 92 | i=i: 93 | M=u: 94 | o=O: 95 | O=O: 96 | u=u: 97 | W=w 98 | y=u: 99 | Y=U 100 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/SynthV EN to OpenUtau ARPAsing.txt: -------------------------------------------------------------------------------- 1 | ax=ah 2 | br= 3 | cl= 4 | dr=jh r 5 | dx=d 6 | pau= 7 | sil= 8 | tr=ch r 9 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/SynthV EN to OpenUtau VCCV.txt: -------------------------------------------------------------------------------- 1 | aa=a 2 | ae=@ 3 | ah=u 4 | ao r=0 r 5 | ao=9 6 | ax=x 7 | eh=e 8 | er=3 9 | ih=i 10 | iy=E 11 | ow l=0 l 12 | uh=6 13 | uw=o 14 | aw=8 15 | ay=I 16 | ey=A 17 | ow=O 18 | oy=Q 19 | dr=d r 20 | dx=dd 21 | hh iy=hh E 22 | hh y=hh y 23 | hh=h 24 | jh=j 25 | tr=t r 26 | br= 27 | cl= 28 | pau= 29 | sil= 30 | ae n=& n 31 | ae m=& m 32 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/SynthV EN to OpenUtau X-SAMPA EN.txt: -------------------------------------------------------------------------------- 1 | aa=A 2 | ae={ 3 | ah=V 4 | ao=O 5 | aw=aU 6 | ax=@ 7 | ay=aI 8 | br= 9 | ch=tS 10 | cl= 11 | dh=D 12 | dr=dZ r 13 | dx=4 14 | eh=E 15 | er=3 16 | ey=eI 17 | hh=h 18 | ih=I 19 | iy=i 20 | jh=dZ 21 | ng=N 22 | ow=oU 23 | oy=OI 24 | pau= 25 | sh=S 26 | sil= 27 | th=T 28 | tr=tS r 29 | uh=U 30 | uw=u 31 | zh=Z 32 | y=j 33 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/SynthV EN to Vocaloid EN.txt: -------------------------------------------------------------------------------- 1 | aa r=Q@ 2 | aa=Q 3 | ae={ 4 | ah=V 5 | ao r=O@ 6 | ao=O: 7 | aw=aU 8 | ax=@ 9 | ay=aI 10 | eh r=e@ 11 | eh=e 12 | er=@r 13 | ey r=e@ 14 | ey=eI 15 | ih r=I@ 16 | ih=I 17 | iy r=I@ 18 | iy=i: 19 | ow r=O@ 20 | ow=@U 21 | oy=OI 22 | uh r=U@ 23 | uh=U 24 | uw r=U@ 25 | uw=u: 26 | ch=tS 27 | dh=D 28 | dr=dh r 29 | dx=4 30 | hh=h 31 | jh=dZ 32 | ng=N 33 | sh=S 34 | th=T 35 | tr=th r 36 | y=j 37 | zh=Z 38 | br=br1 39 | cl=Asp 40 | pau=Sil 41 | sil=Sil 42 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/SynthV JA to Vocaloid JA.txt: -------------------------------------------------------------------------------- 1 | cl=? 2 | pau=Asp 3 | sil=Sil 4 | br=br1 5 | u=M 6 | N=N\ 7 | ry=4' 8 | ky=k' 9 | py=p' 10 | dy=d' 11 | ty=t' 12 | ny=J 13 | hy=C 14 | my=m' 15 | gy=g' 16 | by=b' 17 | sh=S 18 | j=dZ 19 | z=dz 20 | f=p\ 21 | f y=p\' 22 | ch=tS 23 | r=4 24 | y=j 25 | v=p\ 26 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/Vocaloid EN to OpenUtau ARPAsing.txt: -------------------------------------------------------------------------------- 1 | @=ah 2 | @U=ow 3 | @l=ah l 4 | @r=er 5 | I=ih 6 | I@=ih r 7 | O:=ao 8 | O@=ao r 9 | OI=oy 10 | Q=aa 11 | Q@=aa r 12 | U=uh 13 | U@=uh r 14 | V=ah 15 | aI=ay 16 | aU=aw 17 | e=eh 18 | e@=eh r 19 | e@0=ae 20 | eI=ey 21 | i:=iy 22 | u:=uw 23 | {=ae 24 | 4=d 25 | D=dh 26 | N=ng 27 | S=sh 28 | T=th 29 | Z=zh 30 | bh=b 31 | dZ=jh 32 | dh=d 33 | gh=g 34 | h=hh 35 | h\=hh 36 | j=y 37 | kh=k 38 | l0=l 39 | ph=p 40 | R=r 41 | tS=ch 42 | th=t 43 | Sil= 44 | Asp= 45 | br1= 46 | br2= 47 | br3= 48 | br4= 49 | br5= 50 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/Vocaloid EN to OpenUtau VCCV.txt: -------------------------------------------------------------------------------- 1 | @=x 2 | @U=O 3 | @l=x l 4 | @r=3 5 | I=i 6 | I@=i r 7 | O:=9 8 | O@=0 r 9 | Q=a 10 | Q@=a r 11 | OI=Q 12 | U=6 13 | U@=6 r 14 | V=u 15 | aI=I 16 | aU=8 17 | e@=A r 18 | e@0=& 19 | eI=A 20 | i:=E 21 | u:=o 22 | {=@ 23 | 4=dd 24 | D=dh 25 | N=ng 26 | S=sh 27 | T=th 28 | Z=zh 29 | bh=b 30 | dh=d 31 | gh=g 32 | h\=h 33 | j=y 34 | dZ=j 35 | kh=k 36 | l0=l 37 | ph=p 38 | R=r 39 | tS=ch 40 | th=t 41 | Sil= 42 | Asp= 43 | br1= 44 | br2= 45 | br3= 46 | br4= 47 | br5= 48 | @U l=0 l 49 | h i:=hh E 50 | h j=hh y -------------------------------------------------------------------------------- /core/src/main/resources/texts/Vocaloid EN to OpenUtau X-SAMPA EN.txt: -------------------------------------------------------------------------------- 1 | @U=oU 2 | @l=@ l 3 | @r=3 4 | I@=I r 5 | O:=O 6 | O@=O r 7 | Q=A 8 | Q@=A r 9 | U@=U r 10 | e=E 11 | e@=E r 12 | e@0=E @ 13 | i:=i 14 | u:=u 15 | bh=b 16 | dZ=dZ 17 | dh=d 18 | gh=g 19 | h\=h 20 | kh=k 21 | l0=l 22 | ph=p 23 | R=r 24 | tS=tS 25 | th=t 26 | Sil= 27 | Asp= 28 | br1= 29 | br2= 30 | br3= 31 | br4= 32 | br5= 33 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/Vocaloid EN to SynthV EN.txt: -------------------------------------------------------------------------------- 1 | @=ax 2 | @U=ow 3 | @l=ax l 4 | @r=er 5 | I=ih 6 | I@=ih r 7 | O:=ao 8 | O@=ao r 9 | OI=oy 10 | Q=aa 11 | Q@=aa r 12 | U=uh 13 | U@=uh r 14 | V=ah 15 | aI=ay 16 | aU=aw 17 | e=eh 18 | e@=eh r 19 | e@0=ae 20 | eI=ey 21 | i:=iy 22 | u:=uw 23 | {=ae 24 | 4=dx 25 | D=dh 26 | N=ng 27 | S=sh 28 | T=th 29 | Z=zh 30 | bh=b 31 | dZ=jh 32 | dh=d 33 | gh=g 34 | h=hh 35 | h\=hh 36 | j=y 37 | kh=k 38 | l0=l 39 | ph=p 40 | R=dx 41 | tS=ch 42 | th=t 43 | Sil=sil 44 | Asp=cl 45 | br1=br 46 | br2=br 47 | br3=br 48 | br4=br 49 | br5=br 50 | dh r=dr 51 | th r=tr 52 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/Vocaloid JA to SynthV JA.txt: -------------------------------------------------------------------------------- 1 | M=u 2 | k'=ky 3 | g'=gy 4 | N'=gy 5 | N\=N 6 | N=g 7 | S=sh 8 | Z=j 9 | dz=z 10 | dZ=j 11 | t'=ty 12 | tS=ch 13 | d'=dy 14 | J=ny 15 | h\=h 16 | C=hy 17 | p\' i=f i 18 | p\'=f y 19 | p\=f 20 | br1=br 21 | br2=br 22 | br3=br 23 | br4=br 24 | br5=br 25 | b'=by 26 | p'=py 27 | m'=my 28 | j=y 29 | 4'=ry 30 | 4=r 31 | Asp=pau 32 | ?=cl 33 | Sil=sil 34 | -------------------------------------------------------------------------------- /core/src/main/resources/texts/vxbeta-japanese-mapping.txt: -------------------------------------------------------------------------------- 1 | あ=[a] 2 | い=[i] 3 | う=[u] 4 | え=[e] 5 | お=[o] 6 | 7 | ぁ=[a] 8 | ぃ=[i] 9 | ぅ=[u] 10 | ぇ=[e] 11 | ぉ=[o] 12 | 13 | ゐ=[i] 14 | ゑ=[e] 15 | 16 | か=[k a] 17 | き=[k i] 18 | く=[k u] 19 | け=[k e] 20 | こ=[k o] 21 | 22 | きゃ=[ky a] 23 | きゅ=[ky u] 24 | きぇ=[ky e] 25 | きょ=[ky o] 26 | 27 | くぁ=[k w a] 28 | くぃ=[k w i] 29 | くぅ=[k w u] 30 | くぇ=[k w e] 31 | くぉ=[k w o] 32 | 33 | が=[g a] 34 | ぎ=[g i] 35 | ぐ=[g u] 36 | げ=[g e] 37 | ご=[g o] 38 | 39 | ぎゃ=[gy a] 40 | ぎゅ=[gy u] 41 | ぎぇ=[gy e] 42 | ぎょ=[gy o] 43 | 44 | ぐぁ=[g w a] 45 | ぐぃ=[g w i] 46 | ぐぅ=[g w u] 47 | ぐぇ=[g w e] 48 | ぐぉ=[g w o] 49 | 50 | さ=[s a] 51 | し=[sh i] 52 | す=[s u] 53 | せ=[s e] 54 | そ=[s o] 55 | 56 | ざ=[z a] 57 | じ=[j i] 58 | ず=[z u] 59 | ぜ=[z e] 60 | ぞ=[z o] 61 | 62 | しゃ=[sh a] 63 | しゅ=[sh u] 64 | しぇ=[sh e] 65 | しょ=[sh o] 66 | 67 | すぃ=[s i] 68 | 69 | じゃ=[j a] 70 | じゅ=[j u] 71 | じぇ=[j e] 72 | じょ=[j o] 73 | 74 | ずぃ=[z i] 75 | 76 | た=[t a] 77 | ち=[ch i] 78 | つ=[ts u] 79 | て=[t e] 80 | と=[t o] 81 | 82 | ちゃ=[ch a] 83 | ちゅ=[ch u] 84 | ちぇ=[ch e] 85 | ちょ=[ch o] 86 | 87 | つぁ=[ts a] 88 | つぃ=[ts i] 89 | つぇ=[ts e] 90 | つぉ=[ts o] 91 | 92 | てゃ=[ty a] 93 | てぃ=[t i] 94 | てゅ=[ty u] 95 | てぇ=[ty e] 96 | てょ=[ty o] 97 | 98 | とぅ=[t u] 99 | 100 | だ=[d a] 101 | ぢ=[j i] 102 | づ=[z u] 103 | で=[d e] 104 | ど=[d o] 105 | 106 | ぢゃ=[j a] 107 | ぢゅ=[j u] 108 | ぢぇ=[j e] 109 | ぢょ=[j o] 110 | 111 | でゃ=[dy a] 112 | でぃ=[d i] 113 | でゅ=[dy u] 114 | でぇ=[dy e] 115 | でょ=[dy o] 116 | 117 | どぅ=[d u] 118 | 119 | な=[n a] 120 | に=[n i] 121 | ぬ=[n u] 122 | ね=[n e] 123 | の=[n o] 124 | 125 | にゃ=[ny a] 126 | にゅ=[ny u] 127 | にぇ=[ny e] 128 | にょ=[ny o] 129 | 130 | は=[h a] 131 | ひ=[h i] 132 | ふ=[f u] 133 | へ=[h e] 134 | ほ=[h o] 135 | 136 | ひゃ=[hy a] 137 | ひゅ=[hy u] 138 | ひぇ=[hy e] 139 | ひょ=[hy o] 140 | 141 | ふぁ=[f a] 142 | ふぃ=[f i] 143 | ふぇ=[f e] 144 | ふぉ=[f o] 145 | 146 | ふゃ=[f y a] 147 | ふゅ=[f y i] 148 | 149 | ば=[b a] 150 | び=[b i] 151 | ぶ=[b u] 152 | べ=[b e] 153 | ぼ=[b o] 154 | 155 | びゃ=[by a] 156 | びゅ=[by u] 157 | びぇ=[by e] 158 | びょ=[by o] 159 | 160 | ぶぁ=[b w a] 161 | ぶぃ=[b w i] 162 | ぶぇ=[b w e] 163 | ぶぉ=[b w o] 164 | 165 | ヴぁ=[v a] 166 | ヴぃ=[v i] 167 | ヴ=[v u] 168 | ヴぇ=[v e] 169 | ヴぉ=[v o] 170 | 171 | ヴゃ=[v y a] 172 | ヴゅ=[v y i] 173 | 174 | ぱ=[p a] 175 | ぴ=[p i] 176 | ぷ=[p u] 177 | ぺ=[p e] 178 | ぽ=[p o] 179 | 180 | ぴゃ=[py a] 181 | ぴゅ=[py u] 182 | ぴぇ=[py e] 183 | ぴょ=[py o] 184 | 185 | ぷぁ=[p w a] 186 | ぷぃ=[p w i] 187 | ぷぇ=[p w e] 188 | ぷぉ=[p w o] 189 | 190 | ま=[m a] 191 | み=[m i] 192 | む=[m u] 193 | め=[m e] 194 | も=[m o] 195 | 196 | みゃ=[my a] 197 | みゅ=[my u] 198 | みぇ=[my e] 199 | みょ=[my o] 200 | 201 | や=[y a] 202 | ゆ=[y u] 203 | よ=[y o] 204 | 205 | ら=[r a] 206 | り=[r i] 207 | る=[r u] 208 | れ=[r e] 209 | ろ=[r o] 210 | 211 | りゃ=[ry a] 212 | りゅ=[ry u] 213 | りぇ=[ry e] 214 | りょ=[ry o] 215 | 216 | わ=[w a] 217 | を=[w o] 218 | ん=[N] 219 | 220 | っ=[cl] 221 | ー=[-] 222 | 息=[br] 223 | -------------------------------------------------------------------------------- /core/webpack.config.d/resources.js: -------------------------------------------------------------------------------- 1 | config.module.rules.push({ 2 | test: /\.vsqx$/i, 3 | loader: 'raw-loader' 4 | }, { 5 | test: /\.vprjson$/i, 6 | loader: 'raw-loader' 7 | }, { 8 | test: /\.svp$/i, 9 | loader: 'raw-loader' 10 | }, { 11 | test: /\.s5p$/i, 12 | loader: 'raw-loader' 13 | }, { 14 | test: /\.musicxml/i, 15 | loader: 'raw-loader' 16 | }, { 17 | test: /\.ccs$/i, 18 | loader: 'raw-loader' 19 | }, { 20 | test: /\.ustx/i, 21 | loader: 'raw-loader' 22 | }, { 23 | test: /\.txt/i, 24 | loader: 'raw-loader' 25 | }, { 26 | test: /\.(png|jpe?g|gif)$/i, 27 | use: [ 28 | { 29 | loader: 'file-loader', 30 | }, 31 | ], 32 | }); 33 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | kotlin.daemon.jvmargs=-Xmx4G 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "utaformatix" 2 | include("core") 3 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | import kotlinx.browser.document 2 | import react.create 3 | import react.dom.client.createRoot 4 | import ui.App 5 | import ui.strings.Language 6 | import ui.strings.initializeI18n 7 | 8 | const val APP_NAME = "UtaFormatix" 9 | const val APP_VERSION = "3.24" 10 | 11 | suspend fun main() { 12 | initializeI18n(Language.English) 13 | createRoot(document.createElement("div").also { document.body!!.appendChild(it) }) 14 | .render(App.create()) 15 | } 16 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/AppTheme.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import kotlinx.js.jso 4 | import mui.material.PaletteMode 5 | import mui.material.styles.createTheme 6 | 7 | val appTheme = 8 | createTheme( 9 | jso { 10 | palette = 11 | jso { 12 | mode = PaletteMode.dark 13 | primary = 14 | jso { 15 | main = "#3b3b3b" 16 | light = "#757575" 17 | dark = "#212121" 18 | contrastText = "#e8e8e8" 19 | } 20 | secondary = 21 | jso { 22 | main = "#f48fb1" 23 | light = "#f0afc5" 24 | dark = "#fff" 25 | } 26 | background = 27 | jso { 28 | default = "#303030" 29 | paper = "#3b3b3b" 30 | } 31 | } 32 | }, 33 | ) 34 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/CustomFooter.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import csstype.NamedColor 4 | import emotion.react.css 5 | import mui.material.Link 6 | import mui.material.LinkUnderline 7 | import mui.material.Typography 8 | import mui.material.TypographyAlign 9 | import mui.material.styles.TypographyVariant 10 | import react.FC 11 | import react.Props 12 | import react.dom.html.AnchorTarget 13 | import react.dom.html.ReactHTML.footer 14 | import ui.strings.Strings 15 | 16 | val CustomFooter = 17 | FC { props -> 18 | footer { 19 | Typography { 20 | align = TypographyAlign.center 21 | variant = TypographyVariant.body2 22 | css { 23 | color = NamedColor.grey 24 | } 25 | Link { 26 | color = NamedColor.grey 27 | underline = LinkUnderline.hover 28 | href = "https://github.com/sdercolin/utaformatix3?tab=License-1-ov-file#readme" 29 | target = AnchorTarget._blank 30 | +"UtaFormatix © 2020 sdercolin" 31 | } 32 | +" | " 33 | Link { 34 | color = NamedColor.grey 35 | underline = LinkUnderline.hover 36 | href = "https://github.com/sdercolin/utaformatix3" 37 | target = AnchorTarget._blank 38 | +"GitHub" 39 | } 40 | +" | " 41 | Link { 42 | color = NamedColor.grey 43 | underline = LinkUnderline.hover 44 | href = "https://discord.gg/TyEcQ6P73y" 45 | target = AnchorTarget._blank 46 | +"Discord" 47 | } 48 | +" | " 49 | Link { 50 | color = NamedColor.grey 51 | underline = LinkUnderline.hover 52 | onClick = { props.onOpenEmbeddedPage(Strings.ReleaseNotesUrl) } 53 | +"Release Notes" 54 | } 55 | +" | " 56 | Link { 57 | color = NamedColor.grey 58 | underline = LinkUnderline.hover 59 | onClick = { props.onOpenEmbeddedPage(Strings.GoogleAnalyticsUsageInfoUrl) } 60 | +"About Usage of Google Analytics" 61 | } 62 | } 63 | } 64 | } 65 | 66 | external interface CustomFooterProps : Props { 67 | var onOpenEmbeddedPage: (urlKey: Strings) -> Unit 68 | } 69 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/EmbeddedPage.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import core.util.runCatchingCancellable 4 | import csstype.px 5 | import emotion.react.css 6 | import kotlinx.browser.window 7 | import kotlinx.coroutines.await 8 | import kotlinx.coroutines.launch 9 | import react.Props 10 | import react.dom.html.ReactHTML.div 11 | import react.useEffect 12 | import react.useState 13 | import ui.common.scopedFC 14 | import ui.external.react.Markdown 15 | 16 | val EmbeddedPage = 17 | scopedFC { props, scope -> 18 | var url: String? by useState { props.url } 19 | var content: String? by useState() 20 | 21 | useEffect { 22 | scope.launch { 23 | if (content == null || url != props.url) { 24 | url = props.url 25 | runCatchingCancellable { 26 | window 27 | .fetch(props.url) 28 | .await() 29 | .text() 30 | .await() 31 | }.onFailure { t -> 32 | t.toString() 33 | }.onSuccess { 34 | content = it 35 | } 36 | } 37 | } 38 | } 39 | 40 | div { 41 | css { 42 | marginTop = 32.px 43 | marginBottom = 48.px 44 | } 45 | Markdown { 46 | +content.orEmpty() 47 | } 48 | } 49 | } 50 | 51 | external interface EmbeddedPageProps : Props { 52 | var url: String 53 | } 54 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/LanguageSelector.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | import dom.html.HTMLButtonElement 4 | import kotlinx.coroutines.launch 5 | import mui.icons.material.Language 6 | import mui.material.Button 7 | import mui.material.ButtonColor 8 | import mui.material.Menu 9 | import mui.material.MenuItem 10 | import org.w3c.dom.Element 11 | import react.Props 12 | import react.dom.html.ReactHTML.div 13 | import react.useState 14 | import ui.common.scopedFC 15 | import ui.strings.Language 16 | import ui.strings.changeLanguage 17 | 18 | typealias MyLanguage = Language 19 | 20 | val LanguageSelector = 21 | scopedFC { props, scope -> 22 | var anchorElement: Element? by useState() 23 | 24 | fun openMenu(currentTarget: HTMLButtonElement) { 25 | anchorElement = currentTarget 26 | } 27 | 28 | fun closeMenu() { 29 | anchorElement = null 30 | } 31 | 32 | fun selectLanguage(language: MyLanguage) { 33 | scope.launch { 34 | changeLanguage(language.code) 35 | props.onChangeLanguage(language) 36 | } 37 | } 38 | 39 | div { 40 | Button { 41 | color = ButtonColor.inherit 42 | onClick = { event -> 43 | openMenu(event.currentTarget) 44 | } 45 | Language() 46 | } 47 | 48 | Menu { 49 | anchorEl = anchorElement?.let { { _ -> it } } 50 | open = anchorElement != null 51 | onClose = { closeMenu() } 52 | MyLanguage.values().forEach { language -> 53 | MenuItem { 54 | onClick = { 55 | selectLanguage(language) 56 | closeMenu() 57 | } 58 | +language.displayName 59 | } 60 | } 61 | } 62 | } 63 | } 64 | 65 | external interface LanguageSelectorProps : Props { 66 | var onChangeLanguage: (Language) -> Unit 67 | } 68 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/Resources.kt: -------------------------------------------------------------------------------- 1 | package ui 2 | 3 | object Resources { 4 | val vocaloidMidIcon: String 5 | get() = core.external.require("./images/vocaloid1.png").default as String 6 | 7 | val vsqIcon: String 8 | get() = core.external.require("./images/vocaloid2.png").default as String 9 | 10 | val vsqxIcon: String 11 | get() = core.external.require("./images/vocaloid4.png").default as String 12 | 13 | val vprIcon: String 14 | get() = core.external.require("./images/vocaloid5.png").default as String 15 | 16 | val ustIcon: String 17 | get() = core.external.require("./images/utau.png").default as String 18 | 19 | val ustxIcon: String 20 | get() = core.external.require("./images/openutau.png").default as String 21 | 22 | val ccsIcon: String 23 | get() = core.external.require("./images/cevio.png").default as String 24 | 25 | val svpIcon: String 26 | get() = core.external.require("./images/svr2.png").default as String 27 | 28 | val s5pIcon: String 29 | get() = core.external.require("./images/svr1.png").default as String 30 | 31 | val dvIcon: String 32 | get() = core.external.require("./images/dv.png").default as String 33 | 34 | val standardMidiIcon: String 35 | get() = core.external.require("./images/midi.png").default as String 36 | 37 | val ufdataIcon: String 38 | get() = core.external.require("./images/ufdata.png").default as String 39 | 40 | val tsslnIcon: String 41 | get() = core.external.require("./images/voisona.png").default as String 42 | } 43 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/common/ErrorDialog.kt: -------------------------------------------------------------------------------- 1 | package ui.common 2 | 3 | import csstype.px 4 | import kotlinx.browser.window 5 | import kotlinx.js.jso 6 | import mui.material.Alert 7 | import mui.material.AlertColor 8 | import mui.material.Button 9 | import mui.material.ButtonColor 10 | import mui.material.Dialog 11 | import mui.material.DialogActions 12 | import mui.material.DialogContent 13 | import mui.material.DialogContentText 14 | import mui.material.DialogTitle 15 | import react.ChildrenBuilder 16 | import react.dom.html.ReactHTML.div 17 | import ui.strings.Strings 18 | import ui.strings.string 19 | 20 | fun ChildrenBuilder.errorDialog( 21 | state: DialogErrorState, 22 | close: () -> Unit, 23 | ) { 24 | Dialog { 25 | open = state.isShowing 26 | onClose = { _, _ -> close() } 27 | DialogTitle { 28 | +state.title 29 | } 30 | Alert { 31 | severity = AlertColor.error 32 | style = jso { borderRadius = 0.px } 33 | +state.message 34 | } 35 | div { 36 | DialogContent { 37 | DialogContentText { 38 | +string(Strings.ErrorDialogDescription) 39 | } 40 | } 41 | } 42 | DialogActions { 43 | Button { 44 | onClick = { close() } 45 | color = ButtonColor.inherit 46 | +string(Strings.CancelButton) 47 | } 48 | Button { 49 | onClick = { 50 | close() 51 | window.open(string(Strings.ReportUrl), target = "_blank") 52 | } 53 | color = ButtonColor.inherit 54 | +string(Strings.ReportButton) 55 | } 56 | } 57 | } 58 | } 59 | 60 | data class DialogErrorState( 61 | val isShowing: Boolean = false, 62 | val title: String = "", 63 | val message: String = "", 64 | ) 65 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/common/FeatureSwitch.kt: -------------------------------------------------------------------------------- 1 | package ui.common 2 | 3 | import mui.material.FormControlLabel 4 | import mui.material.LabelPlacement 5 | import mui.material.Switch 6 | import mui.material.SwitchColor 7 | import react.ChildrenBuilder 8 | import react.ReactNode 9 | import react.create 10 | import ui.strings.Strings 11 | import ui.strings.string 12 | 13 | fun ChildrenBuilder.configurationSwitch( 14 | isOn: Boolean, 15 | onSwitched: (Boolean) -> Unit, 16 | labelStrings: Strings, 17 | ) { 18 | FormControlLabel { 19 | label = ReactNode(string(labelStrings)) 20 | control = 21 | Switch.create { 22 | color = SwitchColor.secondary 23 | checked = isOn 24 | onChange = { event, _ -> 25 | val checked = event.target.checked 26 | onSwitched(checked) 27 | } 28 | } 29 | labelPlacement = LabelPlacement.end 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/common/MessageBar.kt: -------------------------------------------------------------------------------- 1 | package ui.common 2 | 3 | import kotlinx.js.jso 4 | import mui.material.Alert 5 | import mui.material.AlertColor 6 | import mui.material.AlertVariant 7 | import mui.material.Snackbar 8 | import mui.material.SnackbarOriginHorizontal 9 | import mui.material.SnackbarOriginVertical 10 | import react.ChildrenBuilder 11 | 12 | fun ChildrenBuilder.messageBar( 13 | isShowing: Boolean, 14 | message: String, 15 | close: () -> Unit, 16 | color: AlertColor, 17 | ) = Snackbar { 18 | anchorOrigin = 19 | jso { 20 | vertical = SnackbarOriginVertical.bottom 21 | horizontal = SnackbarOriginHorizontal.center 22 | } 23 | autoHideDuration = 5000 // ms 24 | open = isShowing 25 | onClose = { _, _ -> close() } 26 | Alert { 27 | severity = color 28 | variant = AlertVariant.filled 29 | +message 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/common/Progress.kt: -------------------------------------------------------------------------------- 1 | package ui.common 2 | 3 | import csstype.AlignItems 4 | import csstype.Display 5 | import csstype.JustifyContent 6 | import emotion.react.css 7 | import mui.material.Backdrop 8 | import mui.material.CircularProgress 9 | import mui.material.CircularProgressColor 10 | import mui.material.Grid 11 | import mui.material.GridDirection 12 | import mui.material.Typography 13 | import mui.material.styles.TypographyVariant 14 | import mui.system.responsive 15 | import react.ChildrenBuilder 16 | 17 | data class ProgressProps( 18 | val isShowing: Boolean, 19 | val total: Int? = null, 20 | val current: Int? = null, 21 | ) { 22 | companion object { 23 | val Initial = ProgressProps(false) 24 | } 25 | } 26 | 27 | fun ChildrenBuilder.progress(props: ProgressProps) { 28 | Backdrop { 29 | open = props.isShowing 30 | Grid { 31 | css { 32 | display = Display.flex 33 | alignItems = AlignItems.center 34 | justifyContent = JustifyContent.center 35 | } 36 | container = true 37 | direction = responsive(GridDirection.column) 38 | spacing = responsive(3) 39 | Grid { 40 | item = true 41 | CircularProgress { 42 | color = CircularProgressColor.secondary 43 | disableShrink = true 44 | } 45 | } 46 | if (props.total != null && props.current != null && props.total > 1) { 47 | Grid { 48 | item = true 49 | Typography { 50 | variant = TypographyVariant.h6 51 | +"${props.current} / ${props.total}" 52 | } 53 | } 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/common/ScopedFC.kt: -------------------------------------------------------------------------------- 1 | package ui.common 2 | 3 | import kotlinx.coroutines.CoroutineScope 4 | import kotlinx.coroutines.Dispatchers 5 | import kotlinx.coroutines.cancel 6 | import react.ChildrenBuilder 7 | import react.FC 8 | import react.Props 9 | import react.useEffectOnce 10 | 11 | fun

scopedFC(block: ChildrenBuilder.(props: P, scope: CoroutineScope) -> Unit) = 12 | FC

{ props -> 13 | val scope = CoroutineScope(Dispatchers.Default) 14 | 15 | useEffectOnce { 16 | cleanup { scope.cancel() } 17 | } 18 | 19 | block(props, scope) 20 | } 21 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/common/SubFC.kt: -------------------------------------------------------------------------------- 1 | package ui.common 2 | 3 | import kotlinx.serialization.Serializable 4 | import react.ChildrenBuilder 5 | import react.FC 6 | import react.Props 7 | import react.StateSetter 8 | import react.useState 9 | 10 | external interface SubProps : Props { 11 | var initialState: T 12 | var submitState: StateSetter 13 | } 14 | 15 | @Serializable 16 | abstract class SubState { 17 | open val isReady: Boolean = true 18 | } 19 | 20 | fun

, T : SubState> subFC( 21 | block: ChildrenBuilder.(props: P, state: T, editState: (T.() -> T) -> Unit) -> Unit, 22 | ) = FC

{ props -> 23 | var state by useState(props.initialState) 24 | 25 | fun editState(editor: T.() -> T) { 26 | val newState = editor(state) 27 | state = newState 28 | props.submitState(newState) 29 | } 30 | block(props, state, ::editState) 31 | } 32 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/common/Title.kt: -------------------------------------------------------------------------------- 1 | package ui.common 2 | 3 | import csstype.px 4 | import kotlinx.js.jso 5 | import mui.material.Typography 6 | import mui.material.styles.TypographyVariant 7 | import react.ChildrenBuilder 8 | import ui.strings.Strings 9 | import ui.strings.string 10 | 11 | fun ChildrenBuilder.title(titleKey: Strings) = 12 | Typography { 13 | style = 14 | jso { 15 | marginTop = 45.px 16 | marginBottom = 20.px 17 | } 18 | variant = TypographyVariant.h3 19 | +string(titleKey) 20 | } 21 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/common/WarningDialog.kt: -------------------------------------------------------------------------------- 1 | package ui.common 2 | 3 | import csstype.px 4 | import kotlinx.browser.window 5 | import kotlinx.js.jso 6 | import mui.material.Alert 7 | import mui.material.AlertColor 8 | import mui.material.Button 9 | import mui.material.ButtonColor 10 | import mui.material.Dialog 11 | import mui.material.DialogActions 12 | import mui.material.DialogTitle 13 | import react.ChildrenBuilder 14 | import react.useCallback 15 | import react.useMemo 16 | import ui.strings.Strings 17 | import ui.strings.string 18 | 19 | fun ChildrenBuilder.warningDialog( 20 | id: String, 21 | state: DialogWarningState, 22 | close: () -> Unit, 23 | ) { 24 | val ignoredIds = 25 | useMemo(id) { 26 | window.localStorage 27 | .getItem("ignoredWarnings") 28 | ?.split(",") 29 | ?.toSet() ?: emptySet() 30 | } 31 | val ignored = 32 | useMemo(id) { 33 | ignoredIds.contains(id) 34 | } 35 | val closeAndIgnore = 36 | useCallback(id) { warningId: String -> 37 | window.localStorage.setItem("ignoredWarnings", (ignoredIds + warningId).joinToString(",")) 38 | close() 39 | } 40 | if (!ignored) { 41 | Dialog { 42 | open = state.isShowing 43 | onClose = { _, _ -> close() } 44 | DialogTitle { 45 | +state.title 46 | } 47 | Alert { 48 | severity = AlertColor.warning 49 | style = jso { borderRadius = 0.px } 50 | +state.message 51 | } 52 | DialogActions { 53 | Button { 54 | onClick = { close() } 55 | color = ButtonColor.inherit 56 | +string(Strings.ConfirmButton) 57 | } 58 | Button { 59 | onClick = { closeAndIgnore(id) } 60 | color = ButtonColor.secondary 61 | +string(Strings.DoNotShownAgainButton) 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | data class DialogWarningState( 69 | val isShowing: Boolean = false, 70 | val title: String = "", 71 | val message: String = "", 72 | ) 73 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/configuration/ChinesePinyinConversion.kt: -------------------------------------------------------------------------------- 1 | package ui.configuration 2 | 3 | import mui.material.FormGroup 4 | import react.dom.html.ReactHTML.div 5 | import ui.ChinesePinyinConversionState 6 | import ui.common.SubProps 7 | import ui.common.configurationSwitch 8 | import ui.common.subFC 9 | import ui.strings.Strings 10 | 11 | external interface ChinesePinyinConversionProps : SubProps 12 | 13 | val ChinesePinyinConversionBlock = 14 | subFC { _, state, editState -> 15 | FormGroup { 16 | div { 17 | configurationSwitch( 18 | isOn = state.isOn, 19 | onSwitched = { editState { copy(isOn = it) } }, 20 | labelStrings = Strings.ChinesePinyinConversion, 21 | ) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/configuration/LyricsConversion.kt: -------------------------------------------------------------------------------- 1 | package ui.configuration 2 | 3 | import core.model.Format 4 | import core.model.JapaneseLyricsType 5 | import core.model.Project 6 | import csstype.Length 7 | import csstype.Margin 8 | import csstype.px 9 | import emotion.react.css 10 | import mui.material.FormControl 11 | import mui.material.FormControlLabel 12 | import mui.material.FormControlMargin 13 | import mui.material.FormGroup 14 | import mui.material.FormLabel 15 | import mui.material.Paper 16 | import mui.material.Radio 17 | import mui.material.RadioColor 18 | import mui.material.RadioGroup 19 | import mui.material.Typography 20 | import mui.material.styles.TypographyVariant 21 | import react.ChildrenBuilder 22 | import react.create 23 | import react.dom.html.ReactHTML.div 24 | import ui.JapaneseLyricsConversionState 25 | import ui.common.SubProps 26 | import ui.common.configurationSwitch 27 | import ui.common.subFC 28 | import ui.strings.Strings 29 | import ui.strings.string 30 | 31 | external interface LyricsConversionProps : SubProps { 32 | var projects: List 33 | var outputFormat: Format 34 | } 35 | 36 | val JapaneseLyricsConversionBlock = 37 | subFC { props, state, editState -> 38 | FormGroup { 39 | configurationSwitch( 40 | isOn = state.isOn, 41 | onSwitched = { editState { copy(isOn = it) } }, 42 | labelStrings = Strings.JapaneseLyricsConversion, 43 | ) 44 | } 45 | 46 | if (state.isOn) { 47 | buildLyricsDetail( 48 | props = props, 49 | detectedType = state.detectedType, 50 | fromLyricsType = state.fromType, 51 | setFromLyricsType = { editState { copy(fromType = it) } }, 52 | toLyricsType = state.toType, 53 | setToLyricsType = { editState { copy(toType = it) } }, 54 | ) 55 | } 56 | } 57 | 58 | private fun ChildrenBuilder.buildLyricsDetail( 59 | props: LyricsConversionProps, 60 | detectedType: JapaneseLyricsType, 61 | fromLyricsType: JapaneseLyricsType?, 62 | setFromLyricsType: (JapaneseLyricsType) -> Unit, 63 | toLyricsType: JapaneseLyricsType?, 64 | setToLyricsType: (JapaneseLyricsType) -> Unit, 65 | ) { 66 | div { 67 | css { 68 | margin = Margin(horizontal = 40.px, vertical = 0.px) 69 | width = Length.maxContent 70 | } 71 | Paper { 72 | elevation = 0 73 | div { 74 | css { 75 | margin = 76 | Margin( 77 | horizontal = 24.px, 78 | top = 16.px, 79 | bottom = 24.px, 80 | ) 81 | } 82 | FormGroup { 83 | buildLyricsTypeControl( 84 | labelText = 85 | string( 86 | Strings.FromLyricsTypeLabel, 87 | "type" to detectedType.text, 88 | ), 89 | type = fromLyricsType, 90 | setType = setFromLyricsType, 91 | lyricTypeOptions = 92 | listOf( 93 | JapaneseLyricsType.RomajiCv, 94 | JapaneseLyricsType.RomajiVcv, 95 | JapaneseLyricsType.KanaCv, 96 | JapaneseLyricsType.KanaVcv, 97 | ), 98 | ) 99 | 100 | buildLyricsTypeControl( 101 | labelText = string(Strings.ToLyricsTypeLabel), 102 | type = toLyricsType, 103 | setType = setToLyricsType, 104 | lyricTypeOptions = props.outputFormat.possibleLyricsTypes, 105 | ) 106 | } 107 | } 108 | } 109 | } 110 | } 111 | 112 | private fun ChildrenBuilder.buildLyricsTypeControl( 113 | labelText: String, 114 | type: JapaneseLyricsType?, 115 | setType: (JapaneseLyricsType) -> Unit, 116 | lyricTypeOptions: List, 117 | ) { 118 | FormControl { 119 | margin = FormControlMargin.normal 120 | FormLabel { 121 | focused = false 122 | +labelText 123 | } 124 | RadioGroup { 125 | row = true 126 | value = type?.name.orEmpty() 127 | onChange = { event, _ -> 128 | val value = event.target.value 129 | setType(JapaneseLyricsType.valueOf(value)) 130 | } 131 | lyricTypeOptions.forEach { lyricsType -> 132 | FormControlLabel { 133 | value = lyricsType.name 134 | control = 135 | Radio.create { 136 | color = RadioColor.secondary 137 | } 138 | label = 139 | Typography.create { 140 | variant = TypographyVariant.subtitle2 141 | +lyricsType.text 142 | } 143 | } 144 | } 145 | } 146 | } 147 | } 148 | 149 | private val JapaneseLyricsType.text get() = string(strings) 150 | 151 | private val JapaneseLyricsType.strings 152 | get() = 153 | when (this) { 154 | JapaneseLyricsType.RomajiCv -> Strings.LyricsTypeRomajiCV 155 | JapaneseLyricsType.RomajiVcv -> Strings.LyricsTypeRomajiVCV 156 | JapaneseLyricsType.KanaCv -> Strings.LyricsTypeKanaCV 157 | JapaneseLyricsType.KanaVcv -> Strings.LyricsTypeKanaVCV 158 | JapaneseLyricsType.Unknown -> Strings.LyricsTypeUnknown 159 | } 160 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/configuration/PitchConversion.kt: -------------------------------------------------------------------------------- 1 | package ui.configuration 2 | 3 | import csstype.VerticalAlign 4 | import kotlinx.js.jso 5 | import mui.icons.material.ErrorOutline 6 | import mui.material.FormGroup 7 | import mui.material.Tooltip 8 | import mui.material.TooltipPlacement 9 | import react.ReactNode 10 | import react.dom.html.ReactHTML.div 11 | import ui.PitchConversionState 12 | import ui.common.SubProps 13 | import ui.common.configurationSwitch 14 | import ui.common.subFC 15 | import ui.strings.Strings 16 | import ui.strings.string 17 | 18 | external interface PitchConversionProps : SubProps 19 | 20 | val PitchConversionBlock = 21 | subFC { _, state, editState -> 22 | FormGroup { 23 | div { 24 | configurationSwitch( 25 | isOn = state.isOn, 26 | onSwitched = { editState { copy(isOn = it) } }, 27 | labelStrings = Strings.ConvertPitchData, 28 | ) 29 | Tooltip { 30 | title = ReactNode(string(Strings.ConvertPitchDataDescription)) 31 | placement = TooltipPlacement.right 32 | disableInteractive = false 33 | ErrorOutline { 34 | style = 35 | jso { 36 | verticalAlign = VerticalAlign.middle 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/configuration/ProjectSplit.kt: -------------------------------------------------------------------------------- 1 | package ui.configuration 2 | 3 | import csstype.Length 4 | import csstype.Margin 5 | import csstype.VerticalAlign 6 | import csstype.em 7 | import csstype.px 8 | import emotion.react.css 9 | import kotlinx.js.jso 10 | import mui.icons.material.HelpOutline 11 | import mui.material.BaseTextFieldProps 12 | import mui.material.Box 13 | import mui.material.FormControl 14 | import mui.material.FormControlMargin 15 | import mui.material.FormControlVariant 16 | import mui.material.FormGroup 17 | import mui.material.FormLabel 18 | import mui.material.Paper 19 | import mui.material.StandardTextFieldProps 20 | import mui.material.TextField 21 | import mui.material.Tooltip 22 | import mui.material.TooltipPlacement 23 | import mui.material.Typography 24 | import mui.material.styles.TypographyVariant 25 | import mui.system.sx 26 | import react.ChildrenBuilder 27 | import react.ReactNode 28 | import react.dom.html.ReactHTML.div 29 | import ui.ProjectSplitState 30 | import ui.common.SubProps 31 | import ui.common.configurationSwitch 32 | import ui.common.subFC 33 | import ui.strings.Strings 34 | import ui.strings.string 35 | 36 | external interface ProjectSplitProps : SubProps 37 | 38 | val ProjectSplitBlock = 39 | subFC { _, state, editState -> 40 | FormGroup { 41 | div { 42 | configurationSwitch( 43 | isOn = state.isOn, 44 | onSwitched = { editState { copy(isOn = it) } }, 45 | labelStrings = Strings.ProjectSplit, 46 | ) 47 | Tooltip { 48 | title = ReactNode(string(Strings.ProjectSplitDescription)) 49 | placement = TooltipPlacement.right 50 | disableInteractive = false 51 | HelpOutline { 52 | style = 53 | jso { 54 | verticalAlign = VerticalAlign.middle 55 | } 56 | } 57 | } 58 | } 59 | } 60 | if (state.isOn) buildProjectSplitDetail(state, editState) 61 | } 62 | 63 | private fun ChildrenBuilder.buildProjectSplitDetail( 64 | state: ProjectSplitState, 65 | editState: (ProjectSplitState.() -> ProjectSplitState) -> Unit, 66 | ) { 67 | div { 68 | css { 69 | margin = Margin(horizontal = 40.px, vertical = 0.px) 70 | width = Length.maxContent 71 | } 72 | Paper { 73 | elevation = 0 74 | Box { 75 | style = 76 | jso { 77 | margin = 78 | Margin( 79 | left = 24.px, 80 | right = 48.px, 81 | top = 16.px, 82 | bottom = 16.px, 83 | ) 84 | paddingBottom = 8.px 85 | } 86 | sx { minWidth = 15.em } 87 | FormControl { 88 | FormLabel { 89 | focused = false 90 | Typography { 91 | variant = TypographyVariant.caption 92 | +string(Strings.ProjectSplitMaxTrackCountLabel) 93 | } 94 | } 95 | margin = FormControlMargin.normal 96 | variant = FormControlVariant.standard 97 | focused = false 98 | TextField { 99 | sx { width = 5.em } 100 | value = state.maxTrackCountInput 101 | (this.unsafeCast()).variant = FormControlVariant.standard 102 | (this.unsafeCast()).onChange = { event -> 103 | val value = event.target.asDynamic().value as String 104 | editState { copy(maxTrackCountInput = value) } 105 | } 106 | error = state.isReady.not() 107 | } 108 | } 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/configuration/ProjectZoom.kt: -------------------------------------------------------------------------------- 1 | package ui.configuration 2 | 3 | import core.model.Project 4 | import core.process.needWarningZoom 5 | import core.process.projectZoomFactorOptions 6 | import csstype.Length 7 | import csstype.Margin 8 | import csstype.VerticalAlign 9 | import csstype.em 10 | import csstype.px 11 | import emotion.react.css 12 | import kotlinx.js.jso 13 | import mui.icons.material.ErrorOutline 14 | import mui.icons.material.HelpOutline 15 | import mui.material.BaseTextFieldProps 16 | import mui.material.Box 17 | import mui.material.FormControl 18 | import mui.material.FormControlMargin 19 | import mui.material.FormControlVariant 20 | import mui.material.FormGroup 21 | import mui.material.FormLabel 22 | import mui.material.MenuItem 23 | import mui.material.Paper 24 | import mui.material.StandardTextFieldProps 25 | import mui.material.TextField 26 | import mui.material.Tooltip 27 | import mui.material.TooltipPlacement 28 | import mui.material.Typography 29 | import mui.material.styles.TypographyVariant 30 | import mui.system.sx 31 | import react.ChildrenBuilder 32 | import react.ReactNode 33 | import react.dom.html.ReactHTML.div 34 | import ui.ProjectZoomState 35 | import ui.common.SubProps 36 | import ui.common.configurationSwitch 37 | import ui.common.subFC 38 | import ui.strings.Strings 39 | import ui.strings.string 40 | 41 | external interface ProjectZoomProps : SubProps { 42 | var projects: List 43 | } 44 | 45 | val ProjectZoomBlock = 46 | subFC { props, state, editState -> 47 | FormGroup { 48 | div { 49 | configurationSwitch( 50 | isOn = state.isOn, 51 | onSwitched = { editState { copy(isOn = it) } }, 52 | labelStrings = Strings.ProjectZoom, 53 | ) 54 | Tooltip { 55 | title = ReactNode(string(Strings.ProjectZoomDescription)) 56 | placement = TooltipPlacement.right 57 | disableInteractive = false 58 | HelpOutline { 59 | style = 60 | jso { 61 | verticalAlign = VerticalAlign.middle 62 | } 63 | } 64 | } 65 | if (props.projects.any { it.needWarningZoom(state.factorValue) }) { 66 | Tooltip { 67 | title = ReactNode(string(Strings.ProjectZoomWarning)) 68 | placement = TooltipPlacement.right 69 | disableInteractive = false 70 | ErrorOutline { 71 | style = 72 | jso { 73 | verticalAlign = VerticalAlign.middle 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | if (state.isOn) buildProjectZoomDetail(state, editState) 81 | } 82 | 83 | private fun ChildrenBuilder.buildProjectZoomDetail( 84 | state: ProjectZoomState, 85 | editState: (ProjectZoomState.() -> ProjectZoomState) -> Unit, 86 | ) { 87 | div { 88 | css { 89 | margin = Margin(horizontal = 40.px, vertical = 0.px) 90 | width = Length.maxContent 91 | } 92 | Paper { 93 | elevation = 0 94 | Box { 95 | style = 96 | jso { 97 | margin = 98 | Margin( 99 | left = 24.px, 100 | right = 48.px, 101 | top = 16.px, 102 | bottom = 16.px, 103 | ) 104 | paddingBottom = 8.px 105 | } 106 | sx { minWidth = 15.em } 107 | FormControl { 108 | FormLabel { 109 | focused = false 110 | Typography { 111 | variant = TypographyVariant.caption 112 | +string(Strings.ProjectZoomLabel) 113 | } 114 | } 115 | margin = FormControlMargin.normal 116 | variant = FormControlVariant.standard 117 | focused = false 118 | TextField { 119 | style = jso { minWidth = 5.em } 120 | select = true 121 | value = state.factor.unsafeCast() 122 | (this.unsafeCast()).variant = FormControlVariant.standard 123 | (this.unsafeCast()).onChange = { event -> 124 | val value = event.target.asDynamic().value as String 125 | editState { copy(factor = value) } 126 | } 127 | projectZoomFactorOptions.forEach { factor -> 128 | MenuItem { 129 | value = factor 130 | +(factor) 131 | } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/configuration/SlightRestFilling.kt: -------------------------------------------------------------------------------- 1 | package ui.configuration 2 | 3 | import core.process.restsFillingMaxLengthDenominatorOptions 4 | import csstype.Length 5 | import csstype.Margin 6 | import csstype.VerticalAlign 7 | import csstype.em 8 | import csstype.px 9 | import emotion.react.css 10 | import kotlinx.js.jso 11 | import mui.icons.material.HelpOutline 12 | import mui.material.BaseTextFieldProps 13 | import mui.material.Box 14 | import mui.material.FormControl 15 | import mui.material.FormControlMargin 16 | import mui.material.FormControlVariant 17 | import mui.material.FormGroup 18 | import mui.material.FormLabel 19 | import mui.material.MenuItem 20 | import mui.material.Paper 21 | import mui.material.StandardTextFieldProps 22 | import mui.material.TextField 23 | import mui.material.Tooltip 24 | import mui.material.TooltipPlacement 25 | import mui.material.Typography 26 | import mui.material.styles.TypographyVariant 27 | import mui.system.sx 28 | import react.ChildrenBuilder 29 | import react.ReactNode 30 | import react.dom.html.ReactHTML.div 31 | import ui.SlightRestsFillingState 32 | import ui.common.SubProps 33 | import ui.common.configurationSwitch 34 | import ui.common.subFC 35 | import ui.strings.Strings 36 | import ui.strings.string 37 | 38 | external interface SlightRestsFillingProps : SubProps 39 | 40 | val SlightRestsFillingBlock = 41 | subFC { _, state, editState -> 42 | FormGroup { 43 | div { 44 | configurationSwitch( 45 | isOn = state.isOn, 46 | onSwitched = { editState { copy(isOn = it) } }, 47 | labelStrings = Strings.SlightRestsFilling, 48 | ) 49 | Tooltip { 50 | title = ReactNode(string(Strings.SlightRestsFillingDescription)) 51 | placement = TooltipPlacement.right 52 | disableInteractive = false 53 | HelpOutline { 54 | style = 55 | jso { 56 | verticalAlign = VerticalAlign.middle 57 | } 58 | } 59 | } 60 | } 61 | } 62 | 63 | if (state.isOn) buildRestsFillingDetail(state, editState) 64 | } 65 | 66 | private fun ChildrenBuilder.buildRestsFillingDetail( 67 | state: SlightRestsFillingState, 68 | editState: (SlightRestsFillingState.() -> SlightRestsFillingState) -> Unit, 69 | ) { 70 | div { 71 | css { 72 | margin = Margin(horizontal = 40.px, vertical = 0.px) 73 | width = Length.maxContent 74 | } 75 | Paper { 76 | elevation = 0 77 | Box { 78 | style = 79 | jso { 80 | margin = 81 | Margin( 82 | left = 24.px, 83 | right = 48.px, 84 | top = 16.px, 85 | bottom = 16.px, 86 | ) 87 | paddingBottom = 8.px 88 | } 89 | sx { minWidth = 15.em } 90 | FormControl { 91 | margin = FormControlMargin.normal 92 | variant = FormControlVariant.standard 93 | focused = false 94 | FormLabel { 95 | focused = false 96 | Typography { 97 | variant = TypographyVariant.caption 98 | +string(Strings.SlightRestsFillingThresholdLabel) 99 | } 100 | } 101 | TextField { 102 | style = jso { minWidth = 5.em } 103 | select = true 104 | value = state.excludedMaxLengthDenominator.toString().unsafeCast() 105 | (this.unsafeCast()).variant = FormControlVariant.standard 106 | (this.unsafeCast()).onChange = { event -> 107 | val value = event.target.asDynamic().value as String 108 | editState { copy(excludedMaxLengthDenominator = value.toInt()) } 109 | } 110 | restsFillingMaxLengthDenominatorOptions.forEach { denominator -> 111 | MenuItem { 112 | value = denominator.toString() 113 | +string( 114 | Strings.SlightRestsFillingThresholdItem, 115 | "denominator" to denominator.toString(), 116 | ) 117 | } 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/external/Cookies.kt: -------------------------------------------------------------------------------- 1 | package ui.external 2 | 3 | @JsModule("js-cookie") 4 | @JsNonModule 5 | external class Cookies { 6 | companion object { 7 | fun set( 8 | key: String, 9 | value: String, 10 | ) 11 | 12 | fun get(key: String): String? 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/external/react/FileDrop.kt: -------------------------------------------------------------------------------- 1 | @file:JsModule("react-file-drop") 2 | @file:JsNonModule 3 | 4 | package ui.external.react 5 | 6 | import org.w3c.dom.events.Event 7 | import org.w3c.files.FileList 8 | import react.ComponentClass 9 | import react.Props 10 | 11 | @JsName("FileDrop") 12 | external val FileDrop: ComponentClass 13 | 14 | external interface FileDropProps : Props { 15 | var onDrop: (FileList, Event) -> Unit 16 | } 17 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/external/react/Markdown.kt: -------------------------------------------------------------------------------- 1 | package ui.external.react 2 | 3 | import react.ComponentClass 4 | import react.Props 5 | 6 | @JsModule("react-markdown") 7 | @JsNonModule 8 | external val Markdown: ComponentClass 9 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/model/Stage.kt: -------------------------------------------------------------------------------- 1 | package ui.model 2 | 3 | import ui.strings.Strings 4 | import ui.strings.string 5 | 6 | enum class Stage( 7 | val index: Int, 8 | private val displayNameKey: Strings?, 9 | ) { 10 | ExtraPage(-1, null), 11 | Import(0, Strings.ImportProjectCaption), 12 | SelectOutputFormat(1, Strings.SelectOutputFormatCaption), 13 | Configure(2, Strings.ConfigurationEditorCaption), 14 | Export(3, Strings.ExportCaption), 15 | ; 16 | 17 | val displayName get() = displayNameKey?.let { string(it) } 18 | 19 | companion object { 20 | val forStepper get() = listOf(Import, SelectOutputFormat, Configure, Export) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/model/StageInfo.kt: -------------------------------------------------------------------------------- 1 | package ui.model 2 | 3 | import core.model.ExportResult 4 | import core.model.Format 5 | import core.model.Project 6 | import ui.strings.Strings 7 | 8 | sealed class StageInfo( 9 | val stage: Stage, 10 | ) { 11 | object Import : StageInfo(Stage.Import) 12 | 13 | data class SelectOutputFormat( 14 | val projects: List, 15 | ) : StageInfo(Stage.SelectOutputFormat) 16 | 17 | data class Configure( 18 | val projects: List, 19 | val outputFormat: Format, 20 | ) : StageInfo(Stage.Configure) 21 | 22 | data class Export( 23 | val results: List, 24 | val outputFormat: Format, 25 | ) : StageInfo(Stage.Export) 26 | 27 | data class ExtraPage( 28 | val urlKey: Strings, 29 | ) : StageInfo(Stage.ExtraPage) 30 | } 31 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/strings/I18n.kt: -------------------------------------------------------------------------------- 1 | package ui.strings 2 | 3 | import core.external.require 4 | import kotlin.coroutines.resume 5 | import kotlin.coroutines.suspendCoroutine 6 | 7 | val i18next = core.external.require("i18next").default 8 | 9 | suspend fun initializeI18n(defaultLanguage: Language) = 10 | suspendCoroutine { cont -> 11 | val reactI18next = core.external.require("react-i18next").initReactI18next 12 | val languageDetector = core.external.require("i18next-browser-languagedetector").default 13 | val options = object {}.asDynamic() 14 | options["detection"] = 15 | object {}.also { 16 | val detection = it.asDynamic() 17 | detection["order"] = arrayOf("cookie", "localStorage", "navigator") 18 | detection["caches"] = arrayOf("cookie", "localStorage") 19 | detection["cookieMinutes"] = 60 * 24 * 90 20 | detection["lookupCookie"] = "i18next" 21 | detection["lookupLocalStorage"] = "i18nextLng" 22 | } 23 | options["fallbackLng"] = defaultLanguage.code 24 | options["interpolation"] = object {}.also { it.asDynamic()["escapeValue"] = false } 25 | options["resources"] = 26 | object {}.also { resources -> 27 | Language.values().forEach { language -> 28 | val translation = object {}.asDynamic() 29 | Strings.values().forEach { 30 | translation[it.name] = it.get(language) 31 | } 32 | resources.asDynamic()[language.code] = 33 | object {}.also { it.asDynamic()["translation"] = translation } 34 | } 35 | } 36 | i18next 37 | .use(reactI18next) 38 | .use(languageDetector) 39 | .init(options) 40 | .then { 41 | val languageName = i18next.t(Strings.LanguageDisplayName.name) 42 | console.log("i18n is initialized with language: $languageName") 43 | cont.resume(i18next) 44 | } 45 | Unit 46 | } 47 | 48 | suspend fun changeLanguage(code: String) = 49 | suspendCoroutine { cont -> 50 | i18next 51 | .changeLanguage(code) 52 | .then { 53 | val languageName = i18next.t(Strings.LanguageDisplayName.name) 54 | console.log("i18n has changed language to: $languageName") 55 | cont.resume(Unit) 56 | } 57 | Unit 58 | } 59 | -------------------------------------------------------------------------------- /src/jsMain/kotlin/ui/strings/Language.kt: -------------------------------------------------------------------------------- 1 | package ui.strings 2 | 3 | enum class Language( 4 | val code: String, 5 | val displayName: String, 6 | ) { 7 | English("en", "English"), 8 | Japanese("ja", "日本語"), 9 | SimplifiedChinese("zh-CN", "简体中文"), 10 | Russian("ru", "Русский"), 11 | French("fr", "Français"), 12 | } 13 | -------------------------------------------------------------------------------- /src/jsMain/resources/fileDrop.css: -------------------------------------------------------------------------------- 1 | /* https://sarink.github.io/react-file-drop/ */ 2 | 3 | .file-drop { 4 | /* relatively position the container bc the contents are absolute */ 5 | position: relative; 6 | height: 400px; 7 | width: 100%; 8 | background-color: #424242; 9 | margin-bottom: 20px; 10 | } 11 | 12 | .file-drop > .file-drop-target { 13 | /* basic styles */ 14 | position: absolute; 15 | top: 0; 16 | left: 0; 17 | height: 100%; 18 | width: 100%; 19 | border-radius: 2px; 20 | 21 | /* horizontally and vertically center all content */ 22 | display: flex; 23 | display: -webkit-box; 24 | display: -webkit-flex; 25 | display: -ms-flexbox; 26 | 27 | flex-direction: column; 28 | -webkit-box-orient: vertical; 29 | -webkit-box-direction: normal; 30 | -webkit-flex-direction: column; 31 | -ms-flex-direction: column; 32 | 33 | align-items: center; 34 | -webkit-box-align: center; 35 | -webkit-align-items: center; 36 | -ms-flex-align: center; 37 | 38 | justify-content: center; 39 | -webkit-box-pack: center; 40 | -webkit-justify-content: center; 41 | -ms-flex-pack: center; 42 | 43 | align-content: center; 44 | -webkit-align-content: center; 45 | -ms-flex-line-pack: center; 46 | 47 | text-align: center; 48 | } 49 | 50 | .file-drop > .file-drop-target.file-drop-dragging-over-frame { 51 | /* overlay a black mask when dragging over the frame */ 52 | border: none; 53 | background-color: rgba(255, 255, 255, 0.65); 54 | box-shadow: none; 55 | z-index: 50; 56 | opacity: 0.5; 57 | 58 | /* typography */ 59 | color: white; 60 | } 61 | 62 | .file-drop > .file-drop-target.file-drop-dragging-over-target { 63 | /* turn stuff orange when we are dragging over the target */ 64 | /* color: #ff6e40; */ 65 | /* box-shadow: 0 0 13px 3px #ff6e40; */ 66 | } 67 | 68 | .file-drop > .file-drop-target > div > p { 69 | margin: 12px; 70 | } 71 | -------------------------------------------------------------------------------- /src/jsMain/resources/images/cevio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/cevio.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/dv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/dv.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/midi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/midi.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/openutau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/openutau.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/svr1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/svr1.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/svr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/svr2.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/ufdata.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/ufdata.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/utau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/utau.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/vocaloid1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/vocaloid1.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/vocaloid2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/vocaloid2.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/vocaloid4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/vocaloid4.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/vocaloid5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/vocaloid5.png -------------------------------------------------------------------------------- /src/jsMain/resources/images/voisona.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sdercolin/utaformatix3/f3c83354f57894492410bbc5ba03f7e97169c92c/src/jsMain/resources/images/voisona.png -------------------------------------------------------------------------------- /src/jsMain/resources/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | UtaFormatix 6 | 7 | 8 | 9 | 10 | 11 | 12 | 19 | 20 | 21 |

22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/jsMain/resources/link.css: -------------------------------------------------------------------------------- 1 | /* unvisited link */ 2 | a:link { 3 | color: grey; 4 | } 5 | 6 | /* visited link */ 7 | a:visited { 8 | color: grey; 9 | } 10 | 11 | /* mouse over link */ 12 | a:hover { 13 | color: white; 14 | } 15 | 16 | /* selected link */ 17 | a:active { 18 | color: grey; 19 | } 20 | -------------------------------------------------------------------------------- /src/jsMain/resources/scrollbar.css: -------------------------------------------------------------------------------- 1 | ::-webkit-scrollbar { 2 | background: #ffffff1f; 3 | border-radius: 3px; 4 | width: 12px; 5 | } 6 | 7 | ::-webkit-scrollbar-thumb { 8 | background: #ffffff3f; 9 | border-radius: 6px; 10 | } 11 | 12 | * { 13 | scrollbar-color: #ffffff3f #ffffff1f; 14 | scrollbar-width: thin; 15 | } 16 | -------------------------------------------------------------------------------- /src/jsTest/kotlin/process/lyrics/LyricsReplacementTest.kt: -------------------------------------------------------------------------------- 1 | package process.lyrics 2 | 3 | import core.model.Format 4 | import core.model.Note 5 | import core.model.Track 6 | import core.process.lyrics.LyricsReplacementRequest 7 | import core.process.lyrics.replaceLyrics 8 | import kotlin.test.Test 9 | import kotlin.test.assertEquals 10 | 11 | class LyricsReplacementTest { 12 | @Test 13 | fun testSimple() { 14 | val lyrics = listOf("as", "ad", "ff", "qqw") 15 | val request = 16 | LyricsReplacementRequest( 17 | listOf( 18 | LyricsReplacementRequest.Item( 19 | filterType = LyricsReplacementRequest.FilterType.Exact, 20 | filter = "ff", 21 | matchType = LyricsReplacementRequest.MatchType.All, 22 | from = "", 23 | to = "cc", 24 | ), 25 | ), 26 | ) 27 | val result = lyrics.map { request.doReplace(it) } 28 | val expected = listOf("as", "ad", "cc", "qqw") 29 | assertEquals(expected, result) 30 | } 31 | 32 | @Test 33 | fun testRegex() { 34 | val lyrics = listOf("a_as_R", "ad", "f_af_R", "qqw", "f_af_Rr") 35 | val request = 36 | LyricsReplacementRequest( 37 | listOf( 38 | LyricsReplacementRequest.Item( 39 | filterType = LyricsReplacementRequest.FilterType.Regex, 40 | filter = """\w+_\w+_R""", 41 | matchType = LyricsReplacementRequest.MatchType.Regex, 42 | from = """(\w+)_(\w+)_R""", 43 | to = "$1_$2_$1$2", 44 | ), 45 | ), 46 | ) 47 | val result = lyrics.map { request.doReplace(it) } 48 | val expected = listOf("a_as_aas", "ad", "f_af_faf", "qqw", "f_af_Rr") 49 | assertEquals(expected, result) 50 | } 51 | 52 | @Test 53 | fun testRegex2() { 54 | val lyrics = listOf("あ", "i あ", "e か") 55 | val request = 56 | LyricsReplacementRequest( 57 | listOf( 58 | LyricsReplacementRequest.Item( 59 | filterType = LyricsReplacementRequest.FilterType.None, 60 | filter = "", 61 | matchType = LyricsReplacementRequest.MatchType.Regex, 62 | from = """^. (.+)$""", 63 | to = "$1", 64 | ), 65 | ), 66 | ) 67 | val result = lyrics.map { request.doReplace(it) } 68 | val expected = listOf("あ", "あ", "か") 69 | assertEquals(expected, result) 70 | } 71 | 72 | @Test 73 | fun testRemoveNotes() { 74 | val track = 75 | Track( 76 | id = 0, 77 | notes = 78 | listOf( 79 | Note(0, 60, "a", 0L, 1L), 80 | Note(1, 60, "a R", 1L, 2L), 81 | Note(2, 60, "a", 2L, 3L), 82 | Note(3, 60, "R", 3L, 4L), 83 | Note(4, 60, "a", 4L, 5L), 84 | ), 85 | name = "", 86 | pitch = null, 87 | ) 88 | val request = requireNotNull(LyricsReplacementRequest.getPreset(Format.Ust, Format.Ust)) 89 | val result = track.replaceLyrics(request) 90 | val expected = 91 | listOf( 92 | Note(0, 60, "a", 0L, 1L), 93 | Note(1, 60, "a", 2L, 3L), 94 | Note(2, 60, "a", 4L, 5L), 95 | ) 96 | assertEquals(expected, result.notes) 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/jsTest/kotlin/process/phonemes/PhonemesMappingTest.kt: -------------------------------------------------------------------------------- 1 | package process.phonemes 2 | 3 | import core.model.Note 4 | import core.process.phonemes.PhonemesMappingRequest 5 | import core.process.phonemes.replacePhonemes 6 | import kotlin.test.Test 7 | import kotlin.test.assertEquals 8 | 9 | class PhonemesMappingTest { 10 | private val request = 11 | PhonemesMappingRequest( 12 | mapText = 13 | """ 14 | a=A 15 | b=B 16 | c a=C' A 17 | c=C 18 | d c a=DC' A 19 | d c=DC 20 | sil= 21 | s=S 22 | sh=SH 23 | effff=EF 24 | effff d=EF D 25 | Q=a 26 | OI=Q 27 | """.trimIndent(), 28 | ) 29 | 30 | private fun createNote(phoneme: String) = 31 | Note( 32 | id = 0, 33 | key = 60, 34 | lyric = "", 35 | tickOn = 0L, 36 | tickOff = 480L, 37 | phoneme = phoneme, 38 | ) 39 | 40 | @Test 41 | fun testNoMatch() { 42 | val note = createNote("l o") 43 | val actual = note.replacePhonemes(request).phoneme 44 | assertEquals("l o", actual) 45 | } 46 | 47 | @Test 48 | fun testSingleMatch() { 49 | val note = createNote("b") 50 | val actual = note.replacePhonemes(request).phoneme 51 | assertEquals("B", actual) 52 | } 53 | 54 | @Test 55 | fun testSingleInMultipleMatch() { 56 | val note = createNote("l a m b n") 57 | val actual = note.replacePhonemes(request).phoneme 58 | assertEquals("l A m B n", actual) 59 | } 60 | 61 | @Test 62 | fun testMultipleMatch() { 63 | val note = createNote("c a") 64 | val actual = note.replacePhonemes(request).phoneme 65 | assertEquals("C' A", actual) 66 | } 67 | 68 | @Test 69 | fun testMultipleInMultipleMatch() { 70 | val note = createNote("d c a m d c") 71 | val actual = note.replacePhonemes(request).phoneme 72 | assertEquals("DC' A m DC", actual) 73 | } 74 | 75 | @Test 76 | fun testRepeatedMultipleMatch() { 77 | val note = createNote("d c a d c a") 78 | val actual = note.replacePhonemes(request).phoneme 79 | assertEquals("DC' A DC' A", actual) 80 | } 81 | 82 | @Test 83 | fun testMutedPhoneme() { 84 | val note = createNote("sil a") 85 | val actual = note.replacePhonemes(request).phoneme 86 | assertEquals("A", actual) 87 | } 88 | 89 | @Test 90 | fun testSortLength() { 91 | val note = createNote("sh") 92 | val actual = note.replacePhonemes(request).phoneme 93 | assertEquals("SH", actual) 94 | } 95 | 96 | @Test 97 | fun testSortMultiLength() { 98 | val note = createNote("effff d c a") 99 | val actual = note.replacePhonemes(request).phoneme 100 | assertEquals("EF DC' A", actual) 101 | } 102 | 103 | @Test 104 | fun testSameTextDifferentPhoneme() { 105 | val note = createNote("OI") 106 | val actual = note.replacePhonemes(request).phoneme 107 | assertEquals("Q", actual) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/jsTest/kotlin/process/pitch/PitchCalculationTest.kt: -------------------------------------------------------------------------------- 1 | package process.pitch 2 | 3 | import core.process.pitch.appendPitchPointsForInterpolation 4 | import core.process.pitch.reduceRepeatedPitchPoints 5 | import kotlin.test.Test 6 | import kotlin.test.assertEquals 7 | 8 | class PitchCalculationTest { 9 | @Test 10 | fun testAppendPitchPointsForInterpolation() { 11 | val input = 12 | listOf( 13 | 0L to 0.0, 14 | 3L to 6.0, 15 | 10L to 20.0, 16 | 50L to 100.0, 17 | ) 18 | val interval = 4L 19 | 20 | val expected = 21 | listOf( 22 | 0L to 0.0, 23 | 3L to 6.0, 24 | 6L to 6.0, 25 | 10L to 20.0, 26 | 46L to 20.0, 27 | 50L to 100.0, 28 | ) 29 | val actual = appendPitchPointsForInterpolation(input, interval) 30 | 31 | assertEquals(expected, actual) 32 | } 33 | 34 | @Test 35 | fun testReduceRepeatedPitchPoints() { 36 | val input = 37 | listOf(0, 0, 0, 1, 1, 1, 2, 2, 1, 3, 2, 3, 3, 3, 3, 3, 4, 5, 5, 5) 38 | .mapIndexed { index: Int, i: Int -> index.toLong() to i.toDouble() } 39 | 40 | val expected = listOf(0, null, 0, 1, null, 1, 2, 2, 1, 3, 2, 3, null, null, null, 3, 4, 5, null, 5) 41 | val reduced = input.reduceRepeatedPitchPoints() 42 | val actual = 43 | input.indices.map { i -> 44 | reduced.find { it.first.toInt() == i }?.second?.toInt() 45 | } 46 | 47 | assertEquals(expected, actual) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/jsTest/kotlin/process/pitch/TickTimeTransformerTest.kt: -------------------------------------------------------------------------------- 1 | package process.pitch 2 | 3 | import core.model.Tempo 4 | import core.process.pitch.TickTimeTransformer 5 | import kotlin.test.Test 6 | import kotlin.test.assertEquals 7 | 8 | class TickTimeTransformerTest { 9 | // when bpm = 125.0, 1 tick = 1 milli-second 10 | private val transformer = 11 | TickTimeTransformer( 12 | listOf( 13 | Tempo(0, 125.0), 14 | Tempo(10000, 250.0), 15 | Tempo(20000, 125.0), 16 | ), 17 | ) 18 | 19 | @Test 20 | fun testTickToSec() { 21 | val ticks = listOf(0L, 5000L, 10000L, 15000L, 20000L, 30000L) 22 | val actual = ticks.map { transformer.tickToSec(it) } 23 | val expected = listOf(0.0, 5.0, 10.0, 12.5, 15.0, 25.0) 24 | assertEquals(expected, actual) 25 | } 26 | 27 | @Test 28 | fun testSecToTick() { 29 | val seconds = listOf(0.0, 5.0, 10.0, 12.5, 15.0, 25.0) 30 | val actual = seconds.map { transformer.secToTick(it) } 31 | val expected = listOf(0L, 5000L, 10000L, 15000L, 20000L, 30000L) 32 | assertEquals(expected, actual) 33 | } 34 | 35 | @Test 36 | fun testTickDistanceToSec() { 37 | val start = 8000L 38 | val end = 12000L 39 | val actual = transformer.tickDistanceToSec(start, end) 40 | val expected = 3.0 41 | assertEquals(expected, actual) 42 | } 43 | 44 | @Test 45 | fun testNegativeInput() { 46 | val second = -5.0 47 | val tick = -5000L 48 | println(transformer.secToTick(second)) 49 | assertEquals(tick, transformer.secToTick(second)) 50 | assertEquals(second, transformer.tickToSec(tick)) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /webpack.config.d/global.js: -------------------------------------------------------------------------------- 1 | if (typeof config.output !== 'undefined') { 2 | config.output.globalObject = `(typeof self !== 'undefined' ? self : this)` 3 | } 4 | config.resolve.fallback = {"stream": require.resolve("stream-browserify"), "buffer": require.resolve("buffer/")} 5 | -------------------------------------------------------------------------------- /webpack.config.d/resources.js: -------------------------------------------------------------------------------- 1 | config.module.rules.push({ 2 | test: /\.vsqx$/i, 3 | loader: 'raw-loader' 4 | }, { 5 | test: /\.vprjson$/i, 6 | loader: 'raw-loader' 7 | }, { 8 | test: /\.svp$/i, 9 | loader: 'raw-loader' 10 | }, { 11 | test: /\.s5p$/i, 12 | loader: 'raw-loader' 13 | }, { 14 | test: /\.musicxml/i, 15 | loader: 'raw-loader' 16 | }, { 17 | test: /\.ccs$/i, 18 | loader: 'raw-loader' 19 | }, { 20 | test: /\.ustx/i, 21 | loader: 'raw-loader' 22 | }, { 23 | test: /\.txt/i, 24 | loader: 'raw-loader' 25 | }, { 26 | test: /\.(png|jpe?g|gif)$/i, 27 | use: [ 28 | { 29 | loader: 'file-loader', 30 | }, 31 | ], 32 | }); 33 | --------------------------------------------------------------------------------