From 907e2375b95ba0115b8695bb75da2d343cc45532 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Wed, 28 Feb 2024 20:34:11 +0900 Subject: [PATCH 01/35] =?UTF-8?q?Fix:=20=E5=BF=85=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E3=82=BB=E3=83=83=E3=83=88=E3=82=A2=E3=83=83=E3=83=97=E3=81=8C?= =?UTF-8?q?=E8=A1=8C=E3=82=8F=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=8B?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=20(#1882?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sing/SingEditor.vue | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/components/Sing/SingEditor.vue b/src/components/Sing/SingEditor.vue index d375b05820..27da8b6c09 100644 --- a/src/components/Sing/SingEditor.vue +++ b/src/components/Sing/SingEditor.vue @@ -89,16 +89,16 @@ onetimeWatch( notes: [], }, }); - - await store.dispatch("SET_VOLUME", { volume: 0.6 }); - await store.dispatch("SET_PLAYHEAD_POSITION", { position: 0 }); - await store.dispatch("SET_LEFT_LOCATOR_POSITION", { - position: 0, - }); - await store.dispatch("SET_RIGHT_LOCATOR_POSITION", { - position: 480 * 4 * 16, - }); } + + await store.dispatch("SET_VOLUME", { volume: 0.6 }); + await store.dispatch("SET_PLAYHEAD_POSITION", { position: 0 }); + await store.dispatch("SET_LEFT_LOCATOR_POSITION", { + position: 0, + }); + await store.dispatch("SET_RIGHT_LOCATOR_POSITION", { + position: 480 * 4 * 16, + }); isCompletedInitialStartup.value = true; await store.dispatch("SET_SINGER", {}); From 5184f03d7741cfd42ca36c80d6ac0e7e19e86c4b Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Fri, 1 Mar 2024 04:00:29 +0900 Subject: [PATCH 02/35] =?UTF-8?q?[Docs]=20README=E3=81=ABworkflow=5Fdispat?= =?UTF-8?q?ch=E3=82=92=E4=BD=BF=E3=81=A3=E3=81=9F=E3=83=93=E3=83=AB?= =?UTF-8?q?=E3=83=89=E6=96=B9=E6=B3=95=E3=82=92=E8=BF=BD=E5=8A=A0=20(#1877?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Docs] READMEにworkflow_dispatchを使ったビルド方法を追加) --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 69ee5dd6d6..0bc0e22a51 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,11 @@ npm run browser:serve npm run electron:build ``` +### Github Actions でビルド + +fork したリポジトリで Actions を ON にし、workflow_dispatch で`build.yml`を起動すればビルドできます。 +成果物は Release にアップロードされます。 + ## テスト ### 単体テスト From cc2bceb1b398b6417f2f861cdc715651e30655dd Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Fri, 1 Mar 2024 04:49:41 +0900 Subject: [PATCH 03/35] =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=82=AB=E3=83=83=E3=83=88=E3=82=AD=E3=83=BC=E3=81=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6Command=E3=82=AD=E3=83=BC=E3=82=92=E4=BD=BF=E3=81=88?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B=20(#1865?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * HotkeyComboブランド型を導入 * 忘れ物 * cmdを使えるようにする * resolve conflict * Update src/plugins/hotkeyPlugin.ts * コメントを変更 --------- Co-authored-by: y-chan --- src/plugins/hotkeyPlugin.ts | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/plugins/hotkeyPlugin.ts b/src/plugins/hotkeyPlugin.ts index ddb5948f74..2efb55295c 100644 --- a/src/plugins/hotkeyPlugin.ts +++ b/src/plugins/hotkeyPlugin.ts @@ -34,6 +34,8 @@ export const useHotkeyManager = () => { type Editor = "talk" | "song"; +type BindingKey = string & { __brand: "BindingKey" }; // BindingKey専用のブランド型 + /** * ショートカットキーの処理を登録するための型。 */ @@ -50,16 +52,17 @@ export type HotkeyAction = { export type HotkeysJs = { ( - key: string, + key: BindingKey, options: { scope: string; }, callback: (e: KeyboardEvent) => void ): void; - unbind: (key: string, scope: string) => void; + unbind: (key: BindingKey, scope: string) => void; setScope: (scope: string) => void; }; +// デフォルトはテキストボックス内でショートカットキー無効なので有効にする hotkeys.filter = () => { return true; }; @@ -269,8 +272,19 @@ export class HotkeyManager { } } -const combinationToBindingKey = (combination: HotkeyCombination) => { - return combination.toLowerCase().replaceAll(" ", "+"); +/** hotkeys-js用のキーに変換する */ +const combinationToBindingKey = ( + combination: HotkeyCombination +): BindingKey => { + // MetaキーはCommandキーとして扱う + // NOTE: hotkeys-jsにはWinキーが無く、Commandキーとして扱われている + // NOTE: Metaキーは以前採用していたmousetrapがそうだった名残り + const bindingKey = combination + .toLowerCase() + .split(" ") + .map((key) => (key === "meta" ? "command" : key)) + .join("+"); + return bindingKey as BindingKey; }; export const hotkeyPlugin: Plugin = { From cd9bdc87e26c636d077939367e597f576eb385af Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Fri, 1 Mar 2024 05:26:20 +0900 Subject: [PATCH 04/35] =?UTF-8?q?[refactor]=20VueRouter=E3=81=AE=E4=BE=9D?= =?UTF-8?q?=E5=AD=98=E3=82=92=E5=A4=B1=E3=81=8F=E3=81=99=20(#1875)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * VueRouterの依存を失くす * /talkなどを削除 --- README.md | 4 +- ...56\346\255\251\343\201\215\346\226\271.md" | 1 - package-lock.json | 3 +- package.json | 1 - src/backend/electron/main.ts | 1 - src/components/App.vue | 64 +++++++++++-------- .../Menu/MenuBar/TitleBarEditorSwitcher.vue | 19 ++---- src/main.ts | 3 - src/router/index.ts | 21 ------ src/store/type.ts | 6 ++ src/store/ui.ts | 10 +++ tests/e2e/navigators.ts | 4 +- 12 files changed, 64 insertions(+), 73 deletions(-) delete mode 100644 src/router/index.ts diff --git a/README.md b/README.md index 0bc0e22a51..8b86c47496 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,7 @@ npm run electron:serve npm run browser:serve ``` -また、main ブランチのビルド結果がこちらにデプロイされています +また、main ブランチのビルド結果がこちらにデプロイされています 今はローカル PC 上で音声合成エンジンを起動する必要があります。 ## ビルド @@ -114,7 +114,7 @@ Playwright を使用しているためテストパターンを生成すること **ブラウザ版を起動している状態で**以下のコマンドを実行してください。 ```bash -npx playwright codegen http://localhost:5173/#/talk --viewport-size=800,600 +npx playwright codegen http://localhost:5173/ --viewport-size=800,600 ``` 詳細は [Playwright ドキュメントの Test generator](https://playwright.dev/docs/codegen-intro) を参照してください。 diff --git "a/docs/\343\202\263\343\203\274\343\203\211\343\201\256\346\255\251\343\201\215\346\226\271.md" "b/docs/\343\202\263\343\203\274\343\203\211\343\201\256\346\255\251\343\201\215\346\226\271.md" index a2400272f4..6b1bdf7d2d 100644 --- "a/docs/\343\202\263\343\203\274\343\203\211\343\201\256\346\255\251\343\201\215\346\226\271.md" +++ "b/docs/\343\202\263\343\203\274\343\203\211\343\201\256\346\255\251\343\201\215\346\226\271.md" @@ -88,7 +88,6 @@ TODO - styles ディレクトリ ・・・ CSS や SCSS などのディレクトリ。 - infrastructures ディレクトリ ・・・ UI 用のコードと UI 以外のコードを跨ぐときに一枚かませたいときのためのコードのディレクトリ。 - openapi ディレクトリ ・・・ エンジンの API を叩くためのコードのディレクトリ。OpenAPI で自動生成される。 - - router ディレクトリ ・・・ Vue Router 用のディレクトリ。 - helpers ディレクトリ ・・・ 便利な関数を置くディレクトリ。 - shared ディレクトリ ・・・ UI と Electron 両方から参照されるコードを置くディレクトリ。 - public diff --git a/package-lock.json b/package-lock.json index 8193f6592b..63ad065289 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,6 @@ "tree-kill": "1.2.2", "uuid": "9.0.0", "vue": "3.2.45", - "vue-router": "4.0.8", "vuedraggable": "4.1.0", "vuex": "4.0.2", "zod": "3.22.4" @@ -13132,6 +13131,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.8.tgz", "integrity": "sha512-42mWSQaH7CCBQDspQTHv63f34VEnZC20g9QNK4WJ/zW8SdIUeT6TQ2i/78fjF/pVBUPLBWrGhvB7uDnaz7O/pA==", + "optional": true, "dependencies": { "@vue/devtools-api": "^6.0.0-beta.10" }, @@ -23603,6 +23603,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.0.8.tgz", "integrity": "sha512-42mWSQaH7CCBQDspQTHv63f34VEnZC20g9QNK4WJ/zW8SdIUeT6TQ2i/78fjF/pVBUPLBWrGhvB7uDnaz7O/pA==", + "optional": true, "requires": { "@vue/devtools-api": "^6.0.0-beta.10" } diff --git a/package.json b/package.json index 8d03618672..89b93a1014 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,6 @@ "tree-kill": "1.2.2", "uuid": "9.0.0", "vue": "3.2.45", - "vue-router": "4.0.8", "vuedraggable": "4.1.0", "vuex": "4.0.2", "zod": "3.22.4" diff --git a/src/backend/electron/main.ts b/src/backend/electron/main.ts index 0f2c29717f..11d649cf5f 100644 --- a/src/backend/electron/main.ts +++ b/src/backend/electron/main.ts @@ -470,7 +470,6 @@ async function loadUrl(obj: { projectFilePath?: string; }) { const fragment = - "#/talk" + `?isMultiEngineOffMode=${obj?.isMultiEngineOffMode ?? false}` + `&projectFilePath=${obj?.projectFilePath ?? ""}`; return win.loadURL(`${firstUrl}${fragment}`); diff --git a/src/components/App.vue b/src/components/App.vue index 2d404164de..567e81ad8c 100644 --- a/src/components/App.vue +++ b/src/components/App.vue @@ -1,15 +1,15 @@ @@ -17,7 +17,8 @@ diff --git a/src/main.ts b/src/main.ts index 68b2870bce..4ccf1ac41f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,6 @@ import { createApp } from "vue"; import { createGtm } from "@gtm-support/vue-gtm"; import { Quasar, Dialog, Loading, Notify } from "quasar"; import iconSet from "quasar/icon-set/material-icons"; -import router from "./router"; import { store, storeKey } from "./store"; import { ipcMessageReceiver } from "./plugins/ipcMessageReceiverPlugin"; import { hotkeyPlugin } from "./plugins/hotkeyPlugin"; @@ -19,11 +18,9 @@ window.dataLayer = []; createApp(App) .use(store, storeKey) - .use(router) .use( createGtm({ id: import.meta.env.VITE_GTM_CONTAINER_ID ?? "GTM-DUMMY", - vueRouter: router, // NOTE: 最初はgtm.jsを読まず、プライバシーポリシーに同意後に読み込む enabled: false, }) diff --git a/src/router/index.ts b/src/router/index.ts deleted file mode 100644 index f0219be066..0000000000 --- a/src/router/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router"; -import SingEditor from "@/components/Sing/SingEditor.vue"; -import TalkEditor from "@/components/Talk/TalkEditor.vue"; - -const routes: Array = [ - { - path: "/talk", - component: TalkEditor, - }, - { - path: "/song", - component: SingEditor, - }, -]; - -const router = createRouter({ - history: createWebHashHistory(import.meta.env.BASE_URL), - routes, -}); - -export default router; diff --git a/src/store/type.ts b/src/store/type.ts index c283b26936..0f2df9fb0b 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -1506,6 +1506,7 @@ export type SettingStoreTypes = { */ export type UiStoreState = { + openedEditor: EditorType | undefined; // undefinedのときはどのエディタを開くか定まっていない uiLockCount: number; dialogLockCount: number; reloadingLock: boolean; @@ -1530,6 +1531,11 @@ export type UiStoreState = { }; export type UiStoreTypes = { + SET_OPENED_EDITOR: { + mutation: { editor: EditorType }; + action(palyoad: { editor: EditorType }): void; + }; + UI_LOCKED: { getter: boolean; }; diff --git a/src/store/ui.ts b/src/store/ui.ts index 9215fdb87c..17df3867bd 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -45,6 +45,7 @@ export function withProgress( } export const uiStoreState: UiStoreState = { + openedEditor: undefined, uiLockCount: 0, dialogLockCount: 0, reloadingLock: false, @@ -69,6 +70,15 @@ export const uiStoreState: UiStoreState = { }; export const uiStore = createPartialStore({ + SET_OPENED_EDITOR: { + mutation(state, { editor }) { + state.openedEditor = editor; + }, + action({ commit }, { editor }) { + commit("SET_OPENED_EDITOR", { editor }); + }, + }, + UI_LOCKED: { getter(state) { return state.uiLockCount > 0; diff --git a/tests/e2e/navigators.ts b/tests/e2e/navigators.ts index 6fe1c9aeef..09cf0385da 100644 --- a/tests/e2e/navigators.ts +++ b/tests/e2e/navigators.ts @@ -2,10 +2,10 @@ import { expect, Page } from "@playwright/test"; import { getNewestQuasarDialog, getQuasarMenu } from "./locators"; /** - * /#/talkに移動 + * 最初の画面に移動 */ export async function gotoHome({ page }: { page: Page }) { - const BASE_URL = "http://localhost:7357/#/talk"; + const BASE_URL = "http://localhost:7357/"; await page.setViewportSize({ width: 800, height: 600 }); await page.goto(BASE_URL); } From 9aa10cb37f2d429a404454d7a7e05092a28faa2f Mon Sep 17 00:00:00 2001 From: tsym77yoshi <67378554+tsym77yoshi@users.noreply.github.com> Date: Sat, 2 Mar 2024 21:06:38 +0900 Subject: [PATCH 05/35] =?UTF-8?q?Add:=20=E7=84=A1=E5=A3=B0=E5=8C=96?= =?UTF-8?q?=E3=81=97=E3=81=9F=E3=82=82=E3=81=AE=E3=81=8Cdisabled=E3=81=AB?= =?UTF-8?q?=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=84=E3=82=8B=E7=90=86=E7=94=B1?= =?UTF-8?q?=E3=81=AE=E8=A1=A8=E7=A4=BA=20(#1885)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add: 無声化したものがdisabledになっている理由の表示 イントネーション欄で「無声化した音」のイントネーションが変更出来ないようになっている理由を表示する * Apply suggestions from code review --------- Co-authored-by: Hiroshiba --- src/components/Talk/AudioParameter.vue | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/components/Talk/AudioParameter.vue b/src/components/Talk/AudioParameter.vue index d16d75ccd4..ec2c94d26b 100644 --- a/src/components/Talk/AudioParameter.vue +++ b/src/components/Talk/AudioParameter.vue @@ -17,6 +17,16 @@ : undefined }} + + 無声化した音にイントネーションは存在しません。
テキストをクリックすることで無声化を解けます。
Date: Sun, 3 Mar 2024 20:24:43 +0900 Subject: [PATCH 06/35] =?UTF-8?q?Fix:=20`startTime=20=3D=3D=3D=200`?= =?UTF-8?q?=E3=81=AE=E3=83=95=E3=83=AC=E3=83=BC=E3=82=BA=E3=81=A7=E3=83=94?= =?UTF-8?q?=E3=83=83=E3=83=81=E3=81=8C=E3=83=AC=E3=83=B3=E3=83=80=E3=83=AA?= =?UTF-8?q?=E3=83=B3=E3=82=B0=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E3=81=AE?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=20(#1894)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit startTimeが0のフレーズでピッチがレンダリングされないのを修正 --- src/components/Sing/SequencerPitch.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/Sing/SequencerPitch.vue b/src/components/Sing/SequencerPitch.vue index 11fac54642..a28c72bc44 100644 --- a/src/components/Sing/SequencerPitch.vue +++ b/src/components/Sing/SequencerPitch.vue @@ -73,10 +73,10 @@ const searchVoicedSections = (phonemes: FramePhoneme[]) => { }; const render = () => { - if (!canvasWidth) { + if (canvasWidth == undefined) { throw new Error("canvasWidth is undefined."); } - if (!canvasHeight) { + if (canvasHeight == undefined) { throw new Error("canvasHeight is undefined."); } if (!renderer) { @@ -104,7 +104,7 @@ const render = () => { } // ピッチラインの生成・更新を行う for (const [phraseKey, phrase] of phrases) { - if (!phrase.singer || !phrase.query || !phrase.startTime) { + if (!phrase.singer || !phrase.query || phrase.startTime == undefined) { continue; } const tempos = [toRaw(phrase.tempos[0])]; From b86cad8a7746169e81d913655a23a9fe542db08d Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Wed, 6 Mar 2024 23:54:25 +0900 Subject: [PATCH 07/35] =?UTF-8?q?=E9=9F=B3=E5=9F=9F=E8=A3=9C=E6=AD=A3?= =?UTF-8?q?=E7=94=A8=E3=81=AE=E3=83=91=E3=83=A9=E3=83=A1=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=83=BC=E3=82=92=E5=A2=97=E3=82=84=E3=81=97=E3=81=A4=E3=81=A4?= =?UTF-8?q?=E3=80=81=E9=96=8B=E7=99=BA=E6=99=82=E3=81=AE=E3=81=BF=E3=81=AE?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=81=AB=20(#1902)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 音高補正にしつつ開発時のみ機能に * VoiceKey→GuidePitch * とりあえず実装としては完成 * keyRangeAdjustmentに * (note|guide)KeyShiftを消し、補正→調整にする * keyShiftを全てKeyRangeAdjustmentへ --- src/components/Sing/ToolBar.vue | 53 +++++++++++++++----------- src/sing/domain.ts | 8 ++-- src/sing/storeHelper.ts | 3 +- src/store/project.ts | 7 ++-- src/store/singing.ts | 66 ++++++++++++++++----------------- src/store/type.ts | 18 ++++----- 6 files changed, 79 insertions(+), 76 deletions(-) diff --git a/src/components/Sing/ToolBar.vue b/src/components/Sing/ToolBar.vue index fc3707f07a..d5cf21512a 100644 --- a/src/components/Sing/ToolBar.vue +++ b/src/components/Sing/ToolBar.vue @@ -3,16 +3,19 @@
- + + import { computed, watch, ref, onMounted, onUnmounted } from "vue"; import { useStore } from "@/store"; +import { isProduction } from "@/type/preload"; + import { getSnapTypes, isTriplet, isValidBeatType, isValidBeats, isValidBpm, - isValidVoiceKeyShift, + isValidKeyRangeAdjustment, } from "@/sing/domain"; import CharacterMenuButton from "@/components/Sing/CharacterMenuButton/MenuButton.vue"; import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; @@ -183,12 +188,14 @@ const redo = () => { const tempos = computed(() => store.state.tempos); const timeSignatures = computed(() => store.state.timeSignatures); -const keyShift = computed(() => store.getters.SELECTED_TRACK.voiceKeyShift); +const keyRangeAdjustment = computed( + () => store.getters.SELECTED_TRACK.keyRangeAdjustment +); const bpmInputBuffer = ref(120); const beatsInputBuffer = ref(4); const beatTypeInputBuffer = ref(4); -const keyShiftInputBuffer = ref(0); +const keyRangeAdjustmentInputBuffer = ref(0); watch( tempos, @@ -207,8 +214,8 @@ watch( { deep: true } ); -watch(keyShift, () => { - keyShiftInputBuffer.value = keyShift.value; +watch(keyRangeAdjustment, () => { + keyRangeAdjustmentInputBuffer.value = keyRangeAdjustment.value; }); const setBpmInputBuffer = (bpmStr: string | number | null) => { @@ -235,12 +242,14 @@ const setBeatTypeInputBuffer = (beatTypeStr: string | number | null) => { beatTypeInputBuffer.value = beatTypeValue; }; -const setKeyShiftInputBuffer = (keyShiftStr: string | number | null) => { - const keyShiftValue = Number(keyShiftStr); - if (!isValidVoiceKeyShift(keyShiftValue)) { +const setKeyRangeAdjustmentInputBuffer = ( + KeyRangeAdjustmentStr: string | number | null +) => { + const KeyRangeAdjustmentValue = Number(KeyRangeAdjustmentStr); + if (!isValidKeyRangeAdjustment(KeyRangeAdjustmentValue)) { return; } - keyShiftInputBuffer.value = keyShiftValue; + keyRangeAdjustmentInputBuffer.value = KeyRangeAdjustmentValue; }; const setTempo = () => { @@ -265,9 +274,9 @@ const setTimeSignature = () => { }); }; -const setKeyShift = () => { - const voiceKeyShift = keyShiftInputBuffer.value; - store.dispatch("COMMAND_SET_VOICE_KEY_SHIFT", { voiceKeyShift }); +const setKeyRangeAdjustment = () => { + const keyRangeAdjustment = keyRangeAdjustmentInputBuffer.value; + store.dispatch("COMMAND_SET_KEY_RANGE_ADJUSTMENT", { keyRangeAdjustment }); }; const playheadTicks = ref(0); diff --git a/src/sing/domain.ts b/src/sing/domain.ts index 63952b58d7..2f65927870 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -279,10 +279,10 @@ export function isValidSnapType(snapType: number, tpqn: number) { return getSnapTypes(tpqn).some((value) => value === snapType); } -export function isValidVoiceKeyShift(voiceKeyShift: number) { +export function isValidKeyRangeAdjustment(keyRangeAdjustment: number) { return ( - Number.isInteger(voiceKeyShift) && - voiceKeyShift <= 24 && - voiceKeyShift >= -24 + Number.isInteger(keyRangeAdjustment) && + keyRangeAdjustment <= 24 && + keyRangeAdjustment >= -24 ); } diff --git a/src/sing/storeHelper.ts b/src/sing/storeHelper.ts index 1b8345ef95..91548d1ebe 100644 --- a/src/sing/storeHelper.ts +++ b/src/sing/storeHelper.ts @@ -8,8 +8,7 @@ export const DEFAULT_BEAT_TYPE = 4; export const generatePhraseHash = async (obj: { singer: Singer | undefined; - notesKeyShift: number; - voiceKeyShift: number; + keyRangeAdjustment: number; tpqn: number; tempos: Tempo[]; notes: Note[]; diff --git a/src/store/project.ts b/src/store/project.ts index 2064844eef..7ab4c0b6a4 100755 --- a/src/store/project.ts +++ b/src/store/project.ts @@ -111,8 +111,8 @@ const applySongProjectToStore = async ( await dispatch("SET_SINGER", { singer: tracks[0].singer, }); - await dispatch("SET_VOICE_KEY_SHIFT", { - voiceKeyShift: tracks[0].voiceKeyShift, + await dispatch("SET_KEY_RANGE_ADJUSTMENT", { + keyRangeAdjustment: tracks[0].keyRangeAdjustment, }); await dispatch("SET_SCORE", { score: { @@ -431,8 +431,7 @@ export const projectStore = createPartialStore({ tracks: [ { singer: undefined, - notesKeyShift: 0, - voiceKeyShift: 0, + keyRangeAdjustment: 0, notes: [], }, ], diff --git a/src/store/singing.ts b/src/store/singing.ts index 4ba8f477cf..f6fbcd2b6b 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -44,7 +44,7 @@ import { isValidSnapType, isValidTempo, isValidTimeSignature, - isValidVoiceKeyShift, + isValidKeyRangeAdjustment, secondToTick, tickToSecond, } from "@/sing/domain"; @@ -144,8 +144,7 @@ export const generateSingingStoreInitialScore = () => { tracks: [ { singer: undefined, - notesKeyShift: 0, - voiceKeyShift: 0, + keyRangeAdjustment: 0, notes: [], }, ], @@ -230,18 +229,18 @@ export const singingStore = createPartialStore({ }, }, - SET_VOICE_KEY_SHIFT: { - mutation(state, { voiceKeyShift }: { voiceKeyShift: number }) { - state.tracks[selectedTrackIndex].voiceKeyShift = voiceKeyShift; + SET_KEY_RANGE_ADJUSTMENT: { + mutation(state, { keyRangeAdjustment }: { keyRangeAdjustment: number }) { + state.tracks[selectedTrackIndex].keyRangeAdjustment = keyRangeAdjustment; }, async action( { dispatch, commit }, - { voiceKeyShift }: { voiceKeyShift: number } + { keyRangeAdjustment }: { keyRangeAdjustment: number } ) { - if (!isValidVoiceKeyShift(voiceKeyShift)) { - throw new Error("The voiceKeyShift is invalid."); + if (!isValidKeyRangeAdjustment(keyRangeAdjustment)) { + throw new Error("The keyRangeAdjustment is invalid."); } - commit("SET_VOICE_KEY_SHIFT", { voiceKeyShift }); + commit("SET_KEY_RANGE_ADJUSTMENT", { keyRangeAdjustment }); dispatch("RENDER"); }, @@ -746,8 +745,7 @@ export const singingStore = createPartialStore({ async action({ state, getters, commit, dispatch }) { const searchPhrases = async ( singer: Singer | undefined, - notesKeyShift: number, - voiceKeyShift: number, + keyRangeAdjustment: number, tpqn: number, tempos: Tempo[], notes: Note[] @@ -767,16 +765,14 @@ export const singingStore = createPartialStore({ const phraseLastNote = phraseNotes[phraseNotes.length - 1]; const hash = await generatePhraseHash({ singer, - notesKeyShift, - voiceKeyShift, + keyRangeAdjustment, tpqn, tempos, notes: phraseNotes, }); foundPhrases.set(hash, { singer, - notesKeyShift, - voiceKeyShift, + keyRangeAdjustment, tpqn, tempos, notes: phraseNotes, @@ -802,7 +798,7 @@ export const singingStore = createPartialStore({ notes: Note[], tempos: Tempo[], tpqn: number, - notesKeyShift: number, + keyRangeAdjustment: number, frameRate: number, restDurationSeconds: number ) => { @@ -834,8 +830,10 @@ export const singingStore = createPartialStore({ .replace("うぉ", "ウォ") .replace("は", "ハ") .replace("へ", "ヘ"); + // トランスポーズする + const key = note.noteNumber - keyRangeAdjustment; notesForRequestToEngine.push({ - key: note.noteNumber + notesKeyShift, + key, frameLength: noteFrameLength, lyric, }); @@ -877,12 +875,12 @@ export const singingStore = createPartialStore({ return frameAudioQuery.phonemes.map((value) => value.phoneme).join(" "); }; - const shiftVoiceKey = ( - voiceKeyShift: number, + const shiftGuidePitch = ( + pitchShift: number, frameAudioQuery: FrameAudioQuery ) => { frameAudioQuery.f0 = frameAudioQuery.f0.map((value) => { - return value * Math.pow(2, voiceKeyShift / 12); + return value * Math.pow(2, pitchShift / 12); }); }; @@ -942,8 +940,7 @@ export const singingStore = createPartialStore({ const tempos = state.tempos.map((value) => ({ ...value })); const track = getters.SELECTED_TRACK; const singer = track.singer ? { ...track.singer } : undefined; - const notesKeyShift = track.notesKeyShift; - const voiceKeyShift = track.voiceKeyShift; + const keyRangeAdjustment = track.keyRangeAdjustment; const notes = track.notes .map((value) => ({ ...value })) .filter((value) => !state.overlappingNoteIds.has(value.id)); @@ -951,8 +948,7 @@ export const singingStore = createPartialStore({ // フレーズを更新する const foundPhrases = await searchPhrases( singer, - notesKeyShift, - voiceKeyShift, + keyRangeAdjustment, tpqn, tempos, notes @@ -1036,7 +1032,7 @@ export const singingStore = createPartialStore({ phrase.notes, phrase.tempos, phrase.tpqn, - phrase.notesKeyShift, + phrase.keyRangeAdjustment, frameRate, restDurationSeconds ).catch((error) => { @@ -1052,7 +1048,7 @@ export const singingStore = createPartialStore({ `Fetched frame audio query. Phonemes are "${phonemes}".` ); - shiftVoiceKey(phrase.voiceKeyShift, frameAudioQuery); + shiftGuidePitch(phrase.keyRangeAdjustment, frameAudioQuery); const startTime = calcStartTime( phrase.notes, @@ -1977,18 +1973,20 @@ export const singingCommandStore = transformCommandStore( dispatch("RENDER"); }, }, - COMMAND_SET_VOICE_KEY_SHIFT: { - mutation(draft, { voiceKeyShift }) { - singingStore.mutations.SET_VOICE_KEY_SHIFT(draft, { voiceKeyShift }); + COMMAND_SET_KEY_RANGE_ADJUSTMENT: { + mutation(draft, { keyRangeAdjustment }) { + singingStore.mutations.SET_KEY_RANGE_ADJUSTMENT(draft, { + keyRangeAdjustment, + }); }, async action( { dispatch, commit }, - { voiceKeyShift }: { voiceKeyShift: number } + { keyRangeAdjustment }: { keyRangeAdjustment: number } ) { - if (!isValidVoiceKeyShift(voiceKeyShift)) { - throw new Error("The voiceKeyShift is invalid."); + if (!isValidKeyRangeAdjustment(keyRangeAdjustment)) { + throw new Error("The keyRangeAdjustment is invalid."); } - commit("COMMAND_SET_VOICE_KEY_SHIFT", { voiceKeyShift }); + commit("COMMAND_SET_KEY_RANGE_ADJUSTMENT", { keyRangeAdjustment }); dispatch("RENDER"); }, diff --git a/src/store/type.ts b/src/store/type.ts index 0f2df9fb0b..efeb26839a 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -751,8 +751,7 @@ export type Singer = z.infer; export const trackSchema = z.object({ singer: singerSchema.optional(), - notesKeyShift: z.number(), - voiceKeyShift: z.number(), + keyRangeAdjustment: z.number(), // 音域調整量 notes: z.array(noteSchema), }); export type Track = z.infer; @@ -765,8 +764,7 @@ export type PhraseState = export type Phrase = { singer?: Singer; - notesKeyShift: number; - voiceKeyShift: number; + keyRangeAdjustment: number; tpqn: number; tempos: Tempo[]; notes: Note[]; @@ -818,9 +816,9 @@ export type SingingStoreTypes = { action(payload: { singer?: Singer }): void; }; - SET_VOICE_KEY_SHIFT: { - mutation: { voiceKeyShift: number }; - action(payload: { voiceKeyShift: number }): void; + SET_KEY_RANGE_ADJUSTMENT: { + mutation: { keyRangeAdjustment: number }; + action(payload: { keyRangeAdjustment: number }): void; }; SET_SCORE: { @@ -1033,9 +1031,9 @@ export type SingingCommandStoreTypes = { action(payload: { singer: Singer }): void; }; - COMMAND_SET_VOICE_KEY_SHIFT: { - mutation: { voiceKeyShift: number }; - action(payload: { voiceKeyShift: number }): void; + COMMAND_SET_KEY_RANGE_ADJUSTMENT: { + mutation: { keyRangeAdjustment: number }; + action(payload: { keyRangeAdjustment: number }): void; }; COMMAND_SET_TEMPO: { From 36fb7f2ac7d76a9d25d86b8d858d0d2054f9850a Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Thu, 7 Mar 2024 01:06:53 +0900 Subject: [PATCH 08/35] =?UTF-8?q?[refactor]=20SET=5FSINGER=E5=86=85?= =?UTF-8?q?=E3=81=AEawait=20SETUP=5FSINGER=E3=82=92=E5=BE=85=E6=A9=9F?= =?UTF-8?q?=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E3=81=99?= =?UTF-8?q?=E3=82=8B=20(#1891)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * SET_SINGER内でSETUP_SINGERしてるのを解体する * SETするたびにSETUP_SINGERするように * いらない * GET_DEFAULT_SINGERいらない気がしたので * void * voidもいらないかも --- src/store/singing.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/store/singing.ts b/src/store/singing.ts index f6fbcd2b6b..ce4ec702d5 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -222,7 +222,7 @@ export const singingStore = createPartialStore({ const styleId = singer?.styleId ?? defaultStyleId; - await dispatch("SETUP_SINGER", { singer: { engineId, styleId } }); + dispatch("SETUP_SINGER", { singer: { engineId, styleId } }); commit("SET_SINGER", { singer: { engineId, styleId } }); dispatch("RENDER"); @@ -1967,7 +1967,7 @@ export const singingCommandStore = transformCommandStore( singingStore.mutations.SET_SINGER(draft, { singer }); }, async action({ dispatch, commit }, { singer }) { - await dispatch("SETUP_SINGER", { singer }); + dispatch("SETUP_SINGER", { singer }); commit("COMMAND_SET_SINGER", { singer }); dispatch("RENDER"); From be6e005c6dab30a7f448ba175289c9178087197c Mon Sep 17 00:00:00 2001 From: nao Date: Thu, 7 Mar 2024 01:10:29 +0900 Subject: [PATCH 09/35] =?UTF-8?q?=E6=83=85=E5=A0=B1=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=81=AB=E9=96=A2=E3=81=99=E3=82=8B=E3=82=B5?= =?UTF-8?q?=E3=83=BC=E3=83=89=E3=83=91=E3=83=BC=E3=83=86=E3=82=A3=E9=96=8B?= =?UTF-8?q?=E7=99=BA=E8=80=85=E5=90=91=E3=81=91=E3=83=89=E3=82=AD=E3=83=A5?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=20(#1858)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 未対応エンジン追加時にリストが消える件(#1168) ・追加されたエンジンが未対応である場合には追加を阻止 ・追加されてしまっている場合には、エラーで処理中断しないように * lintチェックエラー部分の修正 * コードレビューの反映 (ref #1179) ・MinimumEngineManifestの更新 * コードレビュー分の反映② ref #1179 ・engineManifests[selectedId]自体が undefined であるケースに対応 * ref #1738 の会話にあった「情報ファイルに関するドキュメント」(新規執筆) * markdown lint でエラーが出てた件の修正 * Update docs/サードパーティ開発者の方へ.md Co-authored-by: Hiroshiba * フォーマット表記提案サジェストの適用 Co-authored-by: Nanashi. * * 表の表記をJSONP内コメント追記に * VOICEVOXの仕組みを追記 * 環境変数の修正 Co-authored-by: Hiroshiba * コメントいただいた部分を中心に追記 * Update docs/サードパーティ開発者の方へ.md --------- Co-authored-by: Hiroshiba Co-authored-by: Nanashi. --- ...05\343\201\256\346\226\271\343\201\270.md" | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 "docs/\343\202\265\343\203\274\343\203\211\343\203\221\343\203\274\343\203\206\343\202\243\351\226\213\347\231\272\350\200\205\343\201\256\346\226\271\343\201\270.md" diff --git "a/docs/\343\202\265\343\203\274\343\203\211\343\203\221\343\203\274\343\203\206\343\202\243\351\226\213\347\231\272\350\200\205\343\201\256\346\226\271\343\201\270.md" "b/docs/\343\202\265\343\203\274\343\203\211\343\203\221\343\203\274\343\203\206\343\202\243\351\226\213\347\231\272\350\200\205\343\201\256\346\226\271\343\201\270.md" new file mode 100644 index 0000000000..3be715633e --- /dev/null +++ "b/docs/\343\202\265\343\203\274\343\203\211\343\203\221\343\203\274\343\203\206\343\202\243\351\226\213\347\231\272\350\200\205\343\201\256\346\226\271\343\201\270.md" @@ -0,0 +1,82 @@ +# サードパーティ開発者の方へ + +## サードパーティが利用するときの注意 + +### VOICEVOXの基本動作について + +VOICEVOXアプリケーションは、大まかに分けるとユーザに見えるGUI(フロントエンド部分)と、音声合成を担当するエンジン(バックエンド部分)で構成されています。通常、VOICEVOXのGUIが起動されると、見えない形でエンジンが(マルチエンジンとして登録されている数だけ)順番に立ち上がる仕組みになっています。サードパーティが音声合成機能を使うためには、裏で立ち上がっているエンジンにアクセスする必要があります。 + +### VOICEVOXへのアクセスについて + +VOICEVOXのエンジンを使用するためには、APIを用いる必要があります。特別な理由がない限り、API以外でのアクセスを避けるようにしてください。(例えば、ファイルを直接書き換えるなどの動作は不具合を引き起こす可能性が高くなるほか、VOICEVOXが事前テストで動作確認できてないような望まない動作を引き起こす可能性があります。) + +基本的にVOICEVOXで音声を合成するために必要なAPIはすでに用意されていますが、もしAPIで実現できない機能/操作がある場合は、VOIEVOX の Issue ページにてAPIの実装提案を行う事をお勧めします。 + +### APIのアクセス先について + +HTTP経由でAPIにアクセスできます。 + +* VOICEVOXエンジンがデフォルトで使用するポート番号は、内部で決まっています(例:50021/tcp)。 +* 他のアプリケーションが同じポート番号を使用している場合、競合が発生するため別のポートを開きます。 +* 最終的に開いたポート番号については、情報ファイルを参照して特定してください。 +* 動的に番号が変更されるのは、VOICEVOX v0.16以降の機能です。 + +### ランタイム情報ファイル(以下情報ファイル)について + +サードパーティが必要な情報を手に入れるために、「情報ファイル」というものが生成されるようになっています。サードパーティの製作者は、まずはこのファイルを参照してアクセス先を決定してください。 + +また、VOICEVOX内でエンジンが再起動した場合に、通信ポート番号が変わる可能性があります。サードパーティ側でAPI使用中に突如通信失敗が発生した場合は、このファイルが更新されているか確認するようにしてください。 + +(なお、古いVOICEVOXではこのファイル生成機能が実装されていません。また、動的にポート番号を決定しないバージョンもあります。下位互換性を保つためには、このファイルがない場合も想定してください) + +## 情報ファイルについて + +### ファイルの場所 + +ファイルは下記の場所にあります。 + +|OS |ファイルパス | +|----------------|-----------------------------------------------------------------------------| +|Windows |C:\Users\(ユーザー名)\AppData\Roaming\voicevox\runtime-info.json | +|MacOS |/Users/(ユーザー名)/Library/Application Support/voicevox/runtime-info.json | + +なお、Windows のプロファイル設定次第では、上記ファイルの配置が変更されることがあります。確実に場所を特定するためには、環境変数 ``APPDATA`` を用いて、Roamingフォルダの位置を特定してください。 + +### ファイルの中身と意味 + +ファイル自体はJSON形式になっています。構造としては下記のような形になります。 + +```JSONC +{ + //[string] VOICEVOXのバージョン番号 + "appVersion": "xxx.yyy.zzz", + + //[number] ファイル構造バージョン(仕様変更毎にインクリメントされる) + "formatVersion": 1, + + //エンジンデータ(起動している数だけ) + "engineInfos": [ + { + //[string] エンジン通称 + "name": "engine1", + + //[string] APIエンドポイント + "url": "http://127.0.0.1:50021", + + //[UUID] エンジン識別用のUUID + "uuid": "00000000-0000-0000-0000-000000000001", + }, + { + "name": "engine2", + "url": "http://127.0.0.1:50121", + "uuid": "00000000-0000-0000-0000-000000000002", + }, + ] +} +``` + +### 生成されるタイミング + +* エンジンが起動(もしくは再起動)するタイミングでファイルが生成、更新されます。 +* 何らかの理由でファイルの書き込み権が取得できなかった場合には更新されません。(更新のタイミングで他ツールがファイルを開いていた場合など) +* この生成タイミングはファイルバージョン1の実装であり、開発の過程で変更される可能性があります。 From 45a288430681a12412d1b8fca4ccf88ff0ba3085 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Thu, 7 Mar 2024 03:45:32 +0900 Subject: [PATCH 10/35] [release-0.17] to 0.17.0 (#1913) to 0.17.0 --- .github/workflows/build.yml | 4 ++-- public/howtouse.md | 5 +++++ public/updateInfos.json | 25 +++++++++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b24d76c323..640446aaa0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,8 +27,8 @@ on: env: VOICEVOX_ENGINE_REPO_URL: "https://github.com/VOICEVOX/voicevox_engine" - VOICEVOX_ENGINE_VERSION: 0.16.0 - VOICEVOX_RESOURCE_VERSION: 0.16.0 + VOICEVOX_ENGINE_VERSION: 0.17.0 + VOICEVOX_RESOURCE_VERSION: 0.17.0 VOICEVOX_EDITOR_VERSION: |- # releaseタグ名か、workflow_dispatchでのバージョン名か、999.999.999-developが入る ${{ github.event.release.tag_name || github.event.inputs.version || '999.999.999-develop' }} diff --git a/public/howtouse.md b/public/howtouse.md index d46fd4d169..dc16368c86 100644 --- a/public/howtouse.md +++ b/public/howtouse.md @@ -286,6 +286,11 @@ VOICEVOX では、歌声合成機能がプロトタイプ版として提供さ ソング機能は鋭意制作中です。フィードバックをお待ちしています。 +### ソング機能のよくある質問 + +Q. 上に赤いバーが出て声が再生されない +A. なにかしらのエラー状態を示しています。現在のバージョンでは、1つのノート(音符)につき日本語1文字分のみ入力できます。またノートが重なっていてもエラーとなります。 + ## オプション 「設定」の「オプション」でいろいろな設定を変更することができます。 diff --git a/public/updateInfos.json b/public/updateInfos.json index 2f27ac43e5..c57416ea57 100644 --- a/public/updateInfos.json +++ b/public/updateInfos.json @@ -1,4 +1,29 @@ [ + { + "version": "0.17.0", + "descriptions": [ + "サードパーティアプリ向けのランタイム情報ファイルを出力", + "ソング:プロジェクトファイルに保存", + "トーク:連続再生中に裏で音声合成", + "ソング:元に戻す・やり直す機能", + "ソング:ショートカットキー機能", + "開発環境の向上", + "バグ修正" + ], + "contributors": [ + "cm-ayf", + "Hiroshiba", + "nmori", + "P0ngCh4ng", + "romot-co", + "sabonerune", + "sevenc-nanashi", + "sigprogramming", + "tsym77yoshi", + "weweweok", + "y-chan" + ] + }, { "version": "0.16.1", "descriptions": ["マルチエンジン稼働時に起動しないバグを修正"], From b9c093ed97ffea3bd1b713bb37d9c8fc78ecada1 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Thu, 7 Mar 2024 03:50:15 +0900 Subject: [PATCH 11/35] =?UTF-8?q?[release-0.17]=20upload=5Fartifact?= =?UTF-8?q?=E3=81=AE=E5=88=A4=E5=AE=9A=E3=83=95=E3=83=A9=E3=82=B0=E3=81=8C?= =?UTF-8?q?=E9=96=93=E9=81=95=E3=81=88=E3=81=A6=E3=81=84=E3=82=8B=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E4=BF=AE=E6=AD=A3=20(#1914)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit upload_artifactの判定フラグが間違えているので修正 --- .github/workflows/build.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 640446aaa0..da6309f70f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -316,7 +316,7 @@ jobs: rm $name.tar - name: Upload Linux tar.gz (without nvidia) to Artifacts - if: startsWith(matrix.artifact_name, 'linux-') && !contains(matrix.artifact_name, 'nvidia') && github.event.inputs.upload_artifact + if: startsWith(matrix.artifact_name, 'linux-') && !contains(matrix.artifact_name, 'nvidia') && github.event.inputs.upload_artifact == 'true' uses: actions/upload-artifact@v3 with: name: ${{ matrix.artifact_name }}-targz @@ -346,7 +346,7 @@ jobs: 7z rn $name.zip prepackage/ VOICEVOX/ - name: Upload Windows & Mac zip (without nvidia) to Artifacts - if: (startsWith(matrix.artifact_name, 'windows-') || startsWith(matrix.artifact_name, 'macos-')) && !contains(matrix.artifact_name, 'nvidia') && github.event.inputs.upload_artifact + if: (startsWith(matrix.artifact_name, 'windows-') || startsWith(matrix.artifact_name, 'macos-')) && !contains(matrix.artifact_name, 'nvidia') && github.event.inputs.upload_artifact == 'true' uses: actions/upload-artifact@v3 with: name: ${{ matrix.artifact_name }}-zip @@ -435,7 +435,7 @@ jobs: done - name: Upload Linux AppImage split to Artifacts - if: endsWith(matrix.installer_artifact_name, '-appimage') && github.event.inputs.upload_artifact + if: endsWith(matrix.installer_artifact_name, '-appimage') && github.event.inputs.upload_artifact == 'true' uses: actions/upload-artifact@v3 with: name: ${{ matrix.installer_artifact_name }}-release @@ -453,7 +453,7 @@ jobs: target_commitish: ${{ github.sha }} - name: Upload macOS dmg to Artifacts - if: endsWith(matrix.installer_artifact_name, '-dmg') && github.event.inputs.upload_artifact + if: endsWith(matrix.installer_artifact_name, '-dmg') && github.event.inputs.upload_artifact == 'true' uses: actions/upload-artifact@v3 with: name: ${{ matrix.installer_artifact_name }}-release @@ -471,7 +471,7 @@ jobs: target_commitish: ${{ github.sha }} - name: Upload Windows NSIS Web to Artifacts - if: endsWith(matrix.installer_artifact_name, '-nsis-web') && github.event.inputs.upload_artifact + if: endsWith(matrix.installer_artifact_name, '-nsis-web') && github.event.inputs.upload_artifact == 'true' uses: actions/upload-artifact@v3 with: name: ${{ matrix.installer_artifact_name }}-release From 673e3bc85ab082fce5597ce97e988222f860f433 Mon Sep 17 00:00:00 2001 From: Sig Date: Fri, 8 Mar 2024 01:50:30 +0900 Subject: [PATCH 12/35] =?UTF-8?q?undo/redo=E6=99=82=E3=81=AB=E3=83=8E?= =?UTF-8?q?=E3=83=BC=E3=83=88=E3=81=AE=E9=81=B8=E6=8A=9E=E3=81=8C=E8=A7=A3?= =?UTF-8?q?=E9=99=A4=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#1916)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * undoredo時にノートの選択が解除されないのを修正 * Apply suggestions from code review --------- Co-authored-by: Hiroshiba --- src/store/command.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/store/command.ts b/src/store/command.ts index 6db4eced35..9d52992955 100644 --- a/src/store/command.ts +++ b/src/store/command.ts @@ -118,6 +118,8 @@ export const commandStore = createPartialStore({ action({ commit, dispatch }, { editor }: { editor: EditorType }) { commit("UNDO", { editor }); if (editor === "song") { + // TODO: 存在しないノートのみ選択解除、あるいはSELECTED_NOTE_IDS getterを作る + commit("DESELECT_ALL_NOTES"); dispatch("RENDER"); } }, @@ -134,6 +136,8 @@ export const commandStore = createPartialStore({ action({ commit, dispatch }, { editor }: { editor: EditorType }) { commit("REDO", { editor }); if (editor === "song") { + // TODO: 存在しないノートのみ選択解除、あるいはSELECTED_NOTE_IDS getterを作る + commit("DESELECT_ALL_NOTES"); dispatch("RENDER"); } }, From 06bed9d8210f5f91bf793cbffcaab9e2f8685b86 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Fri, 8 Mar 2024 02:02:16 +0900 Subject: [PATCH 13/35] =?UTF-8?q?=E9=9F=B3=E9=87=8F=E5=9F=9F=E8=AA=BF?= =?UTF-8?q?=E6=95=B4=E6=A9=9F=E8=83=BD=20(#1901)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ボリューム補正機能 * トランスポーズに戻しておく * ToolBar.vueの変更: クラス名を変更 * ボーカルの音量スケールをガイドの音量スケールに変更 * 名前変更 * Phraseには不要だった * 改行 * やっぱり必要だった * マイグレーション忘れ * コメント追加 --- src/components/Sing/ToolBar.vue | 48 ++++++++++++++++++++++++-- src/sing/domain.ts | 8 +++++ src/sing/storeHelper.ts | 1 + src/store/project.ts | 23 +++++++++---- src/store/singing.ts | 60 +++++++++++++++++++++++++++++++++ src/store/type.ts | 12 +++++++ 6 files changed, 142 insertions(+), 10 deletions(-) diff --git a/src/components/Sing/ToolBar.vue b/src/components/Sing/ToolBar.vue index d5cf21512a..2161b45505 100644 --- a/src/components/Sing/ToolBar.vue +++ b/src/components/Sing/ToolBar.vue @@ -11,10 +11,20 @@ label="音域調整" dense hide-bottom-space - class="key-shift" + class="key-range-adjustment" @update:model-value="setKeyRangeAdjustmentInputBuffer" @change="setKeyRangeAdjustment" /> + store.state.timeSignatures); const keyRangeAdjustment = computed( () => store.getters.SELECTED_TRACK.keyRangeAdjustment ); +const volumeRangeAdjustment = computed( + () => store.getters.SELECTED_TRACK.volumeRangeAdjustment +); const bpmInputBuffer = ref(120); const beatsInputBuffer = ref(4); const beatTypeInputBuffer = ref(4); const keyRangeAdjustmentInputBuffer = ref(0); +const volumeRangeAdjustmentInputBuffer = ref(0); watch( tempos, @@ -218,6 +233,10 @@ watch(keyRangeAdjustment, () => { keyRangeAdjustmentInputBuffer.value = keyRangeAdjustment.value; }); +watch(volumeRangeAdjustment, () => { + volumeRangeAdjustmentInputBuffer.value = volumeRangeAdjustment.value; +}); + const setBpmInputBuffer = (bpmStr: string | number | null) => { const bpmValue = Number(bpmStr); if (!isValidBpm(bpmValue)) { @@ -252,6 +271,16 @@ const setKeyRangeAdjustmentInputBuffer = ( keyRangeAdjustmentInputBuffer.value = KeyRangeAdjustmentValue; }; +const setVolumeRangeAdjustmentInputBuffer = ( + volumeRangeAdjustmentStr: string | number | null +) => { + const volumeRangeAdjustmentValue = Number(volumeRangeAdjustmentStr); + if (!isValidvolumeRangeAdjustment(volumeRangeAdjustmentValue)) { + return; + } + volumeRangeAdjustmentInputBuffer.value = volumeRangeAdjustmentValue; +}; + const setTempo = () => { const bpm = bpmInputBuffer.value; store.dispatch("COMMAND_SET_TEMPO", { @@ -279,6 +308,13 @@ const setKeyRangeAdjustment = () => { store.dispatch("COMMAND_SET_KEY_RANGE_ADJUSTMENT", { keyRangeAdjustment }); }; +const setVolumeRangeAdjustment = () => { + const volumeRangeAdjustment = volumeRangeAdjustmentInputBuffer.value; + store.dispatch("COMMAND_SET_VOLUME_RANGE_ADJUSTMENT", { + volumeRangeAdjustment, + }); +}; + const playheadTicks = ref(0); /// 再生時間の分と秒 @@ -416,10 +452,16 @@ onUnmounted(() => { flex: 1; } -.key-shift { +.key-range-adjustment { margin-left: 16px; margin-right: 4px; - width: 50px; + width: 55px; +} + +.volume-range-adjustment { + margin-left: 4px; + margin-right: 4px; + width: 55px; } .sing-tempo { diff --git a/src/sing/domain.ts b/src/sing/domain.ts index 2f65927870..62b6f715d6 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -286,3 +286,11 @@ export function isValidKeyRangeAdjustment(keyRangeAdjustment: number) { keyRangeAdjustment >= -24 ); } + +export function isValidvolumeRangeAdjustment(volumeRangeAdjustment: number) { + return ( + Number.isInteger(volumeRangeAdjustment) && + volumeRangeAdjustment <= 20 && + volumeRangeAdjustment >= -20 + ); +} diff --git a/src/sing/storeHelper.ts b/src/sing/storeHelper.ts index 91548d1ebe..bb70d5c170 100644 --- a/src/sing/storeHelper.ts +++ b/src/sing/storeHelper.ts @@ -9,6 +9,7 @@ export const DEFAULT_BEAT_TYPE = 4; export const generatePhraseHash = async (obj: { singer: Singer | undefined; keyRangeAdjustment: number; + volumeRangeAdjustment: number; tpqn: number; tempos: Tempo[]; notes: Note[]; diff --git a/src/store/project.ts b/src/store/project.ts index 7ab4c0b6a4..096c151b2a 100755 --- a/src/store/project.ts +++ b/src/store/project.ts @@ -397,13 +397,9 @@ export const projectStore = createPartialStore({ } if ( - semver.satisfies( - projectAppVersion, - "<0.16.2", - semverSatisfiesOptions - ) + semver.satisfies(projectAppVersion, "<0.17", semverSatisfiesOptions) ) { - // 0.16.2 未満のプロジェクトファイルはトークの情報のみ + // 0.17 未満のプロジェクトファイルはトークの情報のみ // なので全情報(audioKeys/audioItems)をtalkに移動する projectData.talk = { audioKeys: projectData.audioKeys, @@ -412,7 +408,7 @@ export const projectStore = createPartialStore({ // ソングの情報を初期化 // generateSingingStoreInitialScoreが今後変わることがあるかもしれないので、 - // 0.16.2時点のスコア情報を直接書く + // 0.17時点のスコア情報を直接書く projectData.song = { tpqn: DEFAULT_TPQN, tempos: [ @@ -441,6 +437,19 @@ export const projectStore = createPartialStore({ delete projectData.audioItems; } + if ( + semver.satisfies( + projectAppVersion, + "<0.17.1", + semverSatisfiesOptions + ) + ) { + // volumeRangeAdjustmentの追加 + for (const track of projectData.song.tracks) { + track.volumeRangeAdjustment = 0; + } + } + // Validation check // トークはvalidateTalkProjectで検証する // ソングはSET_SCOREの中の`isValidScore`関数で検証される diff --git a/src/store/singing.ts b/src/store/singing.ts index ce4ec702d5..720d442e60 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -45,6 +45,7 @@ import { isValidTempo, isValidTimeSignature, isValidKeyRangeAdjustment, + isValidvolumeRangeAdjustment, secondToTick, tickToSecond, } from "@/sing/domain"; @@ -145,6 +146,7 @@ export const generateSingingStoreInitialScore = () => { { singer: undefined, keyRangeAdjustment: 0, + volumeRangeAdjustment: 0, notes: [], }, ], @@ -246,6 +248,29 @@ export const singingStore = createPartialStore({ }, }, + SET_VOLUME_RANGE_ADJUSTMENT: { + mutation( + state, + { volumeRangeAdjustment }: { volumeRangeAdjustment: number } + ) { + state.tracks[selectedTrackIndex].volumeRangeAdjustment = + volumeRangeAdjustment; + }, + async action( + { dispatch, commit }, + { volumeRangeAdjustment }: { volumeRangeAdjustment: number } + ) { + if (!isValidvolumeRangeAdjustment(volumeRangeAdjustment)) { + throw new Error("The volumeRangeAdjustment is invalid."); + } + commit("SET_VOLUME_RANGE_ADJUSTMENT", { + volumeRangeAdjustment, + }); + + dispatch("RENDER"); + }, + }, + SET_SCORE: { mutation(state, { score }: { score: Score }) { state.overlappingNoteInfos.clear(); @@ -746,6 +771,7 @@ export const singingStore = createPartialStore({ const searchPhrases = async ( singer: Singer | undefined, keyRangeAdjustment: number, + volumeRangeAdjustment: number, tpqn: number, tempos: Tempo[], notes: Note[] @@ -766,6 +792,7 @@ export const singingStore = createPartialStore({ const hash = await generatePhraseHash({ singer, keyRangeAdjustment, + volumeRangeAdjustment, tpqn, tempos, notes: phraseNotes, @@ -773,6 +800,7 @@ export const singingStore = createPartialStore({ foundPhrases.set(hash, { singer, keyRangeAdjustment, + volumeRangeAdjustment, tpqn, tempos, notes: phraseNotes, @@ -884,6 +912,15 @@ export const singingStore = createPartialStore({ }); }; + const scaleGuideVolume = ( + volumeRangeAdjustment: number, + frameAudioQuery: FrameAudioQuery + ) => { + frameAudioQuery.volume = frameAudioQuery.volume.map((value) => { + return value * Math.pow(10, volumeRangeAdjustment / 20); + }); + }; + const calcStartTime = ( notes: Note[], tempos: Tempo[], @@ -941,6 +978,7 @@ export const singingStore = createPartialStore({ const track = getters.SELECTED_TRACK; const singer = track.singer ? { ...track.singer } : undefined; const keyRangeAdjustment = track.keyRangeAdjustment; + const volumeRangeAdjustment = track.volumeRangeAdjustment; const notes = track.notes .map((value) => ({ ...value })) .filter((value) => !state.overlappingNoteIds.has(value.id)); @@ -949,6 +987,7 @@ export const singingStore = createPartialStore({ const foundPhrases = await searchPhrases( singer, keyRangeAdjustment, + volumeRangeAdjustment, tpqn, tempos, notes @@ -1049,6 +1088,7 @@ export const singingStore = createPartialStore({ ); shiftGuidePitch(phrase.keyRangeAdjustment, frameAudioQuery); + scaleGuideVolume(volumeRangeAdjustment, frameAudioQuery); const startTime = calcStartTime( phrase.notes, @@ -1991,6 +2031,26 @@ export const singingCommandStore = transformCommandStore( dispatch("RENDER"); }, }, + COMMAND_SET_VOLUME_RANGE_ADJUSTMENT: { + mutation(draft, { volumeRangeAdjustment }) { + singingStore.mutations.SET_VOLUME_RANGE_ADJUSTMENT(draft, { + volumeRangeAdjustment, + }); + }, + async action( + { dispatch, commit }, + { volumeRangeAdjustment }: { volumeRangeAdjustment: number } + ) { + if (!isValidvolumeRangeAdjustment(volumeRangeAdjustment)) { + throw new Error("The volumeRangeAdjustment is invalid."); + } + commit("COMMAND_SET_VOLUME_RANGE_ADJUSTMENT", { + volumeRangeAdjustment, + }); + + dispatch("RENDER"); + }, + }, COMMAND_SET_TEMPO: { mutation(draft, { tempo }) { singingStore.mutations.SET_TEMPO(draft, { tempo }); diff --git a/src/store/type.ts b/src/store/type.ts index efeb26839a..6e2a5b4bfe 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -752,6 +752,7 @@ export type Singer = z.infer; export const trackSchema = z.object({ singer: singerSchema.optional(), keyRangeAdjustment: z.number(), // 音域調整量 + volumeRangeAdjustment: z.number(), // 音量域調整量 notes: z.array(noteSchema), }); export type Track = z.infer; @@ -765,6 +766,7 @@ export type PhraseState = export type Phrase = { singer?: Singer; keyRangeAdjustment: number; + volumeRangeAdjustment: number; tpqn: number; tempos: Tempo[]; notes: Note[]; @@ -821,6 +823,11 @@ export type SingingStoreTypes = { action(payload: { keyRangeAdjustment: number }): void; }; + SET_VOLUME_RANGE_ADJUSTMENT: { + mutation: { volumeRangeAdjustment: number }; + action(payload: { volumeRangeAdjustment: number }): void; + }; + SET_SCORE: { mutation: { score: Score }; action(payload: { score: Score }): void; @@ -1036,6 +1043,11 @@ export type SingingCommandStoreTypes = { action(payload: { keyRangeAdjustment: number }): void; }; + COMMAND_SET_VOLUME_RANGE_ADJUSTMENT: { + mutation: { volumeRangeAdjustment: number }; + action(payload: { volumeRangeAdjustment: number }): void; + }; + COMMAND_SET_TEMPO: { mutation: { tempo: Tempo }; action(payload: { tempo: Tempo }): void; From 72529793995dae49d52c046ac52ae9b6b84fc500 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Fri, 8 Mar 2024 02:38:32 +0900 Subject: [PATCH 14/35] =?UTF-8?q?=E9=9F=B3=E5=9F=9F=E3=83=BB=E9=9F=B3?= =?UTF-8?q?=E9=87=8F=E5=9F=9F=E8=AA=BF=E6=95=B4=E6=A9=9F=E8=83=BD=E3=82=92?= =?UTF-8?q?=E9=96=8B=E7=99=BA=E7=92=B0=E5=A2=83=E3=81=98=E3=82=83=E3=81=AA?= =?UTF-8?q?=E3=81=8F=E3=81=A6=E3=82=82=E4=BD=BF=E3=81=88=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B=20(#1918)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sing/ToolBar.vue | 43 +++++++++++++++------------------ 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/src/components/Sing/ToolBar.vue b/src/components/Sing/ToolBar.vue index 2161b45505..33010da84a 100644 --- a/src/components/Sing/ToolBar.vue +++ b/src/components/Sing/ToolBar.vue @@ -3,29 +3,26 @@
- - + + Date: Sat, 9 Mar 2024 07:18:57 +0900 Subject: [PATCH 15/35] =?UTF-8?q?=E3=80=8C=E9=9F=B3=E9=87=8F=E5=9F=9F?= =?UTF-8?q?=E8=AA=BF=E6=95=B4=E3=80=8D=E3=82=92=E3=80=8C=E5=A3=B0=E9=87=8F?= =?UTF-8?q?=E8=AA=BF=E6=95=B4=E3=80=8D=E3=81=AB=E5=A4=89=E6=9B=B4=E3=81=97?= =?UTF-8?q?=E3=80=81=E4=BD=BF=E3=81=84=E6=96=B9=E3=82=82=E5=B0=91=E3=81=97?= =?UTF-8?q?=E8=A8=98=E8=BF=B0=20(#1920)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 音量域→声量 * 動かせる音域を28にし、龍星くんに対応 * 説明文を追加 --- public/howtouse.md | 14 ++++++++++++++ src/components/Sing/ToolBar.vue | 6 +++--- src/sing/domain.ts | 4 ++-- src/store/type.ts | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/public/howtouse.md b/public/howtouse.md index d46fd4d169..4d32d711b0 100644 --- a/public/howtouse.md +++ b/public/howtouse.md @@ -286,6 +286,20 @@ VOICEVOX では、歌声合成機能がプロトタイプ版として提供さ ソング機能は鋭意制作中です。フィードバックをお待ちしています。 +### 音域調整 + +デフォルトの設定だと、声が低いキャラクターがうまく歌えないことがあります。 +そのような場合は「音域調整」を`-12`や`-24`などにすることで、音域を低めに合わせることができます。 + +将来的にこの値は自動設定される予定です。 + +### 声量調整 + +デフォルトの設定だと、キャラクターによっては声が少しかすれてしまうことがあります。 +そのような場合は「声量調整」を`5`や`10`などにすることで、発声をより強くすることができます。 + +将来的にこの値は自動設定される予定です。 + ## オプション 「設定」の「オプション」でいろいろな設定を変更することができます。 diff --git a/src/components/Sing/ToolBar.vue b/src/components/Sing/ToolBar.vue index 33010da84a..4838290011 100644 --- a/src/components/Sing/ToolBar.vue +++ b/src/components/Sing/ToolBar.vue @@ -16,7 +16,7 @@ { .key-range-adjustment { margin-left: 16px; margin-right: 4px; - width: 55px; + width: 50px; } .volume-range-adjustment { margin-left: 4px; margin-right: 4px; - width: 55px; + width: 50px; } .sing-tempo { diff --git a/src/sing/domain.ts b/src/sing/domain.ts index 62b6f715d6..57c1f9b129 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -282,8 +282,8 @@ export function isValidSnapType(snapType: number, tpqn: number) { export function isValidKeyRangeAdjustment(keyRangeAdjustment: number) { return ( Number.isInteger(keyRangeAdjustment) && - keyRangeAdjustment <= 24 && - keyRangeAdjustment >= -24 + keyRangeAdjustment <= 28 && + keyRangeAdjustment >= -28 ); } diff --git a/src/store/type.ts b/src/store/type.ts index 6e2a5b4bfe..4740e8a31d 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -752,7 +752,7 @@ export type Singer = z.infer; export const trackSchema = z.object({ singer: singerSchema.optional(), keyRangeAdjustment: z.number(), // 音域調整量 - volumeRangeAdjustment: z.number(), // 音量域調整量 + volumeRangeAdjustment: z.number(), // 声量調整量 notes: z.array(noteSchema), }); export type Track = z.infer; From 5be71739424a9b000b79c54307c130f7564065de Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Sat, 9 Mar 2024 07:58:45 +0900 Subject: [PATCH 16/35] =?UTF-8?q?[release-0.17]=20main=E3=83=96=E3=83=A9?= =?UTF-8?q?=E3=83=B3=E3=83=81=E3=82=92release-0.17=E3=81=AB=E3=83=9E?= =?UTF-8?q?=E3=83=BC=E3=82=B8=20(#1921)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/howtouse.md | 14 +++++++ src/components/Sing/ToolBar.vue | 67 ++++++++++++++++++++++++++------- src/sing/domain.ts | 12 +++++- src/sing/storeHelper.ts | 1 + src/store/project.ts | 23 +++++++---- src/store/singing.ts | 60 +++++++++++++++++++++++++++++ src/store/type.ts | 12 ++++++ 7 files changed, 166 insertions(+), 23 deletions(-) diff --git a/public/howtouse.md b/public/howtouse.md index dc16368c86..55e624a3e3 100644 --- a/public/howtouse.md +++ b/public/howtouse.md @@ -286,6 +286,20 @@ VOICEVOX では、歌声合成機能がプロトタイプ版として提供さ ソング機能は鋭意制作中です。フィードバックをお待ちしています。 +### 音域調整 + +デフォルトの設定だと、声が低いキャラクターがうまく歌えないことがあります。 +そのような場合は「音域調整」を`-12`や`-24`などにすることで、音域を低めに合わせることができます。 + +将来的にこの値は自動設定される予定です。 + +### 声量調整 + +デフォルトの設定だと、キャラクターによっては声が少しかすれてしまうことがあります。 +そのような場合は「声量調整」を`5`や`10`などにすることで、発声をより強くすることができます。 + +将来的にこの値は自動設定される予定です。 + ### ソング機能のよくある質問 Q. 上に赤いバーが出て声が再生されない diff --git a/src/components/Sing/ToolBar.vue b/src/components/Sing/ToolBar.vue index d5cf21512a..4838290011 100644 --- a/src/components/Sing/ToolBar.vue +++ b/src/components/Sing/ToolBar.vue @@ -3,19 +3,26 @@
- - + + store.state.timeSignatures); const keyRangeAdjustment = computed( () => store.getters.SELECTED_TRACK.keyRangeAdjustment ); +const volumeRangeAdjustment = computed( + () => store.getters.SELECTED_TRACK.volumeRangeAdjustment +); const bpmInputBuffer = ref(120); const beatsInputBuffer = ref(4); const beatTypeInputBuffer = ref(4); const keyRangeAdjustmentInputBuffer = ref(0); +const volumeRangeAdjustmentInputBuffer = ref(0); watch( tempos, @@ -218,6 +230,10 @@ watch(keyRangeAdjustment, () => { keyRangeAdjustmentInputBuffer.value = keyRangeAdjustment.value; }); +watch(volumeRangeAdjustment, () => { + volumeRangeAdjustmentInputBuffer.value = volumeRangeAdjustment.value; +}); + const setBpmInputBuffer = (bpmStr: string | number | null) => { const bpmValue = Number(bpmStr); if (!isValidBpm(bpmValue)) { @@ -252,6 +268,16 @@ const setKeyRangeAdjustmentInputBuffer = ( keyRangeAdjustmentInputBuffer.value = KeyRangeAdjustmentValue; }; +const setVolumeRangeAdjustmentInputBuffer = ( + volumeRangeAdjustmentStr: string | number | null +) => { + const volumeRangeAdjustmentValue = Number(volumeRangeAdjustmentStr); + if (!isValidvolumeRangeAdjustment(volumeRangeAdjustmentValue)) { + return; + } + volumeRangeAdjustmentInputBuffer.value = volumeRangeAdjustmentValue; +}; + const setTempo = () => { const bpm = bpmInputBuffer.value; store.dispatch("COMMAND_SET_TEMPO", { @@ -279,6 +305,13 @@ const setKeyRangeAdjustment = () => { store.dispatch("COMMAND_SET_KEY_RANGE_ADJUSTMENT", { keyRangeAdjustment }); }; +const setVolumeRangeAdjustment = () => { + const volumeRangeAdjustment = volumeRangeAdjustmentInputBuffer.value; + store.dispatch("COMMAND_SET_VOLUME_RANGE_ADJUSTMENT", { + volumeRangeAdjustment, + }); +}; + const playheadTicks = ref(0); /// 再生時間の分と秒 @@ -416,12 +449,18 @@ onUnmounted(() => { flex: 1; } -.key-shift { +.key-range-adjustment { margin-left: 16px; margin-right: 4px; width: 50px; } +.volume-range-adjustment { + margin-left: 4px; + margin-right: 4px; + width: 50px; +} + .sing-tempo { margin-left: 8px; margin-right: 4px; diff --git a/src/sing/domain.ts b/src/sing/domain.ts index 2f65927870..57c1f9b129 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -282,7 +282,15 @@ export function isValidSnapType(snapType: number, tpqn: number) { export function isValidKeyRangeAdjustment(keyRangeAdjustment: number) { return ( Number.isInteger(keyRangeAdjustment) && - keyRangeAdjustment <= 24 && - keyRangeAdjustment >= -24 + keyRangeAdjustment <= 28 && + keyRangeAdjustment >= -28 + ); +} + +export function isValidvolumeRangeAdjustment(volumeRangeAdjustment: number) { + return ( + Number.isInteger(volumeRangeAdjustment) && + volumeRangeAdjustment <= 20 && + volumeRangeAdjustment >= -20 ); } diff --git a/src/sing/storeHelper.ts b/src/sing/storeHelper.ts index 91548d1ebe..bb70d5c170 100644 --- a/src/sing/storeHelper.ts +++ b/src/sing/storeHelper.ts @@ -9,6 +9,7 @@ export const DEFAULT_BEAT_TYPE = 4; export const generatePhraseHash = async (obj: { singer: Singer | undefined; keyRangeAdjustment: number; + volumeRangeAdjustment: number; tpqn: number; tempos: Tempo[]; notes: Note[]; diff --git a/src/store/project.ts b/src/store/project.ts index 7ab4c0b6a4..096c151b2a 100755 --- a/src/store/project.ts +++ b/src/store/project.ts @@ -397,13 +397,9 @@ export const projectStore = createPartialStore({ } if ( - semver.satisfies( - projectAppVersion, - "<0.16.2", - semverSatisfiesOptions - ) + semver.satisfies(projectAppVersion, "<0.17", semverSatisfiesOptions) ) { - // 0.16.2 未満のプロジェクトファイルはトークの情報のみ + // 0.17 未満のプロジェクトファイルはトークの情報のみ // なので全情報(audioKeys/audioItems)をtalkに移動する projectData.talk = { audioKeys: projectData.audioKeys, @@ -412,7 +408,7 @@ export const projectStore = createPartialStore({ // ソングの情報を初期化 // generateSingingStoreInitialScoreが今後変わることがあるかもしれないので、 - // 0.16.2時点のスコア情報を直接書く + // 0.17時点のスコア情報を直接書く projectData.song = { tpqn: DEFAULT_TPQN, tempos: [ @@ -441,6 +437,19 @@ export const projectStore = createPartialStore({ delete projectData.audioItems; } + if ( + semver.satisfies( + projectAppVersion, + "<0.17.1", + semverSatisfiesOptions + ) + ) { + // volumeRangeAdjustmentの追加 + for (const track of projectData.song.tracks) { + track.volumeRangeAdjustment = 0; + } + } + // Validation check // トークはvalidateTalkProjectで検証する // ソングはSET_SCOREの中の`isValidScore`関数で検証される diff --git a/src/store/singing.ts b/src/store/singing.ts index ce4ec702d5..720d442e60 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -45,6 +45,7 @@ import { isValidTempo, isValidTimeSignature, isValidKeyRangeAdjustment, + isValidvolumeRangeAdjustment, secondToTick, tickToSecond, } from "@/sing/domain"; @@ -145,6 +146,7 @@ export const generateSingingStoreInitialScore = () => { { singer: undefined, keyRangeAdjustment: 0, + volumeRangeAdjustment: 0, notes: [], }, ], @@ -246,6 +248,29 @@ export const singingStore = createPartialStore({ }, }, + SET_VOLUME_RANGE_ADJUSTMENT: { + mutation( + state, + { volumeRangeAdjustment }: { volumeRangeAdjustment: number } + ) { + state.tracks[selectedTrackIndex].volumeRangeAdjustment = + volumeRangeAdjustment; + }, + async action( + { dispatch, commit }, + { volumeRangeAdjustment }: { volumeRangeAdjustment: number } + ) { + if (!isValidvolumeRangeAdjustment(volumeRangeAdjustment)) { + throw new Error("The volumeRangeAdjustment is invalid."); + } + commit("SET_VOLUME_RANGE_ADJUSTMENT", { + volumeRangeAdjustment, + }); + + dispatch("RENDER"); + }, + }, + SET_SCORE: { mutation(state, { score }: { score: Score }) { state.overlappingNoteInfos.clear(); @@ -746,6 +771,7 @@ export const singingStore = createPartialStore({ const searchPhrases = async ( singer: Singer | undefined, keyRangeAdjustment: number, + volumeRangeAdjustment: number, tpqn: number, tempos: Tempo[], notes: Note[] @@ -766,6 +792,7 @@ export const singingStore = createPartialStore({ const hash = await generatePhraseHash({ singer, keyRangeAdjustment, + volumeRangeAdjustment, tpqn, tempos, notes: phraseNotes, @@ -773,6 +800,7 @@ export const singingStore = createPartialStore({ foundPhrases.set(hash, { singer, keyRangeAdjustment, + volumeRangeAdjustment, tpqn, tempos, notes: phraseNotes, @@ -884,6 +912,15 @@ export const singingStore = createPartialStore({ }); }; + const scaleGuideVolume = ( + volumeRangeAdjustment: number, + frameAudioQuery: FrameAudioQuery + ) => { + frameAudioQuery.volume = frameAudioQuery.volume.map((value) => { + return value * Math.pow(10, volumeRangeAdjustment / 20); + }); + }; + const calcStartTime = ( notes: Note[], tempos: Tempo[], @@ -941,6 +978,7 @@ export const singingStore = createPartialStore({ const track = getters.SELECTED_TRACK; const singer = track.singer ? { ...track.singer } : undefined; const keyRangeAdjustment = track.keyRangeAdjustment; + const volumeRangeAdjustment = track.volumeRangeAdjustment; const notes = track.notes .map((value) => ({ ...value })) .filter((value) => !state.overlappingNoteIds.has(value.id)); @@ -949,6 +987,7 @@ export const singingStore = createPartialStore({ const foundPhrases = await searchPhrases( singer, keyRangeAdjustment, + volumeRangeAdjustment, tpqn, tempos, notes @@ -1049,6 +1088,7 @@ export const singingStore = createPartialStore({ ); shiftGuidePitch(phrase.keyRangeAdjustment, frameAudioQuery); + scaleGuideVolume(volumeRangeAdjustment, frameAudioQuery); const startTime = calcStartTime( phrase.notes, @@ -1991,6 +2031,26 @@ export const singingCommandStore = transformCommandStore( dispatch("RENDER"); }, }, + COMMAND_SET_VOLUME_RANGE_ADJUSTMENT: { + mutation(draft, { volumeRangeAdjustment }) { + singingStore.mutations.SET_VOLUME_RANGE_ADJUSTMENT(draft, { + volumeRangeAdjustment, + }); + }, + async action( + { dispatch, commit }, + { volumeRangeAdjustment }: { volumeRangeAdjustment: number } + ) { + if (!isValidvolumeRangeAdjustment(volumeRangeAdjustment)) { + throw new Error("The volumeRangeAdjustment is invalid."); + } + commit("COMMAND_SET_VOLUME_RANGE_ADJUSTMENT", { + volumeRangeAdjustment, + }); + + dispatch("RENDER"); + }, + }, COMMAND_SET_TEMPO: { mutation(draft, { tempo }) { singingStore.mutations.SET_TEMPO(draft, { tempo }); diff --git a/src/store/type.ts b/src/store/type.ts index efeb26839a..4740e8a31d 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -752,6 +752,7 @@ export type Singer = z.infer; export const trackSchema = z.object({ singer: singerSchema.optional(), keyRangeAdjustment: z.number(), // 音域調整量 + volumeRangeAdjustment: z.number(), // 声量調整量 notes: z.array(noteSchema), }); export type Track = z.infer; @@ -765,6 +766,7 @@ export type PhraseState = export type Phrase = { singer?: Singer; keyRangeAdjustment: number; + volumeRangeAdjustment: number; tpqn: number; tempos: Tempo[]; notes: Note[]; @@ -821,6 +823,11 @@ export type SingingStoreTypes = { action(payload: { keyRangeAdjustment: number }): void; }; + SET_VOLUME_RANGE_ADJUSTMENT: { + mutation: { volumeRangeAdjustment: number }; + action(payload: { volumeRangeAdjustment: number }): void; + }; + SET_SCORE: { mutation: { score: Score }; action(payload: { score: Score }): void; @@ -1036,6 +1043,11 @@ export type SingingCommandStoreTypes = { action(payload: { keyRangeAdjustment: number }): void; }; + COMMAND_SET_VOLUME_RANGE_ADJUSTMENT: { + mutation: { volumeRangeAdjustment: number }; + action(payload: { volumeRangeAdjustment: number }): void; + }; + COMMAND_SET_TEMPO: { mutation: { tempo: Tempo }; action(payload: { tempo: Tempo }): void; From 6a98b2c9b3bd918b7626da939a64c748e40e9149 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Sat, 9 Mar 2024 09:47:14 +0900 Subject: [PATCH 17/35] [release-0.17] to 0.17.1 (#1923) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ビルド周り改修、更新情報更新 * デフォルトの画面サイズを大きくする [update snapshots] * (スナップショットを更新) --------- Co-authored-by: github-actions[bot] --- .github/workflows/build.yml | 4 ++-- README.md | 2 +- public/updateInfos.json | 10 ++++++++++ src/backend/electron/main.ts | 4 ++-- src/components/Talk/TalkEditor.vue | 8 +++++--- ...347\224\273\351\235\242-browser-win32.png" | Bin 48714 -> 53608 bytes tests/e2e/navigators.ts | 2 +- 7 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da6309f70f..300ace8bec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,8 +27,8 @@ on: env: VOICEVOX_ENGINE_REPO_URL: "https://github.com/VOICEVOX/voicevox_engine" - VOICEVOX_ENGINE_VERSION: 0.17.0 - VOICEVOX_RESOURCE_VERSION: 0.17.0 + VOICEVOX_ENGINE_VERSION: 0.17.1 + VOICEVOX_RESOURCE_VERSION: 0.17.1 VOICEVOX_EDITOR_VERSION: |- # releaseタグ名か、workflow_dispatchでのバージョン名か、999.999.999-developが入る ${{ github.event.release.tag_name || github.event.inputs.version || '999.999.999-develop' }} diff --git a/README.md b/README.md index 8b86c47496..ff4597406b 100644 --- a/README.md +++ b/README.md @@ -114,7 +114,7 @@ Playwright を使用しているためテストパターンを生成すること **ブラウザ版を起動している状態で**以下のコマンドを実行してください。 ```bash -npx playwright codegen http://localhost:5173/ --viewport-size=800,600 +npx playwright codegen http://localhost:5173/ --viewport-size=1024,630 ``` 詳細は [Playwright ドキュメントの Test generator](https://playwright.dev/docs/codegen-intro) を参照してください。 diff --git a/public/updateInfos.json b/public/updateInfos.json index c57416ea57..9565bf127d 100644 --- a/public/updateInfos.json +++ b/public/updateInfos.json @@ -1,4 +1,14 @@ [ + { + "version": "0.17.1", + "descriptions": [ + "キャラクター「玄野武宏」「白上虎太郎」「青山龍星」「冥鳴ひまり」「九州そら」のハミングを追加", + "キャラクター「もち子さん」「剣崎雌雄」のハミングを追加", + "音域調整・声量調整機能", + "バグ修正" + ], + "contributors": ["Hiroshiba", "sigprogramming"] + }, { "version": "0.17.0", "descriptions": [ diff --git a/src/backend/electron/main.ts b/src/backend/electron/main.ts index 11d649cf5f..2c5c10e345 100644 --- a/src/backend/electron/main.ts +++ b/src/backend/electron/main.ts @@ -364,8 +364,8 @@ let filePathOnMac: string | undefined = undefined; // create window async function createWindow() { const mainWindowState = windowStateKeeper({ - defaultWidth: 800, - defaultHeight: 600, + defaultWidth: 1024, + defaultHeight: 630, }); const currentTheme = configManager.get("currentTheme"); diff --git a/src/components/Talk/TalkEditor.vue b/src/components/Talk/TalkEditor.vue index 9b302d8888..2513a06e8c 100644 --- a/src/components/Talk/TalkEditor.vue +++ b/src/components/Talk/TalkEditor.vue @@ -221,10 +221,11 @@ const removeAudioItem = async () => { }; // view -const DEFAULT_PORTRAIT_PANE_WIDTH = 25; // % +const DEFAULT_PORTRAIT_PANE_WIDTH = 22; // % const MIN_PORTRAIT_PANE_WIDTH = 0; const MAX_PORTRAIT_PANE_WIDTH = 40; -const MIN_AUDIO_INFO_PANE_WIDTH = 160; // px +const DEFAULT_AUDIO_INFO_PANE_WIDTH = 200; // px +const MIN_AUDIO_INFO_PANE_WIDTH = 160; const MAX_AUDIO_INFO_PANE_WIDTH = 250; const MIN_AUDIO_DETAIL_PANE_HEIGHT = 185; // px const MAX_AUDIO_DETAIL_PANE_HEIGHT = 500; @@ -365,7 +366,8 @@ watch(shouldShowPanes, (val, old) => { ); audioInfoPaneWidth.value = clamp( - splitterPosition.value.audioInfoPaneWidth ?? MIN_AUDIO_INFO_PANE_WIDTH, + splitterPosition.value.audioInfoPaneWidth ?? + DEFAULT_AUDIO_INFO_PANE_WIDTH, MIN_AUDIO_INFO_PANE_WIDTH, MAX_AUDIO_INFO_PANE_WIDTH ); diff --git "a/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\241\343\202\244\343\203\263\347\224\273\351\235\242-browser-win32.png" "b/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\241\343\202\244\343\203\263\347\224\273\351\235\242-browser-win32.png" index 7ed89f66aaa410750b59d510142c5636c11ac8b5..1f29080d52d77d07a6b59ef8c2ccedb4f9a7b604 100644 GIT binary patch literal 53608 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 Date: Sun, 10 Mar 2024 01:05:20 +0900 Subject: [PATCH 18/35] =?UTF-8?q?[release-0.17]=20hotfix:=20=E3=83=97?= =?UTF-8?q?=E3=83=AD=E3=82=B8=E3=82=A7=E3=82=AF=E3=83=88=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=81=BF=E3=81=A8?= =?UTF-8?q?=E5=A3=B0=E9=87=8F=E3=83=BB=E9=9F=B3=E5=9F=9F=E3=83=91=E3=83=A9?= =?UTF-8?q?=E3=83=A1=E3=83=BC=E3=82=BF=E3=81=AE=E5=88=9D=E6=9C=9F=E5=8C=96?= =?UTF-8?q?=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3=20(#1927)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ロジェクトファイル読み込みと声量・音域パラメータの初期化バグ修正 * ワークアラウンド追加 --- src/components/Sing/SingEditor.vue | 10 ++++++++-- src/components/Sing/ToolBar.vue | 25 ++++++++++++++++--------- src/store/project.ts | 3 +++ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/components/Sing/SingEditor.vue b/src/components/Sing/SingEditor.vue index 27da8b6c09..a88a2c1ad7 100644 --- a/src/components/Sing/SingEditor.vue +++ b/src/components/Sing/SingEditor.vue @@ -89,6 +89,14 @@ onetimeWatch( notes: [], }, }); + + // CI上のe2eテストのNemoエンジンには歌手がいないためエラーになるのでワークアラウンド + // FIXME: 歌手をいると見せかけるmock APIを作り、ここのtry catchを削除する + try { + await store.dispatch("SET_SINGER", {}); + } catch (e) { + window.backend.logError(e); + } } await store.dispatch("SET_VOLUME", { volume: 0.6 }); @@ -101,8 +109,6 @@ onetimeWatch( }); isCompletedInitialStartup.value = true; - await store.dispatch("SET_SINGER", {}); - return "unwatch"; }, { diff --git a/src/components/Sing/ToolBar.vue b/src/components/Sing/ToolBar.vue index 4838290011..a8a8172d64 100644 --- a/src/components/Sing/ToolBar.vue +++ b/src/components/Sing/ToolBar.vue @@ -134,7 +134,6 @@ diff --git a/src/components/Dialog/ImportMidiDialog.vue b/src/components/Dialog/ImportMidiDialog.vue new file mode 100644 index 0000000000..59ca45f2e9 --- /dev/null +++ b/src/components/Dialog/ImportMidiDialog.vue @@ -0,0 +1,166 @@ + + + diff --git a/src/components/Sing/MenuBar.vue b/src/components/Sing/MenuBar.vue index 578b08cabf..f9a1802ec1 100644 --- a/src/components/Sing/MenuBar.vue +++ b/src/components/Sing/MenuBar.vue @@ -13,7 +13,9 @@ const uiLocked = computed(() => store.getters.UI_LOCKED); const importMidiFile = async () => { if (uiLocked.value) return; - await store.dispatch("IMPORT_MIDI_FILE", {}); + await store.dispatch("SET_DIALOG_OPEN", { + isImportMidiDialogOpen: true, + }); }; const importMusicXMLFile = async () => { diff --git a/src/store/singing.ts b/src/store/singing.ts index a21781ed6b..f1bfe8f60d 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -1220,7 +1220,10 @@ export const singingStore = createPartialStore({ IMPORT_MIDI_FILE: { action: createUILockAction( - async ({ dispatch }, { filePath }: { filePath?: string }) => { + async ( + { dispatch }, + { filePath, trackIndex = 0 }: { filePath: string; trackIndex: number } + ) => { const convertPosition = ( position: number, sourceTpqn: number, @@ -1289,25 +1292,16 @@ export const singingStore = createPartialStore({ }); }; - if (!filePath) { - filePath = await window.backend.showImportFileDialog({ - title: "MIDI読み込み", - name: "MIDI", - extensions: ["mid", "midi"], - }); - if (!filePath) return; - } - + // NOTE: トラック選択のために一度ファイルを読み込んでいるので、Midiを渡すなどでもよさそう const midiData = getValueOrThrow( await window.backend.readFile({ filePath }) ); const midi = new Midi(midiData); - const midiTpqn = midi.header.ppq; const midiTempos = [...midi.header.tempos]; const midiTimeSignatures = [...midi.header.timeSignatures]; - // TODO: UIで読み込むトラックを選択できるようにする - const midiNotes = [...midi.tracks[0].notes]; // ひとまず1トラック目のみを読み込む + + const midiNotes = [...midi.tracks[trackIndex].notes]; midiTempos.sort((a, b) => a.ticks - b.ticks); midiTimeSignatures.sort((a, b) => a.ticks - b.ticks); diff --git a/src/store/type.ts b/src/store/type.ts index f312703887..ebf1bac765 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -923,7 +923,7 @@ export type SingingStoreTypes = { }; IMPORT_MIDI_FILE: { - action(payload: { filePath?: string }): void; + action(payload: { filePath: string; trackIndex: number }): void; }; IMPORT_MUSICXML_FILE: { @@ -1521,6 +1521,7 @@ export type UiStoreState = { isDictionaryManageDialogOpen: boolean; isEngineManageDialogOpen: boolean; isUpdateNotificationDialogOpen: boolean; + isImportMidiDialogOpen: boolean; isMaximized: boolean; isPinned: boolean; isFullscreen: boolean; @@ -1592,6 +1593,7 @@ export type UiStoreTypes = { isCharacterOrderDialogOpen?: boolean; isEngineManageDialogOpen?: boolean; isUpdateNotificationDialogOpen?: boolean; + isImportMidiDialogOpen?: boolean; }; action(payload: { isDefaultStyleSelectDialogOpen?: boolean; @@ -1605,6 +1607,7 @@ export type UiStoreTypes = { isCharacterOrderDialogOpen?: boolean; isEngineManageDialogOpen?: boolean; isUpdateNotificationDialogOpen?: boolean; + isImportMidiDialogOpen?: boolean; }): void; }; diff --git a/src/store/ui.ts b/src/store/ui.ts index 17df3867bd..e8e8075dc1 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -62,6 +62,7 @@ export const uiStoreState: UiStoreState = { isDictionaryManageDialogOpen: false, isEngineManageDialogOpen: false, isUpdateNotificationDialogOpen: false, + isImportMidiDialogOpen: false, isMaximized: false, isPinned: false, isFullscreen: false, @@ -181,6 +182,7 @@ export const uiStore = createPartialStore({ isCharacterOrderDialogOpen?: boolean; isEngineManageDialogOpen?: boolean; isUpdateNotificationDialogOpen?: boolean; + isImportMidiDialogOpen?: boolean; } ) { for (const [key, value] of Object.entries(dialogState)) { From 125a5e89f2076d426541680dd3db035ef874f363 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Thu, 21 Mar 2024 07:51:00 +0900 Subject: [PATCH 28/35] =?UTF-8?q?Add:=20Ctrl+Shift+=E3=83=89=E3=83=A9?= =?UTF-8?q?=E3=83=83=E3=82=B0=E3=81=A7=E8=BF=BD=E5=8A=A0=E9=81=B8=E6=8A=9E?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#1934?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add: Ctrl+Shift+ドラッグで追加選択できるように * Fix: Macで動かないのを修正 * Update src/components/Sing/ScoreSequencer.vue --------- Co-authored-by: Hiroshiba --- src/components/Sing/ScoreSequencer.vue | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index a929d78aab..2d34687bee 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -794,7 +794,7 @@ const onMouseUp = (event: MouseEvent) => { } if (isRectSelecting.value) { - rectSelect(); + rectSelect(isOnCommandOrCtrlKeyDown(event)); return; } @@ -821,7 +821,11 @@ const onMouseUp = (event: MouseEvent) => { nowPreviewing.value = false; }; -const rectSelect = () => { +/** + * 矩形選択。 + * @param additive 追加選択とするかどうか。 + */ +const rectSelect = (additive: boolean) => { const rectSelectHitboxElement = rectSelectHitbox.value; if (!rectSelectHitboxElement) { throw new Error("rectSelectHitboxElement is null."); @@ -855,7 +859,9 @@ const rectSelect = () => { noteIdsToSelect.push(note.id); } } - store.dispatch("DESELECT_ALL_NOTES"); + if (!additive) { + store.dispatch("DESELECT_ALL_NOTES"); + } store.dispatch("SELECT_NOTES", { noteIds: noteIdsToSelect }); }; From 86d2e8ebd7c8bfbac45981559a4d6f7f1cb27b4a Mon Sep 17 00:00:00 2001 From: Romot Date: Thu, 21 Mar 2024 07:51:53 +0900 Subject: [PATCH 29/35] =?UTF-8?q?=E3=82=BD=E3=83=B3=E3=82=B0:=20UST?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AE=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=88=E6=A9=9F=E8=83=BD=E3=81=AE=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=20(#1933)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #1908 USTファイルを一応読める試行実装 * #1908 USTインポート修正 * #1908 調整[update snapshots] * Update src/store/singing.ts Co-authored-by: Hiroshiba * Update src/store/singing.ts Co-authored-by: Hiroshiba * エラー内容を修正 --------- Co-authored-by: Romot Co-authored-by: Hiroshiba --- src/components/Sing/MenuBar.vue | 13 ++++ src/store/singing.ts | 121 ++++++++++++++++++++++++++++++++ src/store/type.ts | 4 ++ 3 files changed, 138 insertions(+) diff --git a/src/components/Sing/MenuBar.vue b/src/components/Sing/MenuBar.vue index f9a1802ec1..a3cabf012d 100644 --- a/src/components/Sing/MenuBar.vue +++ b/src/components/Sing/MenuBar.vue @@ -23,6 +23,11 @@ const importMusicXMLFile = async () => { await store.dispatch("IMPORT_MUSICXML_FILE", {}); }; +const importUstFile = async () => { + if (uiLocked.value) return; + await store.dispatch("IMPORT_UST_FILE", {}); +}; + const exportWaveFile = async () => { if (uiLocked.value) return; await store.dispatch("EXPORT_WAVE_FILE", {}); @@ -54,5 +59,13 @@ const fileSubMenuData: MenuItemData[] = [ }, disableWhenUiLocked: true, }, + { + type: "button", + label: "UST読み込み", + onClick: () => { + importUstFile(); + }, + disableWhenUiLocked: true, + }, ]; diff --git a/src/store/singing.ts b/src/store/singing.ts index f1bfe8f60d..cecafe43f2 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -1699,6 +1699,127 @@ export const singingStore = createPartialStore({ ), }, + IMPORT_UST_FILE: { + action: createUILockAction( + async ({ dispatch }, { filePath }: { filePath?: string }) => { + // USTファイルの読み込み + if (!filePath) { + filePath = await window.backend.showImportFileDialog({ + title: "UST読み込み", + name: "UST", + extensions: ["ust"], + }); + if (!filePath) return; + } + // ファイルの読み込み + const fileData = getValueOrThrow( + await window.backend.readFile({ filePath }) + ); + + // ファイルフォーマットに応じてエンコーディングを変える + // UTF-8とShiftJISの2種類に対応 + let ustData; + try { + ustData = new TextDecoder("utf-8").decode(fileData); + // ShiftJISの場合はShiftJISでデコードし直す + if (ustData.includes("\ufffd")) { + ustData = new TextDecoder("shift-jis").decode(fileData); + } + } catch (error) { + throw new Error("Failed to decode UST file.", { cause: error }); + } + if (!ustData || typeof ustData !== "string") { + throw new Error("Failed to decode UST file."); + } + + // 初期化 + const tpqn = DEFAULT_TPQN; + const tempos: Tempo[] = [ + { + position: 0, + bpm: DEFAULT_BPM, + }, + ]; + const timeSignatures: TimeSignature[] = [ + { + measureNumber: 1, + beats: DEFAULT_BEATS, + beatType: DEFAULT_BEAT_TYPE, + }, + ]; + const notes: Note[] = []; + + // USTファイルのセクションをパース + const parseSection = (section: string): { [key: string]: string } => { + const sectionNameMatch = section.match(/\[(.+)\]/); + if (!sectionNameMatch) { + throw new Error("UST section name not found"); + } + const params = section.split(/[\r\n]+/).reduce((acc, line) => { + const [key, value] = line.split("="); + if (key && value) { + acc[key] = value; + } + return acc; + }, {} as { [key: string]: string }); + return { + ...params, + sectionName: sectionNameMatch[1], + }; + }; + + // セクションを分割 + const sections = ustData.split(/^(?=\[)/m); + // ポジション + let position = 0; + // セクションごとに処理 + sections.forEach((section) => { + const params = parseSection(section); + // SETTINGセクション + if (params.sectionName === "#SETTING") { + const tempo = Number(params["Tempo"]); + if (tempo) tempos[0].bpm = tempo; + } + // ノートセクション + if (params.sectionName.match(/^#\d{4}/)) { + // テンポ変更があれば追加 + const tempo = Number(params["Tempo"]); + if (tempo) tempos.push({ position, bpm: tempo }); + const noteNumber = Number(params["NoteNum"]); + const duration = Number(params["Length"]); + // 歌詞の前に連続音が含まれている場合は除去 + const lyric = params["Lyric"].includes(" ") + ? params["Lyric"].split(" ")[1] + : params["Lyric"]; + // 休符であればポジションを進めるのみ + if (lyric === "R") { + position += duration; + } else { + // それ以外の場合はノートを追加 + notes.push({ + id: uuidv4(), + position, + duration, + noteNumber, + lyric, + }); + position += duration; + } + } + }); + + await dispatch("SET_SCORE", { + score: { + tpqn, + tempos, + timeSignatures, + notes, + }, + }); + } + ), + }, + SET_NOW_AUDIO_EXPORTING: { mutation(state, { nowAudioExporting }) { state.nowAudioExporting = nowAudioExporting; diff --git a/src/store/type.ts b/src/store/type.ts index ebf1bac765..8073810ff3 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -930,6 +930,10 @@ export type SingingStoreTypes = { action(payload: { filePath?: string }): void; }; + IMPORT_UST_FILE: { + action(payload: { filePath?: string }): void; + }; + EXPORT_WAVE_FILE: { action(payload: { filePath?: string }): SaveResultObject; }; From 7096c4637dd033e796472a7d9597344f0bfa3bcc Mon Sep 17 00:00:00 2001 From: Romot Date: Fri, 22 Mar 2024 19:54:15 +0900 Subject: [PATCH 30/35] =?UTF-8?q?#1877=20=E3=82=BD=E3=83=B3=E3=82=B0:=20?= =?UTF-8?q?=E7=B7=A8=E9=9B=86=E3=83=A1=E3=83=8B=E3=83=A5=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=81=A8=E3=83=8E=E3=83=BC=E3=83=88=E3=81=AE?= =?UTF-8?q?=E3=82=B3=E3=83=94=E3=83=BC=EF=BC=86=E3=83=9A=E3=83=BC=E3=82=B9?= =?UTF-8?q?=E3=83=88=E3=81=AE=E5=AE=9F=E8=A3=85=20(#1903)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * #1877 コピペや右クリックメニュ ー * #1877 不要セパレータ削除 * Update src/components/Sing/ToolBar.vue Co-authored-by: Hiroshiba * Update src/components/Sing/SequencerNote.vue Co-authored-by: Hiroshiba * Update src/store/singing.ts Co-authored-by: Hiroshiba * Update src/store/singing.ts Co-authored-by: Hiroshiba * #1897 指摘点修正+クオンタイズ * #1877 コメント追加 [update snapshots] * (スナップショットを更新) * #1897 clipboardおよびtextパースでtry...catchする * #1877 import修正 * (スナップショットを更新) * (テストを回すための空コミット) * Apply suggestions from code review * await追加&コードを周りに合わせた --------- Co-authored-by: Romot Co-authored-by: Hiroshiba Co-authored-by: github-actions[bot] --- src/components/Menu/MenuBar/BaseMenuBar.vue | 37 +++++ src/components/Sing/MenuBar.vue | 69 +++++++- src/components/Sing/ScoreSequencer.vue | 149 ++++++++++++++++++ src/components/Sing/SequencerNote.vue | 39 ++++- src/components/Talk/MenuBar.vue | 9 +- src/store/singing.ts | 122 ++++++++++++++ src/store/type.ts | 21 +++ src/type/preload.ts | 25 +++ ...347\224\273\351\235\242-browser-win32.png" | Bin 53608 -> 53964 bytes 9 files changed, 465 insertions(+), 6 deletions(-) diff --git a/src/components/Menu/MenuBar/BaseMenuBar.vue b/src/components/Menu/MenuBar/BaseMenuBar.vue index 3b7b3ea75f..26570a57d5 100644 --- a/src/components/Menu/MenuBar/BaseMenuBar.vue +++ b/src/components/Menu/MenuBar/BaseMenuBar.vue @@ -40,6 +40,8 @@ const props = defineProps<{ /** 「ファイル」メニューのサブメニュー */ fileSubMenuData: MenuItemData[]; + /** 「編集」メニューのサブメニュー */ + editSubMenuData: MenuItemData[]; /** エディタの種類 */ editor: "talk" | "song"; }>(); @@ -90,6 +92,8 @@ const titleText = computed( ? ` - Port: ${defaultEngineAltPortTo.value}` : "") ); +const canUndo = computed(() => store.getters.CAN_UNDO(props.editor)); +const canRedo = computed(() => store.getters.CAN_REDO(props.editor)); // FIXME: App.vue内に移動する watch(titleText, (newTitle) => { @@ -327,6 +331,39 @@ const menudata = computed(() => [ }, ], }, + { + type: "root", + label: "編集", + onClick: () => { + closeAllDialog(); + }, + disableWhenUiLocked: false, + subMenu: [ + { + type: "button", + label: "元に戻す", + onClick: async () => { + if (!uiLocked.value) { + await store.dispatch("UNDO", { editor: props.editor }); + } + }, + disabled: !canUndo.value, + disableWhenUiLocked: true, + }, + { + type: "button", + label: "やり直す", + onClick: async () => { + if (!uiLocked.value) { + await store.dispatch("REDO", { editor: props.editor }); + } + }, + disabled: !canRedo.value, + disableWhenUiLocked: true, + }, + ...props.editSubMenuData, + ], + }, { type: "root", label: "エンジン", diff --git a/src/components/Sing/MenuBar.vue b/src/components/Sing/MenuBar.vue index a3cabf012d..b960a64f3d 100644 --- a/src/components/Sing/MenuBar.vue +++ b/src/components/Sing/MenuBar.vue @@ -1,5 +1,9 @@ diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index 2d34687bee..946549275f 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -28,6 +28,7 @@ @mouseleave="onMouseLeave" @wheel="onWheel" @scroll="onScroll" + @contextmenu.prevent > @@ -208,6 +209,7 @@ }" @input="setZoomY" /> +
@@ -221,6 +223,10 @@ import { onDeactivated, } from "vue"; import { v4 as uuidv4 } from "uuid"; +import ContextMenu, { + ContextMenuItemData, +} from "@/components/Menu/ContextMenu.vue"; +import { isMac } from "@/type/preload"; import { useStore } from "@/store"; import { Note } from "@/store/type"; import { @@ -251,6 +257,7 @@ import SequencerPhraseIndicator from "@/components/Sing/SequencerPhraseIndicator import CharacterPortrait from "@/components/Sing/CharacterPortrait.vue"; import SequencerPitch from "@/components/Sing/SequencerPitch.vue"; import { isOnCommandOrCtrlKeyDown } from "@/store/utility"; +import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; import { useShiftKey } from "@/composables/useModifierKey"; type PreviewMode = "ADD" | "MOVE" | "RESIZE_RIGHT" | "RESIZE_LEFT"; @@ -276,6 +283,9 @@ const timeSignatures = computed(() => state.timeSignatures); // ノート const notes = computed(() => store.getters.SELECTED_TRACK.notes); +const isNoteSelected = computed(() => { + return state.selectedNoteIds.size > 0; +}); const unselectedNotes = computed(() => { const selectedNoteIds = state.selectedNoteIds; return notes.value.filter((value) => !selectedNoteIds.has(value.id)); @@ -744,6 +754,15 @@ const onMouseDown = (event: MouseEvent) => { if (!isSelfEventTarget(event)) { return; } + + // macOSの場合、Ctrl+クリックが右クリックのため、その場合はノートを追加しない + if (isMac && event.ctrlKey && event.button === 0) { + return; + } + + // TODO: メニューが表示されている場合はメニュー非表示のみ行いたい + + // 選択中のノートが無い場合、プレビューを開始しノートIDをリセット if (event.button === 0) { if (event.shiftKey) { isRectSelecting.value = true; @@ -1152,6 +1171,136 @@ onDeactivated(() => { document.removeEventListener("keydown", handleKeydown); }); + +// コンテキストメニュー +// TODO: 分割する +const { registerHotkeyWithCleanup } = useHotkeyManager(); + +registerHotkeyWithCleanup({ + editor: "song", + name: "コピー", + callback: () => { + if (nowPreviewing.value) { + return; + } + if (state.selectedNoteIds.size === 0) { + return; + } + store.dispatch("COPY_NOTES_TO_CLIPBOARD"); + }, +}); + +registerHotkeyWithCleanup({ + editor: "song", + name: "切り取り", + callback: () => { + if (nowPreviewing.value) { + return; + } + if (state.selectedNoteIds.size === 0) { + return; + } + store.dispatch("COMMAND_CUT_NOTES_TO_CLIPBOARD"); + }, +}); + +registerHotkeyWithCleanup({ + editor: "song", + name: "貼り付け", + callback: () => { + if (nowPreviewing.value) { + return; + } + store.dispatch("COMMAND_PASTE_NOTES_FROM_CLIPBOARD"); + }, +}); + +registerHotkeyWithCleanup({ + editor: "song", + name: "すべて選択", + callback: () => { + if (nowPreviewing.value) { + return; + } + store.dispatch("SELECT_ALL_NOTES"); + }, +}); + +const contextMenu = ref>(); + +const contextMenuData = ref([ + { + type: "button", + label: "コピー", + onClick: async () => { + contextMenu.value?.hide(); + await store.dispatch("COPY_NOTES_TO_CLIPBOARD"); + }, + disabled: !isNoteSelected.value, + disableWhenUiLocked: true, + }, + { + type: "button", + label: "切り取り", + onClick: async () => { + contextMenu.value?.hide(); + await store.dispatch("COMMAND_CUT_NOTES_TO_CLIPBOARD"); + }, + disabled: !isNoteSelected.value, + disableWhenUiLocked: true, + }, + { + type: "button", + label: "貼り付け", + onClick: async () => { + contextMenu.value?.hide(); + await store.dispatch("COMMAND_PASTE_NOTES_FROM_CLIPBOARD"); + }, + disableWhenUiLocked: true, + }, + { type: "separator" }, + { + type: "button", + label: "すべて選択", + onClick: async () => { + contextMenu.value?.hide(); + await store.dispatch("SELECT_ALL_NOTES"); + }, + disableWhenUiLocked: true, + }, + { + type: "button", + label: "選択解除", + onClick: async () => { + contextMenu.value?.hide(); + await store.dispatch("DESELECT_ALL_NOTES"); + }, + disabled: !isNoteSelected.value, + disableWhenUiLocked: true, + }, + { type: "separator" }, + { + type: "button", + label: "クオンタイズ", + onClick: async () => { + contextMenu.value?.hide(); + await store.dispatch("COMMAND_QUANTIZE_SELECTED_NOTES"); + }, + disabled: !isNoteSelected.value, + disableWhenUiLocked: true, + }, + { type: "separator" }, + { + type: "button", + label: "削除", + onClick: async () => { + contextMenu.value?.hide(); + await store.dispatch("COMMAND_REMOVE_SELECTED_NOTES"); + }, + disabled: !isNoteSelected.value, + disableWhenUiLocked: true, + }, +]);