From e5e612063a7a22f12f4dc27f60fd8637dc1e7fe0 Mon Sep 17 00:00:00 2001 From: Kai Date: Tue, 21 May 2024 03:54:53 +0900 Subject: [PATCH 01/31] =?UTF-8?q?fix:=20=E3=83=86=E3=82=AD=E3=82=B9?= =?UTF-8?q?=E3=83=88=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=81=BF=E3=81=A7=E3=83=AB?= =?UTF-8?q?=E3=83=93=E6=96=87=E5=AD=97=E5=88=97=E3=82=92=E5=89=8A=E9=99=A4?= =?UTF-8?q?=20(#2084)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fix: テキスト読み込みルビ文字列を削除できるように --- src/store/audio.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/store/audio.ts b/src/store/audio.ts index 45abaf0d51..a92ee136fe 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -671,7 +671,10 @@ export const audioStore = createPartialStore({ const baseAudioItem = payload.baseAudioItem; const fetchQueryParams = { - text, + text: extractYomiText(text, { + enableMemoNotation: state.enableMemoNotation, + enableRubyNotation: state.enableRubyNotation, + }), engineId: voice.engineId, styleId: voice.styleId, }; From 47f2e8bb8619b4abcb8a684060083abb911dc63e Mon Sep 17 00:00:00 2001 From: sabonerune <102559104+sabonerune@users.noreply.github.com> Date: Tue, 21 May 2024 04:09:38 +0900 Subject: [PATCH 02/31] =?UTF-8?q?refactor:=20`image64Helper.ts`=E3=82=92`b?= =?UTF-8?q?ase64Helper.ts`=E3=81=B8=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0=20(#2080)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * perf: BASE64のデコードを`Buffer.from`から`atob`へ移行 * fix: `imageHelper`to`base64Helper` * perf: `buffer`に戻す --- src/components/CharacterButton.vue | 2 +- src/components/Dialog/EngineManageDialog.vue | 2 +- src/components/Menu/MenuBar/MenuBar.vue | 2 +- .../Sing/CharacterMenuButton/MenuButton.vue | 2 +- src/helpers/{imageHelper.ts => base64Helper.ts} | 14 ++++++++------ src/index.html | 5 ----- src/store/audio.ts | 9 ++------- 7 files changed, 14 insertions(+), 22 deletions(-) rename src/helpers/{imageHelper.ts => base64Helper.ts} (62%) diff --git a/src/components/CharacterButton.vue b/src/components/CharacterButton.vue index 8be19c2723..3e49c5f91a 100644 --- a/src/components/CharacterButton.vue +++ b/src/components/CharacterButton.vue @@ -196,7 +196,7 @@ diff --git a/src/store/audio.ts b/src/store/audio.ts index a92ee136fe..8c50abe8f4 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -53,7 +53,7 @@ import { Voice, } from "@/type/preload"; import { AudioQuery, AccentPhrase, Speaker, SpeakerInfo } from "@/openapi"; -import { base64ImageToUri } from "@/helpers/imageHelper"; +import { base64ImageToUri, base64ToUri } from "@/helpers/base64Helper"; import { getValueOrThrow, ResultError } from "@/type/result"; function generateAudioKey() { @@ -288,11 +288,6 @@ export const audioStore = createPartialStore({ const instance = await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { engineId, }); - const base64ToUrl = function (base64: string, type: string) { - const buffer = Buffer.from(base64, "base64"); - const iconBlob = new Blob([buffer.buffer], { type: type }); - return URL.createObjectURL(iconBlob); - }; const getStyles = function ( speaker: Speaker, speakerInfo: SpeakerInfo, @@ -307,7 +302,7 @@ export const audioStore = createPartialStore({ `Not found the style id "${style.id}" of "${speaker.name}". `, ); const voiceSamples = styleInfo.voiceSamples.map((voiceSample) => { - return base64ToUrl(voiceSample, "audio/wav"); + return base64ToUri(voiceSample, "audio/wav"); }); styles[i] = { styleName: style.name, From df8aabdcfbc388616bd8d3e3e57be75e34cf0e4c Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Tue, 21 May 2024 12:57:03 +0900 Subject: [PATCH 03/31] =?UTF-8?q?=E8=BE=9E=E6=9B=B8=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E3=83=80=E3=82=A4=E3=82=A2=E3=83=AD=E3=82=B0=E3=83=BB=E3=83=84?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E3=83=90=E3=83=BC=E3=82=AB=E3=82=B9=E3=82=BF?= =?UTF-8?q?=E3=83=9E=E3=82=A4=E3=82=BA=E3=81=AE=E7=B7=A8=E9=9B=86=E7=A0=B4?= =?UTF-8?q?=E6=A3=84=E3=81=AE=E6=A1=88=E5=86=85=E3=81=8C=E3=82=8F=E3=81=8B?= =?UTF-8?q?=E3=82=8A=E3=81=AB=E3=81=8F=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E4=BF=AE=E6=AD=A3=20(#2069)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 辞書保存ダイアログの編集破棄の案内がわかりにくかったので修正 * 他にもわかりにくいとこ直した --- src/components/Dialog/DictionaryManageDialog.vue | 7 +++---- src/components/Dialog/ToolBarCustomDialog.vue | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/Dialog/DictionaryManageDialog.vue b/src/components/Dialog/DictionaryManageDialog.vue index b646d45205..79971b92eb 100644 --- a/src/components/Dialog/DictionaryManageDialog.vue +++ b/src/components/Dialog/DictionaryManageDialog.vue @@ -559,7 +559,7 @@ const isDeletable = computed(() => !!selectedId.value); const deleteWord = async () => { const result = await store.dispatch("SHOW_WARNING_DIALOG", { title: "登録された単語を削除しますか?", - message: "削除された単語は復旧できません。", + message: "削除された単語は元に戻せません。", actionName: "削除", }); if (result === "OK") { @@ -594,9 +594,8 @@ const discardOrNotDialog = async (okCallback: () => void) => { if (isWordChanged.value) { const result = await store.dispatch("SHOW_WARNING_DIALOG", { title: "単語の追加・変更を破棄しますか?", - message: - "このまま続行すると、単語の追加・変更は破棄されてリセットされます。", - actionName: "続行", + message: "破棄すると、単語の追加・変更はリセットされます。", + actionName: "破棄", }); if (result === "OK") { okCallback(); diff --git a/src/components/Dialog/ToolBarCustomDialog.vue b/src/components/Dialog/ToolBarCustomDialog.vue index 1536ae5723..e0f5db26b2 100644 --- a/src/components/Dialog/ToolBarCustomDialog.vue +++ b/src/components/Dialog/ToolBarCustomDialog.vue @@ -228,7 +228,8 @@ const finishOrNotDialog = async () => { if (isChanged.value) { const result = await store.dispatch("SHOW_WARNING_DIALOG", { title: "カスタマイズを終了しますか?", - message: "このまま終了すると、カスタマイズは破棄されてリセットされます。", + message: + "保存せずに終了すると、カスタマイズは破棄されてリセットされます。", actionName: "終了", }); if (result === "OK") { From e434b2ec49acfb773f30b0b1fe76b735d3d36578 Mon Sep 17 00:00:00 2001 From: Sig Date: Wed, 22 May 2024 02:26:13 +0900 Subject: [PATCH 04/31] =?UTF-8?q?=E3=82=BD=E3=83=B3=E3=82=B0=EF=BC=9A?= =?UTF-8?q?=E3=82=B7=E3=83=BC=E3=82=B1=E3=83=B3=E3=82=B5=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E3=82=B0=E3=83=AA=E3=83=83=E3=83=89=E3=82=92=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=83=9D=E3=83=BC=E3=83=8D=E3=83=B3=E3=83=88=E5=8C=96=E3=81=99?= =?UTF-8?q?=E3=82=8B=20(#2087)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * グリッドをコンポーネント化した * 最後の小節でマウスホイールで縮小を行ったときに表示がおかしくなるのを修正 * PreviewModeの文字列を変更 --- src/components/Sing/ScoreSequencer.vue | 208 ++++--------------------- src/components/Sing/SequencerGrid.vue | 158 +++++++++++++++++++ src/components/Sing/SequencerRuler.vue | 6 +- src/sing/domain.ts | 7 +- src/store/singing.ts | 18 +++ src/store/type.ts | 6 +- 6 files changed, 219 insertions(+), 184 deletions(-) create mode 100644 src/components/Sing/SequencerGrid.vue diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index 6bfcd457b9..94c6585f5c 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -6,7 +6,7 @@ - - - - - - - - - - - - - - +
+ >
{ return event.target === event.currentTarget; }; -const store = useStore(); const { warn } = createLogger("ScoreSequencer"); +const store = useStore(); const state = store.state; -// 分解能(Ticks Per Quarter Note) +// TPQN、テンポ、ノーツ const tpqn = computed(() => state.tpqn); - -// テンポ const tempos = computed(() => state.tempos); - -// 拍子 -const timeSignatures = computed(() => state.timeSignatures); - -// ノート const notes = computed(() => store.getters.SELECTED_TRACK.notes); const isNoteSelected = computed(() => { return state.selectedNoteIds.size > 0; @@ -334,49 +259,15 @@ const snapTicks = computed(() => { return getNoteDuration(state.sequencerSnapType, tpqn.value); }); -// シーケンサグリッド -const gridCellTicks = snapTicks; // ひとまずスナップ幅=グリッドセル幅 -const gridCellWidth = computed(() => { - return tickToBaseX(gridCellTicks.value, tpqn.value) * zoomX.value; -}); +// グリッド const gridCellBaseHeight = getKeyBaseHeight(); -const gridCellHeight = computed(() => { - return gridCellBaseHeight * zoomY.value; -}); -const numOfMeasures = computed(() => { - // NOTE: 最低長: 仮32小節...スコア長(曲長さ)が決まっていないため、無限スクロール化する or 最後尾に足した場合は伸びるようにするなど? - const minNumOfMeasures = 32; - // NOTE: いったん最後尾に足した場合は伸びるようにする - return Math.max( - minNumOfMeasures, - getNumOfMeasures( - notes.value, - tempos.value, - timeSignatures.value, - tpqn.value, - ) + 1, - ); -}); -const beatsPerMeasure = computed(() => { - return timeSignatures.value[0].beats; -}); -const beatWidth = computed(() => { - const beatType = timeSignatures.value[0].beatType; - const wholeNoteDuration = tpqn.value * 4; - const beatTicks = wholeNoteDuration / beatType; - return tickToBaseX(beatTicks, tpqn.value) * zoomX.value; -}); -const gridWidth = computed(() => { - // TODO: 複数拍子に対応する - const beats = timeSignatures.value[0].beats; - const beatType = timeSignatures.value[0].beatType; - const measureDuration = getMeasureDuration(beats, beatType, tpqn.value); - const numOfGridColumns = - Math.round(measureDuration / gridCellTicks.value) * numOfMeasures.value; - return gridCellWidth.value * numOfGridColumns; -}); const gridHeight = computed(() => { - return gridCellHeight.value * keyInfos.length; + return gridCellBaseHeight * zoomY.value * keyInfos.length; +}); + +// 小節の数 +const numMeasures = computed(() => { + return store.getters.SEQUENCER_NUM_MEASURES; }); // スクロール位置 @@ -428,7 +319,7 @@ const onNoteLyricBlur = () => { // プレビュー // FIXME: 関連する値を1つのobjectにまとめる const nowPreviewing = ref(false); -let previewMode: PreviewMode = "ADD"; +let previewMode: PreviewMode = "ADD_NOTE"; let previewRequestId = 0; let previewStartEditTarget: SequencerEditTarget = "NOTE"; let executePreviewProcess = false; @@ -740,16 +631,16 @@ const previewErasePitch = () => { const preview = () => { if (executePreviewProcess) { - if (previewMode === "ADD") { + if (previewMode === "ADD_NOTE") { previewAdd(); } - if (previewMode === "MOVE") { + if (previewMode === "MOVE_NOTE") { previewMove(); } - if (previewMode === "RESIZE_RIGHT") { + if (previewMode === "RESIZE_NOTE_RIGHT") { previewResizeRight(); } - if (previewMode === "RESIZE_LEFT") { + if (previewMode === "RESIZE_NOTE_LEFT") { previewResizeLeft(); } if (previewMode === "DRAW_PITCH") { @@ -809,7 +700,7 @@ const startPreview = (event: MouseEvent, mode: PreviewMode, note?: Note) => { const guideLineTicks = Math.round(cursorTicks / snapTicks.value - 0.25) * snapTicks.value; const copiedNotes: Note[] = []; - if (mode === "ADD") { + if (mode === "ADD_NOTE") { if (cursorNoteNumber < 0) { return; } @@ -857,7 +748,7 @@ const startPreview = (event: MouseEvent, mode: PreviewMode, note?: Note) => { dragStartNoteNumber = cursorNoteNumber; dragStartGuideLineTicks = guideLineTicks; draggingNoteId = note.id; - edited = mode === "ADD"; + edited = mode === "ADD_NOTE"; copiedNotesForPreview.clear(); for (const copiedNote of copiedNotes) { copiedNotesForPreview.set(copiedNote.id, copiedNote); @@ -905,7 +796,7 @@ const endPreview = () => { // 編集ターゲットがノートのときにプレビューを開始した場合の処理 if (edited) { - if (previewMode === "ADD") { + if (previewMode === "ADD_NOTE") { store.dispatch("COMMAND_ADD_NOTES", { notes: previewNotes.value }); store.dispatch("SELECT_NOTES", { noteIds: previewNotes.value.map((value) => value.id), @@ -968,7 +859,7 @@ const onNoteBarMouseDown = (event: MouseEvent, note: Note) => { } if (mouseButton === "LEFT_BUTTON") { - startPreview(event, "MOVE", note); + startPreview(event, "MOVE_NOTE", note); } else if (!state.selectedNoteIds.has(note.id)) { selectOnlyThis(note); } @@ -985,7 +876,7 @@ const onNoteLeftEdgeMouseDown = (event: MouseEvent, note: Note) => { } if (mouseButton === "LEFT_BUTTON") { - startPreview(event, "RESIZE_LEFT", note); + startPreview(event, "RESIZE_NOTE_LEFT", note); } else if (!state.selectedNoteIds.has(note.id)) { selectOnlyThis(note); } @@ -1002,7 +893,7 @@ const onNoteRightEdgeMouseDown = (event: MouseEvent, note: Note) => { } if (mouseButton === "LEFT_BUTTON") { - startPreview(event, "RESIZE_RIGHT", note); + startPreview(event, "RESIZE_NOTE_RIGHT", note); } else if (!state.selectedNoteIds.has(note.id)) { selectOnlyThis(note); } @@ -1041,7 +932,7 @@ const onMouseDown = (event: MouseEvent) => { rectSelectStartX.value = cursorX.value; rectSelectStartY.value = cursorY.value; } else { - startPreview(event, "ADD"); + startPreview(event, "ADD_NOTE"); } } else { store.dispatch("DESELECT_ALL_NOTES"); @@ -1337,6 +1228,7 @@ const onWheel = (event: WheelEvent) => { newZoomX = Math.max(ZOOM_X_MIN, newZoomX); const scrollLeft = sequencerBodyElement.scrollLeft; const scrollTop = sequencerBodyElement.scrollTop; + guideLineX.value = 0; // 補助線がはみ出さないように位置を一旦0にする store.dispatch("SET_ZOOM_X", { zoomX: newZoomX }).then(() => { const cursorBaseX = (scrollLeft + cursorX.value) / oldZoomX; @@ -1415,7 +1307,7 @@ onActivated(() => { yToScroll = scrollY.value; } // 実際にスクロールする - nextTick().then(() => { + nextTick(() => { sequencerBodyElement.scrollTo(xToScroll, yToScroll); }); }); @@ -1610,44 +1502,6 @@ const contextMenuData = ref([ } } -.sequencer-grid { - display: block; - pointer-events: none; -} - -.sequencer-grid-cell { - display: block; - stroke: rgba(colors.$sequencer-sub-divider-rgb, 0.3); - stroke-width: 1; -} - -.sequencer-grid-octave-cell { - stroke: colors.$sequencer-main-divider; -} - -.sequencer-grid-octave-line { - backface-visibility: hidden; - stroke: colors.$sequencer-main-divider; -} - -.sequencer-grid-cell-white { - fill: colors.$sequencer-whitekey-cell; -} - -.sequencer-grid-cell-black { - fill: colors.$sequencer-blackkey-cell; -} - -.sequencer-grid-measure-line { - backface-visibility: hidden; - stroke: colors.$sequencer-main-divider; -} - -.sequencer-grid-beat-line { - backface-visibility: hidden; - stroke: colors.$sequencer-sub-divider; -} - .sequencer-guideline { position: absolute; top: 0; diff --git a/src/components/Sing/SequencerGrid.vue b/src/components/Sing/SequencerGrid.vue new file mode 100644 index 0000000000..bbfafd1b1e --- /dev/null +++ b/src/components/Sing/SequencerGrid.vue @@ -0,0 +1,158 @@ + + + + + diff --git a/src/components/Sing/SequencerRuler.vue b/src/components/Sing/SequencerRuler.vue index 276b940119..48540addb8 100644 --- a/src/components/Sing/SequencerRuler.vue +++ b/src/components/Sing/SequencerRuler.vue @@ -59,11 +59,11 @@ import { baseXToTick, tickToBaseX } from "@/sing/viewHelper"; const props = withDefaults( defineProps<{ offset: number; - numOfMeasures: number; + numMeasures: number; }>(), { offset: 0, - numOfMeasures: 32, + numMeasures: 32, }, ); const store = useStore(); @@ -91,7 +91,7 @@ const endTicks = computed(() => { return ( lastTsPosition + getMeasureDuration(lastTs.beats, lastTs.beatType, tpqn.value) * - (props.numOfMeasures - lastTs.measureNumber + 1) + (props.numMeasures - lastTs.measureNumber + 1) ); }); const width = computed(() => { diff --git a/src/sing/domain.ts b/src/sing/domain.ts index 5eed5a7f61..b9e2818410 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -174,8 +174,8 @@ export function getTimeSignaturePositions( const tsPosition = tsPositions[i]; const nextTs = timeSignatures[i + 1]; const measureDuration = getMeasureDuration(ts.beats, ts.beatType, tpqn); - const numOfMeasures = nextTs.measureNumber - ts.measureNumber; - const nextTsPosition = tsPosition + measureDuration * numOfMeasures; + const numMeasures = nextTs.measureNumber - ts.measureNumber; + const nextTsPosition = tsPosition + measureDuration * numMeasures; tsPositions.push(nextTsPosition); } return tsPositions; @@ -207,7 +207,7 @@ export function getMeasureDuration( return (wholeNoteDuration / beatType) * beats; } -export function getNumOfMeasures( +export function getNumMeasures( notes: Note[], tempos: Tempo[], timeSignatures: TimeSignature[], @@ -281,6 +281,7 @@ export const DEFAULT_TPQN = 480; export const DEFAULT_BPM = 120; export const DEFAULT_BEATS = 4; export const DEFAULT_BEAT_TYPE = 4; +export const SEQUENCER_MIN_NUM_MEASURES = 32; // マルチエンジン対応のために将来的に廃止予定で、利用は非推奨 export const DEPRECATED_DEFAULT_EDIT_FRAME_RATE = 93.75; diff --git a/src/store/singing.ts b/src/store/singing.ts index f16b5ba369..c5a4c15883 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -70,6 +70,8 @@ import { createDefaultTempo, createDefaultTimeSignature, isValidNotes, + SEQUENCER_MIN_NUM_MEASURES, + getNumMeasures, } from "@/sing/domain"; import { FrequentlyUpdatedState, @@ -716,6 +718,22 @@ export const singingStore = createPartialStore({ }, }, + SEQUENCER_NUM_MEASURES: { + getter(state) { + // NOTE: スコア長(曲長さ)が決まっていないため、無限スクロール化する or 最後尾に足した場合は伸びるようにするなど? + // NOTE: いったん最後尾に足した場合は伸びるようにする + return Math.max( + SEQUENCER_MIN_NUM_MEASURES, + getNumMeasures( + state.tracks[selectedTrackIndex].notes, + state.tempos, + state.timeSignatures, + state.tpqn, + ) + 1, + ); + }, + }, + SET_ZOOM_X: { mutation(state, { zoomX }: { zoomX: number }) { state.sequencerZoomX = zoomX; diff --git a/src/store/type.ts b/src/store/type.ts index d134eef477..a5873a13b5 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -813,7 +813,7 @@ export type PhraseSourceHash = z.infer; export type SequencerEditTarget = "NOTE" | "PITCH"; export type SingingStoreState = { - tpqn: number; + tpqn: number; // Ticks Per Quarter Note tempos: Tempo[]; timeSignatures: TimeSignature[]; tracks: Track[]; @@ -992,6 +992,10 @@ export type SingingStoreTypes = { action(payload: { snapType: number }): void; }; + SEQUENCER_NUM_MEASURES: { + getter: number; + }; + SET_ZOOM_X: { mutation: { zoomX: number }; action(payload: { zoomX: number }): void; From ef3477df7d7a12b8abebcec8de3f9088b4d8511a Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Wed, 22 May 2024 02:26:26 +0900 Subject: [PATCH 05/31] =?UTF-8?q?[refactor]=20project=E3=81=AE=E3=83=9E?= =?UTF-8?q?=E3=82=A4=E3=82=B0=E3=83=AC=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E3=82=92=E9=96=A2=E6=95=B0=E5=88=87=E3=82=8A=E5=87=BA=E3=81=97?= =?UTF-8?q?=20(#1967)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * projectのschemaをdomainディレクトリ以下に移動 * 移動し忘れ * noteSchema移動し忘れ * たぶんこう * CharacterInfo差し戻し * anyは仕方ない気がする * import ミス * ミス * ProjectFileFormatError型を用意 * causeいらない --- _typos.toml | 2 +- src/domain/project/index.ts | 302 +++++++++++++++++++++++++++++++++ src/store/project.ts | 326 +++--------------------------------- 3 files changed, 324 insertions(+), 306 deletions(-) create mode 100644 src/domain/project/index.ts diff --git a/_typos.toml b/_typos.toml index ebfe9704b1..42cd7949ad 100644 --- a/_typos.toml +++ b/_typos.toml @@ -8,4 +8,4 @@ ba = "ba" # 7zコマンドの-baオプション commitish = "commitish" # softprops/action-gh-releaseのオプションの1つ [files] -extend-exclude = ["package-lock.json", "src/store/project.ts", "*.svg"] +extend-exclude = ["package-lock.json", "src/domain/project/index.ts", "*.svg"] diff --git a/src/domain/project/index.ts b/src/domain/project/index.ts new file mode 100644 index 0000000000..2664926612 --- /dev/null +++ b/src/domain/project/index.ts @@ -0,0 +1,302 @@ +/** + * プロジェクトファイル関連のコード + */ + +import semver from "semver"; + +import { LatestProjectType, projectSchema } from "./schema"; +import { AccentPhrase } from "@/openapi"; +import { CharacterInfo, EngineId, StyleId } from "@/type/preload"; +import { + DEFAULT_BEAT_TYPE, + DEFAULT_BEATS, + DEFAULT_BPM, + DEFAULT_TPQN, +} from "@/sing/domain"; + +const DEFAULT_SAMPLING_RATE = 24000; + +/** + * プロジェクトファイルのフォーマットエラー + * FIXME: Result型にする + */ +export class ProjectFileFormatError extends Error { + constructor(message: string) { + super(message); + this.name = "ProjectFileFormatError"; + } +} + +const validateTalkProject = (talkProject: LatestProjectType["talk"]) => { + if ( + !talkProject.audioKeys.every( + (audioKey) => audioKey in talkProject.audioItems, + ) + ) { + throw new Error( + "Every audioKey in audioKeys should be a key of audioItems", + ); + } + if ( + !talkProject.audioKeys.every( + (audioKey) => talkProject.audioItems[audioKey]?.voice != undefined, + ) + ) { + throw new Error('Every audioItem should have a "voice" attribute.'); + } + if ( + !talkProject.audioKeys.every( + (audioKey) => + talkProject.audioItems[audioKey]?.voice.engineId != undefined, + ) + ) { + throw new Error('Every voice should have a "engineId" attribute.'); + } + // FIXME: assert engineId is registered + if ( + !talkProject.audioKeys.every( + (audioKey) => + talkProject.audioItems[audioKey]?.voice.speakerId != undefined, + ) + ) { + throw new Error('Every voice should have a "speakerId" attribute.'); + } + if ( + !talkProject.audioKeys.every( + (audioKey) => + talkProject.audioItems[audioKey]?.voice.styleId != undefined, + ) + ) { + throw new Error('Every voice should have a "styleId" attribute.'); + } +}; + +/** + * プロジェクトファイルのマイグレーション + */ +export const migrateProjectFileObject = async ( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + projectData: any, + DI: { + fetchMoraData: (payload: { + accentPhrases: AccentPhrase[]; + engineId: EngineId; + styleId: StyleId; + }) => Promise; + characterInfos: CharacterInfo[]; + }, +) => { + const { fetchMoraData, characterInfos } = DI; + + // appVersion Validation check + if ( + !("appVersion" in projectData && typeof projectData.appVersion === "string") + ) { + throw new ProjectFileFormatError( + "The appVersion of the project file should be string", + ); + } + const projectAppVersion: string = projectData.appVersion; + if (!semver.valid(projectAppVersion)) { + throw new ProjectFileFormatError( + `The app version of the project file "${projectAppVersion}" is invalid. The app version should be a string in semver format.`, + ); + } + + const semverSatisfiesOptions: semver.Options = { + includePrerelease: true, + }; + + // Migration + const engineId = EngineId("074fc39e-678b-4c13-8916-ffca8d505d1d"); + + if (semver.satisfies(projectAppVersion, "<0.4", semverSatisfiesOptions)) { + for (const audioItemsKey in projectData.audioItems) { + if ("charactorIndex" in projectData.audioItems[audioItemsKey]) { + projectData.audioItems[audioItemsKey].characterIndex = + projectData.audioItems[audioItemsKey].charactorIndex; + delete projectData.audioItems[audioItemsKey].charactorIndex; + } + } + for (const audioItemsKey in projectData.audioItems) { + if (projectData.audioItems[audioItemsKey].query != null) { + projectData.audioItems[audioItemsKey].query.volumeScale = 1; + projectData.audioItems[audioItemsKey].query.prePhonemeLength = 0.1; + projectData.audioItems[audioItemsKey].query.postPhonemeLength = 0.1; + projectData.audioItems[audioItemsKey].query.outputSamplingRate = + DEFAULT_SAMPLING_RATE; + } + } + } + + if (semver.satisfies(projectAppVersion, "<0.5", semverSatisfiesOptions)) { + for (const audioItemsKey in projectData.audioItems) { + const audioItem = projectData.audioItems[audioItemsKey]; + if (audioItem.query != null) { + audioItem.query.outputStereo = false; + for (const accentPhrase of audioItem.query.accentPhrases) { + if (accentPhrase.pauseMora) { + accentPhrase.pauseMora.vowelLength = 0; + } + for (const mora of accentPhrase.moras) { + if (mora.consonant) { + mora.consonantLength = 0; + } + mora.vowelLength = 0; + } + } + + // set phoneme length + // 0.7 未満のプロジェクトファイルは styleId ではなく characterIndex なので、ここだけ characterIndex とした + if (audioItem.characterIndex == undefined) + throw new Error("audioItem.characterIndex === undefined"); + await fetchMoraData({ + accentPhrases: audioItem.query.accentPhrases, + engineId, + styleId: audioItem.characterIndex, + }).then((accentPhrases: AccentPhrase[]) => { + accentPhrases.forEach((newAccentPhrase, i) => { + const oldAccentPhrase = audioItem.query.accentPhrases[i]; + if (newAccentPhrase.pauseMora) { + oldAccentPhrase.pauseMora.vowelLength = + newAccentPhrase.pauseMora.vowelLength; + } + newAccentPhrase.moras.forEach((mora, j) => { + if (mora.consonant) { + oldAccentPhrase.moras[j].consonantLength = mora.consonantLength; + } + oldAccentPhrase.moras[j].vowelLength = mora.vowelLength; + }); + }); + }); + } + } + } + + if (semver.satisfies(projectAppVersion, "<0.7", semverSatisfiesOptions)) { + for (const audioItemsKey in projectData.audioItems) { + const audioItem = projectData.audioItems[audioItemsKey]; + if (audioItem.characterIndex != null) { + if (audioItem.characterIndex == 0) { + // 四国めたん 0 -> 四国めたん(あまあま) 0 + audioItem.speaker = 0; + } + if (audioItem.characterIndex == 1) { + // ずんだもん 1 -> ずんだもん(あまあま) 1 + audioItem.speaker = 1; + } + delete audioItem.characterIndex; + } + } + } + + if (semver.satisfies(projectAppVersion, "<0.8", semverSatisfiesOptions)) { + for (const audioItemsKey in projectData.audioItems) { + const audioItem = projectData.audioItems[audioItemsKey]; + if (audioItem.speaker != null) { + audioItem.styleId = audioItem.speaker; + delete audioItem.speaker; + } + } + } + + if (semver.satisfies(projectAppVersion, "<0.14", semverSatisfiesOptions)) { + for (const audioItemsKey in projectData.audioItems) { + const audioItem = projectData.audioItems[audioItemsKey]; + if (audioItem.engineId == undefined) { + audioItem.engineId = engineId; + } + } + } + + if (semver.satisfies(projectAppVersion, "<0.15", semverSatisfiesOptions)) { + for (const audioItemsKey in projectData.audioItems) { + const audioItem = projectData.audioItems[audioItemsKey]; + if (audioItem.voice == undefined) { + const oldEngineId = audioItem.engineId; + const oldStyleId = audioItem.styleId; + const chracterinfo = characterInfos.find((characterInfo) => + characterInfo.metas.styles.some( + (styeleinfo) => + styeleinfo.engineId === audioItem.engineId && + styeleinfo.styleId === audioItem.styleId, + ), + ); + if (chracterinfo == undefined) + throw new Error( + `chracterinfo == undefined: ${oldEngineId}, ${oldStyleId}`, + ); + const speakerId = chracterinfo.metas.speakerUuid; + audioItem.voice = { + engineId: oldEngineId, + speakerId, + styleId: oldStyleId, + }; + + delete audioItem.engineId; + delete audioItem.styleId; + } + } + } + + if (semver.satisfies(projectAppVersion, "<0.17", semverSatisfiesOptions)) { + // 0.17 未満のプロジェクトファイルはトークの情報のみ + // なので全情報(audioKeys/audioItems)をtalkに移動する + projectData.talk = { + audioKeys: projectData.audioKeys, + audioItems: projectData.audioItems, + }; + + // ソングの情報を初期化 + // generateSingingStoreInitialScoreが今後変わることがあるかもしれないので、 + // 0.17時点のスコア情報を直接書く + projectData.song = { + tpqn: DEFAULT_TPQN, + tempos: [ + { + position: 0, + bpm: DEFAULT_BPM, + }, + ], + timeSignatures: [ + { + measureNumber: 1, + beats: DEFAULT_BEATS, + beatType: DEFAULT_BEAT_TYPE, + }, + ], + tracks: [ + { + singer: undefined, + keyRangeAdjustment: 0, + notes: [], + }, + ], + }; + + delete projectData.audioKeys; + delete projectData.audioItems; + } + + if (semver.satisfies(projectAppVersion, "<0.17.1", semverSatisfiesOptions)) { + // 声量調整値の追加 + for (const track of projectData.song.tracks) { + track.volumeRangeAdjustment = 0; + } + } + + if (semver.satisfies(projectAppVersion, "<0.19.0", semverSatisfiesOptions)) { + // ピッチ編集値の追加 + for (const track of projectData.song.tracks) { + track.pitchEditData = []; + } + } + + // Validation check + // トークはvalidateTalkProjectで検証する + // ソングはSET_SCOREの中の`isValidScore`関数で検証される + const parsedProjectData = projectSchema.parse(projectData); + validateTalkProject(parsedProjectData.talk); + + return parsedProjectData; +}; diff --git a/src/store/project.ts b/src/store/project.ts index b8fd91512d..450d263393 100755 --- a/src/store/project.ts +++ b/src/store/project.ts @@ -1,4 +1,3 @@ -import semver from "semver"; import { getBaseName } from "./utility"; import { createPartialStore, Dispatch } from "./vuex"; import { createUILockAction } from "@/store/ui"; @@ -8,69 +7,23 @@ import { ProjectStoreState, ProjectStoreTypes, } from "@/store/type"; -import { AccentPhrase } from "@/openapi"; -import { EngineId } from "@/type/preload"; + import { getValueOrThrow, ResultError } from "@/type/result"; -import { LatestProjectType, projectSchema } from "@/domain/project/schema"; +import { LatestProjectType } from "@/domain/project/schema"; +import { + migrateProjectFileObject, + ProjectFileFormatError, +} from "@/domain/project"; import { createDefaultTempo, createDefaultTimeSignature, - DEFAULT_BEAT_TYPE, - DEFAULT_BEATS, - DEFAULT_BPM, DEFAULT_TPQN, } from "@/sing/domain"; -const DEFAULT_SAMPLING_RATE = 24000; - export const projectStoreState: ProjectStoreState = { savedLastCommandUnixMillisec: null, }; -const validateTalkProject = (talkProject: LatestProjectType["talk"]) => { - if ( - !talkProject.audioKeys.every( - (audioKey) => audioKey in talkProject.audioItems, - ) - ) { - throw new Error( - "Every audioKey in audioKeys should be a key of audioItems", - ); - } - if ( - !talkProject.audioKeys.every( - (audioKey) => talkProject.audioItems[audioKey]?.voice != undefined, - ) - ) { - throw new Error('Every audioItem should have a "voice" attribute.'); - } - if ( - !talkProject.audioKeys.every( - (audioKey) => - talkProject.audioItems[audioKey]?.voice.engineId != undefined, - ) - ) { - throw new Error('Every voice should have a "engineId" attribute.'); - } - // FIXME: assert engineId is registered - if ( - !talkProject.audioKeys.every( - (audioKey) => - talkProject.audioItems[audioKey]?.voice.speakerId != undefined, - ) - ) { - throw new Error('Every voice should have a "speakerId" attribute.'); - } - if ( - !talkProject.audioKeys.every( - (audioKey) => - talkProject.audioItems[audioKey]?.voice.styleId != undefined, - ) - ) { - throw new Error('Every voice should have a "styleId" attribute.'); - } -}; - const applyTalkProjectToStore = async ( dispatch: Dispatch, talkProject: LatestProjectType["talk"], @@ -198,8 +151,6 @@ export const projectStore = createPartialStore({ filePath = ret[0]; } - const projectFileErrorMsg = `VOICEVOX Project file "${filePath}" is a invalid file.`; - let buf: ArrayBuffer; try { buf = await window.backend @@ -209,258 +160,23 @@ export const projectStore = createPartialStore({ await context.dispatch("APPEND_RECENTLY_USED_PROJECT", { filePath, }); + const text = new TextDecoder("utf-8").decode(buf).trim(); const projectData = JSON.parse(text); - // appVersion Validation check - if ( - !( - "appVersion" in projectData && - typeof projectData.appVersion === "string" - ) - ) { - throw new Error( - projectFileErrorMsg + - " The appVersion of the project file should be string", - ); - } - const projectAppVersion: string = projectData.appVersion; - if (!semver.valid(projectAppVersion)) { - throw new Error( - projectFileErrorMsg + - ` The app version of the project file "${projectAppVersion}" is invalid. The app version should be a string in semver format.`, - ); - } - - const semverSatisfiesOptions: semver.Options = { - includePrerelease: true, - }; - - // Migration - const engineId = EngineId("074fc39e-678b-4c13-8916-ffca8d505d1d"); - - if ( - semver.satisfies(projectAppVersion, "<0.4", semverSatisfiesOptions) - ) { - for (const audioItemsKey in projectData.audioItems) { - if ("charactorIndex" in projectData.audioItems[audioItemsKey]) { - projectData.audioItems[audioItemsKey].characterIndex = - projectData.audioItems[audioItemsKey].charactorIndex; - delete projectData.audioItems[audioItemsKey].charactorIndex; - } - } - for (const audioItemsKey in projectData.audioItems) { - if (projectData.audioItems[audioItemsKey].query != null) { - projectData.audioItems[audioItemsKey].query.volumeScale = 1; - projectData.audioItems[audioItemsKey].query.prePhonemeLength = - 0.1; - projectData.audioItems[audioItemsKey].query.postPhonemeLength = - 0.1; - projectData.audioItems[audioItemsKey].query.outputSamplingRate = - DEFAULT_SAMPLING_RATE; - } - } - } - - if ( - semver.satisfies(projectAppVersion, "<0.5", semverSatisfiesOptions) - ) { - for (const audioItemsKey in projectData.audioItems) { - const audioItem = projectData.audioItems[audioItemsKey]; - if (audioItem.query != null) { - audioItem.query.outputStereo = false; - for (const accentPhrase of audioItem.query.accentPhrases) { - if (accentPhrase.pauseMora) { - accentPhrase.pauseMora.vowelLength = 0; - } - for (const mora of accentPhrase.moras) { - if (mora.consonant) { - mora.consonantLength = 0; - } - mora.vowelLength = 0; - } - } - - // set phoneme length - // 0.7 未満のプロジェクトファイルは styleId ではなく characterIndex なので、ここだけ characterIndex とした - if (audioItem.characterIndex == undefined) - throw new Error("audioItem.characterIndex === undefined"); - await context - .dispatch("FETCH_MORA_DATA", { - accentPhrases: audioItem.query.accentPhrases, - engineId, - styleId: audioItem.characterIndex, - }) - .then((accentPhrases: AccentPhrase[]) => { - accentPhrases.forEach((newAccentPhrase, i) => { - const oldAccentPhrase = audioItem.query.accentPhrases[i]; - if (newAccentPhrase.pauseMora) { - oldAccentPhrase.pauseMora.vowelLength = - newAccentPhrase.pauseMora.vowelLength; - } - newAccentPhrase.moras.forEach((mora, j) => { - if (mora.consonant) { - oldAccentPhrase.moras[j].consonantLength = - mora.consonantLength; - } - oldAccentPhrase.moras[j].vowelLength = mora.vowelLength; - }); - }); - }); - } - } - } - - if ( - semver.satisfies(projectAppVersion, "<0.7", semverSatisfiesOptions) - ) { - for (const audioItemsKey in projectData.audioItems) { - const audioItem = projectData.audioItems[audioItemsKey]; - if (audioItem.characterIndex != null) { - if (audioItem.characterIndex == 0) { - // 四国めたん 0 -> 四国めたん(あまあま) 0 - audioItem.speaker = 0; - } - if (audioItem.characterIndex == 1) { - // ずんだもん 1 -> ずんだもん(あまあま) 1 - audioItem.speaker = 1; - } - delete audioItem.characterIndex; - } - } - } - - if ( - semver.satisfies(projectAppVersion, "<0.8", semverSatisfiesOptions) - ) { - for (const audioItemsKey in projectData.audioItems) { - const audioItem = projectData.audioItems[audioItemsKey]; - if (audioItem.speaker != null) { - audioItem.styleId = audioItem.speaker; - delete audioItem.speaker; - } - } - } - - if ( - semver.satisfies(projectAppVersion, "<0.14", semverSatisfiesOptions) - ) { - for (const audioItemsKey in projectData.audioItems) { - const audioItem = projectData.audioItems[audioItemsKey]; - if (audioItem.engineId == undefined) { - audioItem.engineId = engineId; - } - } - } - - if ( - semver.satisfies(projectAppVersion, "<0.15", semverSatisfiesOptions) - ) { - const characterInfos = - context.getters.USER_ORDERED_CHARACTER_INFOS("talk"); - if (characterInfos == undefined) - throw new Error("characterInfos == undefined"); - for (const audioItemsKey in projectData.audioItems) { - const audioItem = projectData.audioItems[audioItemsKey]; - if (audioItem.voice == undefined) { - const oldEngineId = audioItem.engineId; - const oldStyleId = audioItem.styleId; - const chracterinfo = characterInfos.find((characterInfo) => - characterInfo.metas.styles.some( - (styeleinfo) => - styeleinfo.engineId === audioItem.engineId && - styeleinfo.styleId === audioItem.styleId, - ), - ); - if (chracterinfo == undefined) - throw new Error( - `chracterinfo == undefined: ${oldEngineId}, ${oldStyleId}`, - ); - const speakerId = chracterinfo.metas.speakerUuid; - audioItem.voice = { - engineId: oldEngineId, - speakerId, - styleId: oldStyleId, - }; - - delete audioItem.engineId; - delete audioItem.styleId; - } - } - } - - if ( - semver.satisfies(projectAppVersion, "<0.17", semverSatisfiesOptions) - ) { - // 0.17 未満のプロジェクトファイルはトークの情報のみ - // なので全情報(audioKeys/audioItems)をtalkに移動する - projectData.talk = { - audioKeys: projectData.audioKeys, - audioItems: projectData.audioItems, - }; - - // ソングの情報を初期化 - // generateSingingStoreInitialScoreが今後変わることがあるかもしれないので、 - // 0.17時点のスコア情報を直接書く - projectData.song = { - tpqn: DEFAULT_TPQN, - tempos: [ - { - position: 0, - bpm: DEFAULT_BPM, - }, - ], - timeSignatures: [ - { - measureNumber: 1, - beats: DEFAULT_BEATS, - beatType: DEFAULT_BEAT_TYPE, - }, - ], - tracks: [ - { - singer: undefined, - keyRangeAdjustment: 0, - notes: [], - }, - ], - }; - - delete projectData.audioKeys; - delete projectData.audioItems; - } - - if ( - semver.satisfies( - projectAppVersion, - "<0.17.1", - semverSatisfiesOptions, - ) - ) { - // 声量調整値の追加 - for (const track of projectData.song.tracks) { - track.volumeRangeAdjustment = 0; - } - } - - if ( - semver.satisfies( - projectAppVersion, - "<0.19.0", - semverSatisfiesOptions, - ) - ) { - // ピッチ編集値の追加 - for (const track of projectData.song.tracks) { - track.pitchEditData = []; - } - } - - // Validation check - // トークはvalidateTalkProjectで検証する - // ソングはSET_SCOREの中の`isValidScore`関数で検証される - const parsedProjectData = projectSchema.parse(projectData); - validateTalkProject(parsedProjectData.talk); + const characterInfos = + context.getters.USER_ORDERED_CHARACTER_INFOS("talk"); + if (characterInfos == undefined) + throw new Error("characterInfos == undefined"); + + const parsedProjectData = await migrateProjectFileObject( + projectData, + { + fetchMoraData: (payload) => + context.dispatch("FETCH_MORA_DATA", payload), + characterInfos, + }, + ); if (confirm !== false && context.getters.IS_EDITED) { const result = await context.dispatch( @@ -495,7 +211,7 @@ export const projectStore = createPartialStore({ if (!(err instanceof Error)) return "エラーが発生しました。"; if (err instanceof ResultError && err.code === "ENOENT") return "プロジェクトファイルが見つかりませんでした。ファイルが移動、または削除された可能性があります。"; - if (err.message.startsWith(projectFileErrorMsg)) + if (err instanceof ProjectFileFormatError) return "ファイルフォーマットが正しくありません。"; return err.message; })(); From 81158163171a508e6e1e2cf6a4b44e4bac426624 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Wed, 22 May 2024 03:48:41 +0900 Subject: [PATCH 06/31] =?UTF-8?q?refactor:=20=E3=82=BD=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=81=AE=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB=E3=83=88=E3=82=B9?= =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=AB=E3=81=AF=E4=B8=80=E6=97=A6=E5=AE=9F?= =?UTF-8?q?=E8=A3=85=E3=81=97=E3=81=AA=E3=81=84=E6=96=B9=E9=87=9D=20(#2077?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ソングのデフォルトスタイルは一旦実装しない方針 --- src/components/Dialog/AllDialog.vue | 1 - src/components/Sing/CharacterMenuButton/MenuButton.vue | 1 - src/store/singing.ts | 3 --- 3 files changed, 5 deletions(-) diff --git a/src/components/Dialog/AllDialog.vue b/src/components/Dialog/AllDialog.vue index 981cae021e..052434d17e 100644 --- a/src/components/Dialog/AllDialog.vue +++ b/src/components/Dialog/AllDialog.vue @@ -100,7 +100,6 @@ const isCharacterOrderDialogOpenComputed = computed({ }), }); -// TODO: デフォルトスタイル選択(ソング)の実装 // デフォルトスタイル選択(トーク) const orderedTalkCharacterInfos = computed(() => { return filterCharacterInfosByStyleType( diff --git a/src/components/Sing/CharacterMenuButton/MenuButton.vue b/src/components/Sing/CharacterMenuButton/MenuButton.vue index 1bc67bd94d..69edcc411a 100644 --- a/src/components/Sing/CharacterMenuButton/MenuButton.vue +++ b/src/components/Sing/CharacterMenuButton/MenuButton.vue @@ -200,7 +200,6 @@ const getDefaultStyle = (speakerUuid: string) => { // ここで取得されるcharacterInfoには、ソングエディタ向けのスタイルのみ含まれるので、 // その中の最初のスタイルをソングエディタにおける仮のデフォルトスタイルとする - // TODO: ソングエディタ向けのデフォルトスタイルをどうするか考える const defaultStyleId = characterInfo?.metas.styles[0].styleId; const defaultStyle = characterInfo?.metas.styles.find( diff --git a/src/store/singing.ts b/src/store/singing.ts index c5a4c15883..cbba5ead3d 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -232,11 +232,8 @@ export const singingStore = createPartialStore({ const engineId = singer?.engineId ?? state.engineIds[0]; - // 最初のスタイルをソングエディタにおける仮のデフォルトスタイルとする - // TODO: ソングエディタ向けのデフォルトスタイルをどうするか考える const defaultStyleId = userOrderedCharacterInfos[0].metas.styles[0].styleId; - const styleId = singer?.styleId ?? defaultStyleId; dispatch("SETUP_SINGER", { singer: { engineId, styleId } }); From e1dac6e526fc9d5d221199550ba8138ecc179d20 Mon Sep 17 00:00:00 2001 From: honey32 Date: Sat, 25 May 2024 00:51:51 +0900 Subject: [PATCH 07/31] =?UTF-8?q?=E3=80=90=E8=AA=AD=E3=81=BF=E6=96=B9&?= =?UTF-8?q?=E3=82=A2=E3=82=AF=E3=82=BB=E3=83=B3=E3=83=88=E8=BE=9E=E6=9B=B8?= =?UTF-8?q?=20=E5=8D=98=E8=AA=9E=E4=B8=80=E8=A6=A7=E3=80=91=E7=99=BB?= =?UTF-8?q?=E9=8C=B2=E5=8D=98=E8=AA=9E=E3=81=AE=E7=B7=A8=E9=9B=86=E3=83=BB?= =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE=E5=B8=B8?= =?UTF-8?q?=E6=99=82=E8=A1=A8=E7=A4=BA=E3=82=92=E3=82=84=E3=82=81=E3=81=A6?= =?UTF-8?q?OOUI=E7=9A=84=E3=81=AB=20(#2072)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 登録単語の編集・削除ボタンの常時表示をやめてOOUI的に * fix: ツールチップをQTooltipに直す * fix: 一応削除ボタンのclickイベントハンドラに `.stop` を追加 * test: 辞書ダイアログのe2eテストを更新 * feat: 選択時だけでなくホバー時にもボタンを表示する * fix: 辞書アイテム、編集を左に削除を右に * fix: 削除ボタンのスタイルをAudioCell のそれに合わせる * refactor: ホバー状態を単純な文字列で管理 --- .../Dialog/DictionaryManageDialog.vue | 65 ++++++++++++------- ...3\202\242\343\203\255\343\202\260.spec.ts" | 6 +- 2 files changed, 44 insertions(+), 27 deletions(-) diff --git a/src/components/Dialog/DictionaryManageDialog.vue b/src/components/Dialog/DictionaryManageDialog.vue index 79971b92eb..cf273ee621 100644 --- a/src/components/Dialog/DictionaryManageDialog.vue +++ b/src/components/Dialog/DictionaryManageDialog.vue @@ -46,28 +46,12 @@ @click="discardOrNotDialog(cancel)" />
-
単語一覧
-
- 削除 - 編集 +
+ 単語一覧 追加 - {{ + {{ value.surface }} - {{ value.yomi }} + {{ value.yomi }} + + + +
+ + 編集 + + + 削除 + +
@@ -280,6 +294,9 @@ const uiLocked = ref(false); // ダイアログ内でstore.getters.UI_LOCKEDは const nowGenerating = ref(false); const nowPlaying = ref(false); +// word-list の要素のうち、どの要素がホバーされているか +const hoveredKey = ref(undefined); + const loadingDictState = ref("loading"); const userDict = ref>({}); @@ -555,7 +572,6 @@ const saveWord = async () => { await loadingDictProcess(); toInitialState(); }; -const isDeletable = computed(() => !!selectedId.value); const deleteWord = async () => { const result = await store.dispatch("SHOW_WARNING_DIALOG", { title: "登録された単語を削除しますか?", @@ -675,13 +691,14 @@ const toDialogClosedState = () => { .word-list { // menubar-height + toolbar-height + window-border-width + - // 82(title & buttons) + 30(margin 15x2) + // 36(title & buttons) + 30(margin 15x2) height: calc( 100vh - #{vars.$menubar-height + vars.$toolbar-height + - vars.$window-border-width + 82px + 30px} + vars.$window-border-width + 36px + 30px} ); width: 100%; overflow-y: auto; + padding-bottom: 16px; } .active-word { diff --git "a/tests/e2e/browser/\350\276\236\346\233\270\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" "b/tests/e2e/browser/\350\276\236\346\233\270\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" index f3a3a7de7b..41073c9eed 100644 --- "a/tests/e2e/browser/\350\276\236\346\233\270\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" +++ "b/tests/e2e/browser/\350\276\236\346\233\270\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" @@ -114,9 +114,9 @@ test("「設定」→「読み方&アクセント辞書」で「読み方& .click(); await page.waitForTimeout(100); await page - .locator(".word-list-header") - .getByRole("button") - .filter({ hasText: "削除" }) + .getByRole("listitem") + .filter({ hasText: zenkakuRandomString }) + .getByText("delete") .click(); await page.waitForTimeout(100); await getNewestQuasarDialog(page) From 29195c383a1548b95b14fe496325b72eb13b7039 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Sat, 25 May 2024 02:05:52 +0900 Subject: [PATCH 08/31] =?UTF-8?q?ESModule=E3=81=AE=E3=83=91=E3=83=83?= =?UTF-8?q?=E3=82=B1=E3=83=BC=E3=82=B8=E3=82=92=E8=AA=AD=E3=81=BF=E8=BE=BC?= =?UTF-8?q?=E3=82=81=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B?= =?UTF-8?q?=20(#2073)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Migrate: ESModuleに移行 * Revert: vuexの変更をRevert * Fix: __dirnameを追加 * Fix: electronが立ち上がらないのを修正 * Change: type: moduleをやめる * Delete: __dirnameをなくす * Revert: js -> cjsをやめる * Code: コメントを追加 * Revert: package.jsonのパスも戻す * Revert: 色々なコンフィルファイルの拡張子も戻す * Revert: electron-builder.config.jsのも戻す * Revert: js内のも戻す --- package-lock.json | 9 ++++----- package.json | 2 +- src/@types/immer.d.ts | 13 +++++++++++++ src/@types/vuex.d.ts | 8 ++++++++ tsconfig.json | 2 +- 5 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 src/@types/immer.d.ts create mode 100644 src/@types/vuex.d.ts diff --git a/package-lock.json b/package-lock.json index 397cc6c0f1..b9b4e12bf3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "buffer": "6.0.3", "clone-deep": "4.0.1", "dayjs": "1.10.7", - "electron-log": "5.0.0", + "electron-log": "5.1.2", "electron-window-state": "5.0.3", "encoding-japanese": "1.0.30", "fast-array-diff": "1.1.0", @@ -5654,11 +5654,10 @@ } }, "node_modules/electron-log": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.0.0.tgz", - "integrity": "sha512-vB3akupmQvA8jAyNL9rULZtf6WoP8vsabjXsRtiqXS6/D37SwN/4LEyj4JD+9Bv6xoTcx/LrVnsIKEEWdq5ClQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/electron-log/-/electron-log-5.1.2.tgz", + "integrity": "sha512-Cpg4hAZ27yM9wzE77c4TvgzxzavZ+dVltCczParXN+Vb3jocojCSAuSMCVOI9fhFuuOR+iuu3tZLX1cu0y0kgQ==", "engines": { - "electron": ">= 13", "node": ">= 14" } }, diff --git a/package.json b/package.json index f7bf502681..30d8bbf1ed 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "buffer": "6.0.3", "clone-deep": "4.0.1", "dayjs": "1.10.7", - "electron-log": "5.0.0", + "electron-log": "5.1.2", "electron-window-state": "5.0.3", "encoding-japanese": "1.0.30", "fast-array-diff": "1.1.0", diff --git a/src/@types/immer.d.ts b/src/@types/immer.d.ts new file mode 100644 index 0000000000..f339925f1e --- /dev/null +++ b/src/@types/immer.d.ts @@ -0,0 +1,13 @@ +// immerの内部APIの型定義。exportsで指定されていないファイルを参照するために用意したもの。 +declare module "immer/src/plugins/patches" { + export function enablePatches(): void; +} +declare module "immer/src/plugins/mapset" { + export function enableMapSet(): void; +} +declare module "immer/src/utils/plugins" { + import { Patch } from "immer"; + export function getPlugin(name: "Patches"): { + applyPatches_: (state: unknown, patches: Patch[]) => void; + }; +} diff --git a/src/@types/vuex.d.ts b/src/@types/vuex.d.ts new file mode 100644 index 0000000000..c931dbd5ea --- /dev/null +++ b/src/@types/vuex.d.ts @@ -0,0 +1,8 @@ +// vuexのexportsにtypeがないのを回避するWorkaround。 +// https://github.com/vuejs/vuex/issues/2213#issuecomment-1592267216 +declare module "vuex" { + export * from "vuex/types/index.d.ts"; + export * from "vuex/types/helpers.d.ts"; + export * from "vuex/types/logger.d.ts"; + export * from "vuex/types/vue.d.ts"; +} diff --git a/tsconfig.json b/tsconfig.json index 1e4686f201..07ca1940d9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,7 @@ "strict": true, "jsx": "preserve", "importHelpers": true, - "moduleResolution": "node", + "moduleResolution": "bundler", "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, From 024d360b4885fabbd6357196ab1c5cdccda071be Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Sat, 25 May 2024 22:21:53 +0900 Subject: [PATCH 09/31] =?UTF-8?q?[refactor]=20=E6=97=A5=E6=9C=AC=E8=AA=9E?= =?UTF-8?q?=E3=82=92=E5=87=A6=E7=90=86=E3=81=99=E3=82=8B=E3=83=89=E3=83=A1?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=82=92=E5=88=87=E3=82=8B=20(#2091)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * japanese domain * 更新忘れ --- .../Dialog/DictionaryManageDialog.vue | 2 +- src/domain/japanese/index.ts | 42 +++++++++++++++++++ src/sing/domain.ts | 2 +- src/store/audio.ts | 8 ++-- src/store/utility.ts | 36 ---------------- tests/unit/domain/japanese.spec.ts | 30 +++++++++++++ tests/unit/store/utility.spec.ts | 28 ------------- 7 files changed, 79 insertions(+), 69 deletions(-) create mode 100644 src/domain/japanese/index.ts create mode 100644 tests/unit/domain/japanese.spec.ts diff --git a/src/components/Dialog/DictionaryManageDialog.vue b/src/components/Dialog/DictionaryManageDialog.vue index cf273ee621..fe00eb04f2 100644 --- a/src/components/Dialog/DictionaryManageDialog.vue +++ b/src/components/Dialog/DictionaryManageDialog.vue @@ -273,7 +273,7 @@ import { convertHiraToKana, convertLongVowel, createKanaRegex, -} from "@/store/utility"; +} from "@/domain/japanese"; const defaultDictPriority = 5; diff --git a/src/domain/japanese/index.ts b/src/domain/japanese/index.ts new file mode 100644 index 0000000000..e9a90bdfad --- /dev/null +++ b/src/domain/japanese/index.ts @@ -0,0 +1,42 @@ +/** + * 日本語のひらがなやカタカナなどを扱う + */ + +/** 読み仮名を検出するための正規表現を生成する */ +export const createKanaRegex = (includeSeparation?: boolean): RegExp => { + // 以下の文字のみで構成される場合、「読み仮名」としてこれを処理する + // includeSeparationがtrueの時は、読点(U+3001)とクエスチョン(U+FF1F)も含む + // * ひらがな(U+3041~U+3094) + // * カタカナ(U+30A1~U+30F4) + // * 全角長音(U+30FC) + if (includeSeparation) { + return /^[\u3041-\u3094\u30A1-\u30F4\u30FC\u3001\uFF1F]+$/; + } + return /^[\u3041-\u3094\u30A1-\u30F4\u30FC]+$/; +}; + +/** ひらがなをカタカナにする */ +export const convertHiraToKana = (text: string): string => { + return text.replace(/[\u3041-\u3094]/g, (s) => { + return String.fromCharCode(s.charCodeAt(0) + 0x60); + }); +}; + +/** ひらがなやカタカナに含まれる長音を母音に変換する */ +export const convertLongVowel = (text: string): string => { + return text + .replace(/(?<=[アカサタナハマヤラワャァガザダバパ]ー*)ー/g, "ア") + .replace(/(?<=[イキシチニヒミリィギジヂビピ]ー*)ー/g, "イ") + .replace(/(?<=[ウクスツヌフムユルュゥヴグズヅブプ]ー*)ー/g, "ウ") + .replace(/(?<=[エケセテネヘメレェゲゼデベペ]ー*)ー/g, "エ") + .replace(/(?<=[オコソトノホモヨロヲョォゴゾドボポ]ー*)ー/g, "オ") + .replace(/(?<=[ン]ー*)ー/g, "ン") + .replace(/(?<=[ッ]ー*)ー/g, "ッ") + .replace(/(?<=[あかさたなはまやらわゃぁがざだばぱ]ー*)ー/g, "あ") + .replace(/(?<=[いきしちにひみりぃぎじぢびぴ]ー*)ー/g, "い") + .replace(/(?<=[うくすつぬふむゆるゅぅぐずづぶぷ]ー*)ー/g, "う") + .replace(/(?<=[えけせてねへめれぇげぜでべぺ]ー*)ー/g, "え") + .replace(/(?<=[おこそとのほもよろをょぉごぞどぼぽ]ー*)ー/g, "お") + .replace(/(?<=[ん]ー*)ー/g, "ん") + .replace(/(?<=[っ]ー*)ー/g, "っ"); +}; diff --git a/src/sing/domain.ts b/src/sing/domain.ts index b9e2818410..a7ad7591c9 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -1,5 +1,5 @@ import { calculateHash } from "./utility"; -import { convertLongVowel } from "@/store/utility"; +import { convertLongVowel } from "@/domain/japanese"; import { Note, Phrase, diff --git a/src/store/audio.ts b/src/store/audio.ts index 8c50abe8f4..ab2ce541e1 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -16,9 +16,6 @@ import { import { buildAudioFileNameFromRawData, isAccentPhrasesTextDifferent, - convertHiraToKana, - convertLongVowel, - createKanaRegex, currentDateString, extractExportText, extractYomiText, @@ -37,6 +34,11 @@ import { isMorphable, } from "./audioGenerate"; import { ContinuousPlayer } from "./audioContinuousPlayer"; +import { + convertHiraToKana, + convertLongVowel, + createKanaRegex, +} from "@/domain/japanese"; import { AudioKey, CharacterInfo, diff --git a/src/store/utility.ts b/src/store/utility.ts index a5379bf5c0..aaf222f50e 100644 --- a/src/store/utility.ts +++ b/src/store/utility.ts @@ -379,42 +379,6 @@ export const getToolbarButtonName = (tag: ToolbarButtonTagType): string => { return tag2NameObj[tag]; }; -export const createKanaRegex = (includeSeparation?: boolean): RegExp => { - // 以下の文字のみで構成される場合、「読み仮名」としてこれを処理する - // includeSeparationがtrueの時は、読点(U+3001)とクエスチョン(U+FF1F)も含む - // * ひらがな(U+3041~U+3094) - // * カタカナ(U+30A1~U+30F4) - // * 全角長音(U+30FC) - if (includeSeparation) { - return /^[\u3041-\u3094\u30A1-\u30F4\u30FC\u3001\uFF1F]+$/; - } - return /^[\u3041-\u3094\u30A1-\u30F4\u30FC]+$/; -}; - -export const convertHiraToKana = (text: string): string => { - return text.replace(/[\u3041-\u3094]/g, (s) => { - return String.fromCharCode(s.charCodeAt(0) + 0x60); - }); -}; - -export const convertLongVowel = (text: string): string => { - return text - .replace(/(?<=[アカサタナハマヤラワャァガザダバパ]ー*)ー/g, "ア") - .replace(/(?<=[イキシチニヒミリィギジヂビピ]ー*)ー/g, "イ") - .replace(/(?<=[ウクスツヌフムユルュゥヴグズヅブプ]ー*)ー/g, "ウ") - .replace(/(?<=[エケセテネヘメレェゲゼデベペ]ー*)ー/g, "エ") - .replace(/(?<=[オコソトノホモヨロヲョォゴゾドボポ]ー*)ー/g, "オ") - .replace(/(?<=[ン]ー*)ー/g, "ン") - .replace(/(?<=[ッ]ー*)ー/g, "ッ") - .replace(/(?<=[あかさたなはまやらわゃぁがざだばぱ]ー*)ー/g, "あ") - .replace(/(?<=[いきしちにひみりぃぎじぢびぴ]ー*)ー/g, "い") - .replace(/(?<=[うくすつぬふむゆるゅぅぐずづぶぷ]ー*)ー/g, "う") - .replace(/(?<=[えけせてねへめれぇげぜでべぺ]ー*)ー/g, "え") - .replace(/(?<=[おこそとのほもよろをょぉごぞどぼぽ]ー*)ー/g, "お") - .replace(/(?<=[ん]ー*)ー/g, "ん") - .replace(/(?<=[っ]ー*)ー/g, "っ"); -}; - // based on https://github.com/BBWeb/path-browserify/blob/win-version/index.js export const getBaseName = (filePath: string) => { if (!Platform.is.win) return path.basename(filePath); diff --git a/tests/unit/domain/japanese.spec.ts b/tests/unit/domain/japanese.spec.ts new file mode 100644 index 0000000000..3fb4cbba31 --- /dev/null +++ b/tests/unit/domain/japanese.spec.ts @@ -0,0 +1,30 @@ +import { + createKanaRegex, + convertHiraToKana, + convertLongVowel, +} from "@/domain/japanese"; + +describe("createKanaRegex", () => { + it("includeSeparationがtrueの場合、読点とクエスチョンも含む", () => { + const regex = createKanaRegex(true); + expect(regex.test("あいうえお、")).toBe(true); + expect(regex.test("かきくけこ?")).toBe(true); + }); + + it("includeSeparationがfalseの場合、読点とクエスチョンを含まない", () => { + const regex = createKanaRegex(false); + expect(regex.test("あいうえお、")).toBe(false); + expect(regex.test("かきくけこ?")).toBe(false); + }); +}); + +test("convertHiraToKana", () => { + expect(convertHiraToKana("あいうえお")).toBe("アイウエオ"); + expect(convertHiraToKana("がぱをんー")).toBe("ガパヲンー"); +}); + +test("convertLongVowel", () => { + expect(convertLongVowel("アー")).toBe("アア"); + expect(convertLongVowel("ガー")).toBe("ガア"); + expect(convertLongVowel("ンー")).toBe("ンン"); +}); diff --git a/tests/unit/store/utility.spec.ts b/tests/unit/store/utility.spec.ts index 8e46cff5a1..20912cafd3 100644 --- a/tests/unit/store/utility.spec.ts +++ b/tests/unit/store/utility.spec.ts @@ -19,9 +19,6 @@ import { isAccentPhrasesTextDifferent, buildAudioFileNameFromRawData, getToolbarButtonName, - createKanaRegex, - convertHiraToKana, - convertLongVowel, getBaseName, isOnCommandOrCtrlKeyDown, filterCharacterInfosByStyleType, @@ -284,31 +281,6 @@ test("getToolbarButtonName", () => { ); }); -describe("createKanaRegex", () => { - it("includeSeparationがtrueの場合、読点とクエスチョンも含む", () => { - const regex = createKanaRegex(true); - expect(regex.test("あいうえお、")).toBe(true); - expect(regex.test("かきくけこ?")).toBe(true); - }); - - it("includeSeparationがfalseの場合、読点とクエスチョンを含まない", () => { - const regex = createKanaRegex(false); - expect(regex.test("あいうえお、")).toBe(false); - expect(regex.test("かきくけこ?")).toBe(false); - }); -}); - -test("convertHiraToKana", () => { - expect(convertHiraToKana("あいうえお")).toBe("アイウエオ"); - expect(convertHiraToKana("がぱをんー")).toBe("ガパヲンー"); -}); - -test("convertLongVowel", () => { - expect(convertLongVowel("アー")).toBe("アア"); - expect(convertLongVowel("ガー")).toBe("ガア"); - expect(convertLongVowel("ンー")).toBe("ンン"); -}); - test("getBaseName", () => { expect(getBaseName("/path/to/file.txt")).toBe("file.txt"); expect(getBaseName("/path/to/file")).toBe("file"); From da7e57c512c3749da6226a73ace76872e3660f56 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Sat, 25 May 2024 23:06:51 +0900 Subject: [PATCH 10/31] =?UTF-8?q?=E3=83=88=E3=83=BC=E3=82=AF=EF=BC=9A?= =?UTF-8?q?=E5=85=A8=E9=81=B8=E6=8A=9E=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=82=92ctrl+A?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4=E3=81=99=E3=82=8B=20(#2044)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 全選択ショートカットキーをctrl+Aに変更する * 有効なときのみ実行するようにした --- src/components/Menu/MenuBar/MenuBar.vue | 31 +++++++++++++++---------- src/components/Talk/TalkEditor.vue | 8 +++++-- src/type/preload.ts | 4 ---- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/src/components/Menu/MenuBar/MenuBar.vue b/src/components/Menu/MenuBar/MenuBar.vue index e7854564da..2b5625e417 100644 --- a/src/components/Menu/MenuBar/MenuBar.vue +++ b/src/components/Menu/MenuBar/MenuBar.vue @@ -95,6 +95,9 @@ const titleText = computed( ); const canUndo = computed(() => store.getters.CAN_UNDO(props.editor)); const canRedo = computed(() => store.getters.CAN_REDO(props.editor)); +const isMultiSelectEnabled = computed( + () => store.state.experimentalSetting.enableMultiSelect, +); // FIXME: App.vue内に移動する watch(titleText, (newTitle) => { @@ -362,18 +365,22 @@ const menudata = computed(() => [ disabled: !canRedo.value, disableWhenUiLocked: true, }, - { - type: "button", - label: "全セルを選択", - onClick: async () => { - if (!uiLocked.value) { - await store.dispatch("SET_SELECTED_AUDIO_KEYS", { - audioKeys: audioKeys.value, - }); - } - }, - disableWhenUiLocked: true, - }, + ...(isMultiSelectEnabled.value + ? [ + { + type: "button", + label: "すべて選択", + onClick: async () => { + if (!uiLocked.value && isMultiSelectEnabled.value) { + await store.dispatch("SET_SELECTED_AUDIO_KEYS", { + audioKeys: audioKeys.value, + }); + } + }, + disableWhenUiLocked: true, + } as const, + ] + : []), ...props.editSubMenuData, ], }, diff --git a/src/components/Talk/TalkEditor.vue b/src/components/Talk/TalkEditor.vue index cb11bd6e5b..1e9cfac3cb 100644 --- a/src/components/Talk/TalkEditor.vue +++ b/src/components/Talk/TalkEditor.vue @@ -158,6 +158,10 @@ const store = useStore(); const audioKeys = computed(() => store.state.audioKeys); const uiLocked = computed(() => store.getters.UI_LOCKED); +const isMultiSelectEnabled = computed( + () => store.state.experimentalSetting.enableMultiSelect, +); + const { registerHotkeyWithCleanup } = useHotkeyManager(); registerHotkeyWithCleanup({ @@ -251,9 +255,9 @@ registerHotkeyWithCleanup({ registerHotkeyWithCleanup({ editor: "talk", enableInTextbox: false, - name: "全セルを選択", + name: "すべて選択", callback: () => { - if (!uiLocked.value) { + if (!uiLocked.value && isMultiSelectEnabled.value) { store.dispatch("SET_SELECTED_AUDIO_KEYS", { audioKeys: audioKeys.value, }); diff --git a/src/type/preload.ts b/src/type/preload.ts index 179f5a1b05..67b32c34cb 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -179,10 +179,6 @@ export const defaultHotkeySettings: HotkeySettingType[] = [ action: "選択解除", combination: HotkeyCombination("Escape"), }, - { - action: "全セルを選択", - combination: HotkeyCombination(!isMac ? "Ctrl Shift A" : "Meta Shift A"), - }, ...Array.from({ length: 10 }, (_, index) => { const roleKey = index == 9 ? 0 : index + 1; return { From d5577c1920d9b25690f8dbc18d6e5e12e2ee2a73 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Sat, 25 May 2024 23:26:37 +0900 Subject: [PATCH 11/31] =?UTF-8?q?Test:=20=E8=A8=AD=E5=AE=9A=E3=83=80?= =?UTF-8?q?=E3=82=A4=E3=82=A2=E3=83=AD=E3=82=B0=E3=81=AE=E3=82=B9=E3=82=AF?= =?UTF-8?q?=E3=83=AA=E3=83=BC=E3=83=B3=E3=82=B7=E3=83=A7=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E8=BF=BD=E5=8A=A0=20(#1953)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Test: 設定ダイアログのスクリーンショットテスト * [update snapshots] * (スナップショットを更新) * テストのための空コミット * fullPage [update snapshots] * add .png [update snapshots] * (スナップショットを更新) * Revert "(スナップショットを更新)" This reverts commit 62f51a96beab2e8fd6c391dd2103332b35a0e2f6. * Revert "add .png [update snapshots]" This reverts commit f8317dc32c658079bebd4a4a8fe937f9e6583bc0. * Revert "fullPage [update snapshots]" This reverts commit efb475d0d2aee9cfd1d7332758a1d744bbdc368a. * [update snapshots] --------- Co-authored-by: github-actions[bot] --- ...3\202\277\343\203\274\343\203\263.spec.ts" | 20 +++++++++--------- ...3\202\242\343\203\255\343\202\260.spec.ts" | 18 ++++++++++++++++ ...3\203\203\343\203\210-0-browser-win32.png" | Bin 0 -> 55011 bytes ...3\203\203\343\203\210-1-browser-win32.png" | Bin 0 -> 55355 bytes ...3\203\203\343\203\210-2-browser-win32.png" | Bin 0 -> 53693 bytes ...3\203\203\343\203\210-3-browser-win32.png" | Bin 0 -> 58411 bytes ...3\203\203\343\203\210-4-browser-win32.png" | Bin 0 -> 58411 bytes tests/e2e/navigators.ts | 4 ++-- 8 files changed, 30 insertions(+), 12 deletions(-) rename "tests/e2e/browser/\343\202\252\343\203\227\343\202\267\343\203\247\343\203\263/\346\233\270\343\201\215\345\207\272\343\201\227\343\203\225\343\202\241\343\202\244\343\203\253\345\220\215\343\203\221\343\202\277\343\203\274\343\203\263.spec.ts" => "tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\346\233\270\343\201\215\345\207\272\343\201\227\343\203\225\343\202\241\343\202\244\343\203\253\345\220\215\343\203\221\343\202\277\343\203\274\343\203\263.spec.ts" (78%) create mode 100644 "tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" create mode 100644 "tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-0-browser-win32.png" create mode 100644 "tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-1-browser-win32.png" create mode 100644 "tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-2-browser-win32.png" create mode 100644 "tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-3-browser-win32.png" create mode 100644 "tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-4-browser-win32.png" diff --git "a/tests/e2e/browser/\343\202\252\343\203\227\343\202\267\343\203\247\343\203\263/\346\233\270\343\201\215\345\207\272\343\201\227\343\203\225\343\202\241\343\202\244\343\203\253\345\220\215\343\203\221\343\202\277\343\203\274\343\203\263.spec.ts" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\346\233\270\343\201\215\345\207\272\343\201\227\343\203\225\343\202\241\343\202\244\343\203\253\345\220\215\343\203\221\343\202\277\343\203\274\343\203\263.spec.ts" similarity index 78% rename from "tests/e2e/browser/\343\202\252\343\203\227\343\202\267\343\203\247\343\203\263/\346\233\270\343\201\215\345\207\272\343\201\227\343\203\225\343\202\241\343\202\244\343\203\253\345\220\215\343\203\221\343\202\277\343\203\274\343\203\263.spec.ts" rename to "tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\346\233\270\343\201\215\345\207\272\343\201\227\343\203\225\343\202\241\343\202\244\343\203\253\345\220\215\343\203\221\343\202\277\343\203\274\343\203\263.spec.ts" index 19f0a926e0..b12238b34a 100644 --- "a/tests/e2e/browser/\343\202\252\343\203\227\343\202\267\343\203\247\343\203\263/\346\233\270\343\201\215\345\207\272\343\201\227\343\203\225\343\202\241\343\202\244\343\203\253\345\220\215\343\203\221\343\202\277\343\203\274\343\203\263.spec.ts" +++ "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\346\233\270\343\201\215\345\207\272\343\201\227\343\203\225\343\202\241\343\202\244\343\203\253\345\220\215\343\203\221\343\202\277\343\203\274\343\203\263.spec.ts" @@ -1,6 +1,6 @@ import { test, expect, Page, Locator } from "@playwright/test"; -import { gotoHome, navigateToOptionDialog } from "../../navigators"; +import { gotoHome, navigateToSettingDialog } from "../../navigators"; import { getNewestQuasarDialog } from "../../locators"; test.beforeEach(gotoHome); @@ -8,8 +8,8 @@ test.beforeEach(gotoHome); /** * 書き出しファイル名パターンダイアログまで移動 */ -const moveToFilenameDialog = async (page: Page, optionDialog: Locator) => { - await optionDialog.getByRole("button", { name: "編集する" }).click(); +const moveToFilenameDialog = async (page: Page, settingDialog: Locator) => { + await settingDialog.getByRole("button", { name: "編集する" }).click(); await page.waitForTimeout(500); const filenameDialog = getNewestQuasarDialog(page); @@ -28,9 +28,9 @@ const moveToFilenameDialog = async (page: Page, optionDialog: Locator) => { test("「オプション」から「書き出しファイル名パターン」を変更したり保存したりできる", async ({ page, }) => { - const optionDialog = await navigateToOptionDialog(page); + const settingDialog = await navigateToSettingDialog(page); - let { doneButton, textbox } = await moveToFilenameDialog(page, optionDialog); + let { doneButton, textbox } = await moveToFilenameDialog(page, settingDialog); // デフォルト状態は確定ボタンが押せる await expect(textbox).toHaveValue("$連番$_$キャラ$($スタイル$)_$テキスト$"); @@ -40,7 +40,7 @@ test("「オプション」から「書き出しファイル名パターン」 await textbox.click(); await textbox.fill(""); await textbox.press("Enter"); - await expect(optionDialog.getByText("何か入力してください")).toBeVisible(); + await expect(settingDialog.getByText("何か入力してください")).toBeVisible(); await expect(doneButton).toBeDisabled(); // $連番$ が含まれていない場合は確定ボタンが押せない @@ -48,7 +48,7 @@ test("「オプション」から「書き出しファイル名パターン」 await textbox.fill("test"); await textbox.press("Enter"); await expect(textbox).toHaveValue("test"); - await expect(optionDialog.getByText("$連番$は必須です")).toBeVisible(); + await expect(settingDialog.getByText("$連番$は必須です")).toBeVisible(); await expect(doneButton).toBeDisabled(); // 無効な文字が含まれている場合は確定ボタンが押せない @@ -57,7 +57,7 @@ test("「オプション」から「書き出しファイル名パターン」 await textbox.press("Enter"); await expect(doneButton).toBeDisabled(); await expect( - optionDialog.getByText("使用できない文字が含まれています:「\\」"), + settingDialog.getByText("使用できない文字が含まれています:「\\」"), ).toBeVisible(); // $連番$ を含めると確定ボタンが押せる @@ -72,10 +72,10 @@ test("「オプション」から「書き出しファイル名パターン」 // 確定するとダイアログが閉じて設定した内容が反映されている await doneButton.click(); await page.waitForTimeout(700); - await expect(optionDialog.getByText("test$連番$.wav")).toBeVisible(); + await expect(settingDialog.getByText("test$連番$.wav")).toBeVisible(); // 再度開くと設定した内容が反映されている - ({ doneButton, textbox } = await moveToFilenameDialog(page, optionDialog)); + ({ doneButton, textbox } = await moveToFilenameDialog(page, settingDialog)); await expect(textbox).toHaveValue("test$連番$"); // デフォルト値にリセットできる diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" new file mode 100644 index 0000000000..da9f82ecd6 --- /dev/null +++ "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts" @@ -0,0 +1,18 @@ +import { test, expect } from "@playwright/test"; +import { gotoHome, navigateToSettingDialog } from "../../navigators"; + +test.beforeEach(gotoHome); + +test("スクリーンショット", async ({ page }) => { + test.skip(process.platform !== "win32", "Windows以外のためスキップします"); + + await navigateToSettingDialog(page); + await page.waitForTimeout(500); + + // スクリーンショット撮影とスクロールを繰り返す + for (let i = 0; i < 5; i++) { + await expect(page).toHaveScreenshot(`スクリーンショット_${i}.png`); + await page.mouse.wheel(0, 500); + await page.waitForTimeout(300); + } +}); diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-0-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-0-browser-win32.png" new file mode 100644 index 0000000000000000000000000000000000000000..eb3c25ab6abd3ca02a10c65faa21f129900f1c8f GIT binary patch literal 55011 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%0DCI8tZnMxFpt<--F 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 Ls%+&;qrm?S;qMWm literal 0 HcmV?d00001 diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-1-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-1-browser-win32.png" new file mode 100644 index 0000000000000000000000000000000000000000..571f3498bb56d77373409b9f1d194504812dcfb5 GIT binary patch literal 55355 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 literal 0 HcmV?d00001 diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-3-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-3-browser-win32.png" new file mode 100644 index 0000000000000000000000000000000000000000..9d8a1807ac462ea122a14f7f92ba2607174772eb GIT binary patch literal 58411 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

y_H{c>gRi{M?pewV*3uP2 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 literal 0 HcmV?d00001 diff --git "a/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-4-browser-win32.png" "b/tests/e2e/browser/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260/\350\250\255\345\256\232\343\203\200\343\202\244\343\202\242\343\203\255\343\202\260.spec.ts-snapshots/\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-4-browser-win32.png" new file mode 100644 index 0000000000000000000000000000000000000000..9d8a1807ac462ea122a14f7f92ba2607174772eb GIT binary patch literal 58411 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

y_H{c>gRi{M?pewV*3uP2 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 literal 0 HcmV?d00001 diff --git a/tests/e2e/navigators.ts b/tests/e2e/navigators.ts index 145f129a47..e923d25220 100644 --- a/tests/e2e/navigators.ts +++ b/tests/e2e/navigators.ts @@ -56,9 +56,9 @@ export async function navigateToHelpDialog(page: Page) { } /** - * オプションダイアログの表示まで移動 + * 設定ダイアログの表示まで移動 */ -export async function navigateToOptionDialog(page: Page) { +export async function navigateToSettingDialog(page: Page) { await navigateToMain(page); await page.waitForTimeout(100); await page.getByRole("button", { name: "設定" }).click(); From bba10607fa6c57eda45f4695ff643cd9cbd236bf Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Sun, 26 May 2024 00:19:43 +0900 Subject: [PATCH 12/31] =?UTF-8?q?no-unused-var=E3=82=92=E9=96=8B=E7=99=BA?= =?UTF-8?q?=E6=99=82=E4=BB=A5=E5=A4=96=E3=81=AF=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=81=AB=20(#2097)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * no-unused-varをビルド時エラーに * 修正 --- .eslintrc.js | 2 +- README.md | 8 ++++++++ tests/unit/backend/common/configManager.spec.ts | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index c77099198c..678463e093 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -46,7 +46,7 @@ module.exports = { }, ], "@typescript-eslint/no-unused-vars": [ - "warn", + process.env.NODE_ENV === "development" ? "warn" : "error", // 開発時のみwarn { ignoreRestSiblings: true, }, diff --git a/README.md b/README.md index ff4597406b..48a7fd5a47 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,14 @@ npm run license:generate -- -o voicevox_licenses.json npm run license:merge -- -o public/licenses.json -i engine_licenses.json -i voicevox_licenses.json ``` +## リント(静的解析) + +コードの静的解析を行い、バグを未然に防ぎます。プルリクエストを送る前に実行してください。 + +```bash +npm run lint +``` + ## コードフォーマット コードのフォーマットを整えます。プルリクエストを送る前に実行してください。 diff --git a/tests/unit/backend/common/configManager.spec.ts b/tests/unit/backend/common/configManager.spec.ts index deafda6b0a..e4acd0e462 100644 --- a/tests/unit/backend/common/configManager.spec.ts +++ b/tests/unit/backend/common/configManager.spec.ts @@ -1,7 +1,7 @@ import pastConfigs from "./pastConfigs"; import configBugDefaultPreset1996 from "./pastConfigs/0.19.1-bug_default_preset.json"; import { BaseConfigManager } from "@/backend/common/ConfigManager"; -import { Preset, PresetKey, VoiceId, configSchema } from "@/type/preload"; +import { configSchema } from "@/type/preload"; const configBase = { ...configSchema.parse({}), From 279ab2c50a86108d28f349d284e70e7976fac904 Mon Sep 17 00:00:00 2001 From: RikitoNoto <56541594+RikitoNoto@users.noreply.github.com> Date: Mon, 27 May 2024 13:39:25 +0900 Subject: [PATCH 13/31] =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=82=A4=E3=83=AB=E3=81=AE=E4=BF=9D=E5=AD=98=E3=82=92=E3=82=A2?= =?UTF-8?q?=E3=83=88=E3=83=9F=E3=83=83=E3=82=AF=E6=93=8D=E4=BD=9C=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=20(#2098)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #2082 tempファイル作成後に設定ファイルを書き込むよう修正 --- src/backend/electron/electronConfig.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/backend/electron/electronConfig.ts b/src/backend/electron/electronConfig.ts index 06678ebf93..57219548a4 100644 --- a/src/backend/electron/electronConfig.ts +++ b/src/backend/electron/electronConfig.ts @@ -1,6 +1,7 @@ import { join } from "path"; import fs from "fs"; import { app } from "electron"; +import { moveFile } from "move-file"; import { BaseConfigManager, Metadata } from "@/backend/common/ConfigManager"; import { ConfigType } from "@/type/preload"; @@ -21,10 +22,16 @@ export class ElectronConfigManager extends BaseConfigManager { } protected async save(config: ConfigType & Metadata) { + // ファイル書き込みに失敗したときに設定が消えないように、tempファイル書き込み後上書き移動する + const temp_path = `${this.configPath}.tmp`; await fs.promises.writeFile( - this.configPath, + temp_path, JSON.stringify(config, undefined, 2), ); + + await moveFile(temp_path, this.configPath, { + overwrite: true, + }); } private get configPath(): string { From 1604843a66bd9ee6ba67a433a22b547dcd5c31ae Mon Sep 17 00:00:00 2001 From: Segu <51497552+Segu-g@users.noreply.github.com> Date: Wed, 29 May 2024 01:23:17 +0900 Subject: [PATCH 14/31] =?UTF-8?q?vuex=E3=81=AEstore=E3=81=AE=E5=91=BC?= =?UTF-8?q?=E3=81=B3=E5=87=BA=E3=81=97=E3=82=92=E3=83=AA=E3=83=86=E3=83=A9?= =?UTF-8?q?=E3=83=AB=E5=BC=95=E6=95=B0=E3=81=8B=E3=82=89Dot=E8=A8=98?= =?UTF-8?q?=E6=B3=95=E3=81=B8=20(#2099)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add feature dot notation action * use dot notation store in dictionary.ts * このPRと直接関係ないがvuex.tsのStoreはclassである必要がないためfix * add mutaitons and actions to Store class * import alias `createDotNotationPartialStore` to `createDotPartialStore` * コメント追加とDispatch等をstore内でも見えるように * fix name DotPartStore * 丁寧語になっていたのを修正 * as unknownの文についてFIXMEを追加 * rename import `createPartialStore` * format --- src/store/dictionary.ts | 146 +++++++++++++------------ src/store/vuex.ts | 235 +++++++++++++++++++++++++++++++++++++--- 2 files changed, 297 insertions(+), 84 deletions(-) diff --git a/src/store/dictionary.ts b/src/store/dictionary.ts index 50f07d9b1b..f43d259bb3 100644 --- a/src/store/dictionary.ts +++ b/src/store/dictionary.ts @@ -1,4 +1,4 @@ -import { createPartialStore } from "./vuex"; +import { createDotNotationPartialStore as createPartialStore } from "./vuex"; import { UserDictWord, UserDictWordToJSON } from "@/openapi"; import { DictionaryStoreState, DictionaryStoreTypes } from "@/store/type"; import { EngineId } from "@/type/preload"; @@ -7,10 +7,12 @@ export const dictionaryStoreState: DictionaryStoreState = {}; export const dictionaryStore = createPartialStore({ LOAD_USER_DICT: { - async action({ dispatch }, { engineId }) { - const engineDict = await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { - engineId, - }).then((instance) => instance.invoke("getUserDictWordsUserDictGet")({})); + async action({ actions }, { engineId }) { + const engineDict = await actions + .INSTANTIATE_ENGINE_CONNECTOR({ + engineId, + }) + .then((instance) => instance.invoke("getUserDictWordsUserDictGet")({})); // 50音順にソートするために、一旦arrayにする const dictArray = Object.keys(engineDict).map((k) => { @@ -32,10 +34,10 @@ export const dictionaryStore = createPartialStore({ }, LOAD_ALL_USER_DICT: { - async action({ dispatch, state }) { + async action({ actions, state }) { const allDict = await Promise.all( state.engineIds.map((engineId) => { - return dispatch("LOAD_USER_DICT", { engineId }); + return actions.LOAD_USER_DICT({ engineId }); }), ); const mergedDictMap = new Map(); @@ -61,7 +63,7 @@ export const dictionaryStore = createPartialStore({ ADD_WORD: { async action( - { state, dispatch }, + { state, actions }, { surface, pronunciation, accentType, priority }, ) { // 同じ単語IDで登録するために、1つのエンジンで登録したあと全エンジンに同期する。 @@ -69,90 +71,100 @@ export const dictionaryStore = createPartialStore({ if (engineId == undefined) throw new Error(`No such engine registered: index == 0`); - await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { - engineId, - }).then((instance) => - instance.invoke("addUserDictWordUserDictWordPost")({ - surface, - pronunciation, - accentType, - priority, - }), - ); + await actions + .INSTANTIATE_ENGINE_CONNECTOR({ + engineId, + }) + .then((instance) => + instance.invoke("addUserDictWordUserDictWordPost")({ + surface, + pronunciation, + accentType, + priority, + }), + ); - await dispatch("SYNC_ALL_USER_DICT"); + await actions.SYNC_ALL_USER_DICT(); }, }, REWRITE_WORD: { async action( - { state, dispatch }, + { state, actions }, { wordUuid, surface, pronunciation, accentType, priority }, ) { if (state.engineIds.length === 0) throw new Error(`At least one engine must be registered`); for (const engineId of state.engineIds) { - await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { - engineId, - }).then((instance) => - instance.invoke("rewriteUserDictWordUserDictWordWordUuidPut")({ - wordUuid, - surface, - pronunciation, - accentType, - priority, - }), - ); + await actions + .INSTANTIATE_ENGINE_CONNECTOR({ + engineId, + }) + .then((instance) => + instance.invoke("rewriteUserDictWordUserDictWordWordUuidPut")({ + wordUuid, + surface, + pronunciation, + accentType, + priority, + }), + ); } }, }, DELETE_WORD: { - async action({ state, dispatch }, { wordUuid }) { + async action({ state, actions }, { wordUuid }) { if (state.engineIds.length === 0) throw new Error(`At least one engine must be registered`); for (const engineId of state.engineIds) { - await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { - engineId, - }).then((instance) => - instance.invoke("deleteUserDictWordUserDictWordWordUuidDelete")({ - wordUuid, - }), - ); + await actions + .INSTANTIATE_ENGINE_CONNECTOR({ + engineId, + }) + .then((instance) => + instance.invoke("deleteUserDictWordUserDictWordWordUuidDelete")({ + wordUuid, + }), + ); } }, }, SYNC_ALL_USER_DICT: { - async action({ dispatch, state }) { - const mergedDict = await dispatch("LOAD_ALL_USER_DICT"); + async action({ actions, state }) { + const mergedDict = await actions.LOAD_ALL_USER_DICT(); for (const engineId of state.engineIds) { // エンジンの辞書のIDリストを取得する。 - const dictIdSet = await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { - engineId, - }).then( - async (instance) => - new Set( - Object.keys( - await instance.invoke("getUserDictWordsUserDictGet")({}), - ), - ), - ); - if (Object.keys(mergedDict).some((id) => !dictIdSet.has(id))) { - await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { + const dictIdSet = await actions + .INSTANTIATE_ENGINE_CONNECTOR({ engineId, - }).then((instance) => - // マージした辞書をエンジンにインポートする。 - instance.invoke("importUserDictWordsImportUserDictPost")({ - override: true, - requestBody: Object.fromEntries( - Object.entries(mergedDict).map(([k, v]) => [ - k, - UserDictWordToJSON(v), - ]), + }) + .then( + async (instance) => + new Set( + Object.keys( + await instance.invoke("getUserDictWordsUserDictGet")({}), + ), ), - }), ); + if (Object.keys(mergedDict).some((id) => !dictIdSet.has(id))) { + await actions + .INSTANTIATE_ENGINE_CONNECTOR({ + engineId, + }) + .then((instance) => + // マージした辞書をエンジンにインポートする。 + instance.invoke("importUserDictWordsImportUserDictPost")({ + override: true, + requestBody: Object.fromEntries( + Object.entries(mergedDict).map(([k, v]) => [ + k, + UserDictWordToJSON(v), + ]), + ), + }), + ); } const removedDictIdSet = new Set(dictIdSet); // マージされた辞書にあるIDを削除する。 @@ -163,8 +175,9 @@ export const dictionaryStore = createPartialStore({ } } - await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { engineId }).then( - (instance) => { + await actions + .INSTANTIATE_ENGINE_CONNECTOR({ engineId }) + .then((instance) => { // マージ処理で削除された項目をエンジンから削除する。 Promise.all( [...removedDictIdSet].map((id) => @@ -175,8 +188,7 @@ export const dictionaryStore = createPartialStore({ ), ), ); - }, - ); + }); } }, }, diff --git a/src/store/vuex.ts b/src/store/vuex.ts index d51c1eddfa..c3a5ad7b77 100644 --- a/src/store/vuex.ts +++ b/src/store/vuex.ts @@ -3,7 +3,6 @@ import { InjectionKey } from "vue"; import { Store as BaseStore, - createStore as baseCreateStore, useStore as baseUseStore, ModuleTree, Plugin, @@ -34,20 +33,32 @@ export class Store< A extends ActionsBase, M extends MutationsBase, > extends BaseStore { - constructor(options: OriginalStoreOptions) { - super(options); + constructor(options: StoreOptions) { + super(options as OriginalStoreOptions); + // @ts-expect-error Storeの型を書き換えている影響で未初期化として判定される + this.actions = dotNotationDispatchProxy(this.dispatch.bind(this)); + this.mutations = dotNotationCommitProxy( + // @ts-expect-error Storeの型を書き換えている影響で未初期化として判定される + this.commit.bind(this) as Commit, + ); } readonly getters!: G; - // 既に型がつけられているものを上書きすることになるので、TS2564を吐く、それの回避 - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + // @ts-expect-error Storeの型を非互換な型で書き換えているためエラー dispatch: Dispatch; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore + // @ts-expect-error Storeの型を非互換な型で書き換えているためエラー commit: Commit; + /** + * ドット記法用のActionを直接呼べる。エラーになる場合はdispatchを使う。 + * 詳細 https://github.com/VOICEVOX/voicevox/issues/2088 + */ + actions: DotNotationDispatch; + /** + * ドット記法用のMutationを直接呼べる。エラーになる場合はcommitを使う。 + * 詳細 https://github.com/VOICEVOX/voicevox/issues/2088 + */ + mutations: DotNotationCommit; } export function createStore< @@ -56,13 +67,7 @@ export function createStore< A extends ActionsBase, M extends MutationsBase, >(options: StoreOptions): Store { - // optionsをOriginalStoreOptionsで型キャストしないとTS2589を吐く - return baseCreateStore(options as OriginalStoreOptions) as Store< - S, - G, - A, - M - >; + return new Store(options); } export function useStore< @@ -71,7 +76,8 @@ export function useStore< A extends ActionsBase, M extends MutationsBase, >(injectKey?: InjectionKey> | string): Store { - return baseUseStore(injectKey) as Store; + // FIXME: dispatchとcommitの型を戻せばsuper typeになるのでunknownを消せる。 + return baseUseStore(injectKey) as unknown as Store; } export interface Dispatch { @@ -103,6 +109,43 @@ export interface Commit { ): void; } +export type DotNotationDispatch = { + [T in keyof A]: ( + ...payload: Parameters + ) => Promise>>; +}; + +const dotNotationDispatchProxy = ( + dispatch: Dispatch, +): DotNotationDispatch => + new Proxy( + { dispatch }, + { + get(target, tag: string) { + return (...payloads: Parameters) => + target.dispatch(tag, ...payloads); + }, + }, + ) as DotNotationDispatch; + +export type DotNotationCommit = { + [T in keyof M]: ( + ...payload: M[T] extends undefined ? void[] : [M[T]] + ) => void; +}; + +const dotNotationCommitProxy = ( + commit: Commit, +): DotNotationCommit => + new Proxy( + { commit }, + { + get(target, tag: string) { + return (...payloads: [M[string]]) => target.commit(tag, ...payloads); + }, + }, + ) as DotNotationCommit; + export interface StoreOptions< S, G extends GettersBase, @@ -161,6 +204,49 @@ export interface ActionObject< handler: ActionHandler; } +export type DotNotationActionContext< + S, + R, + SG extends GettersBase, + SA extends ActionsBase, + SM extends MutationsBase, +> = { + /** + * ドット記法用のActionを直接呼べる。エラーになる場合はdispatchを使う。 + * 詳細 https://github.com/VOICEVOX/voicevox/issues/2088 + */ + actions: DotNotationDispatch; + /** + * ドット記法用のMutationを直接呼べる。エラーになる場合はcommitを使う。 + * 詳細 https://github.com/VOICEVOX/voicevox/issues/2088 + */ + mutations: DotNotationCommit; +} & ActionContext; + +export type DotNotationActionHandler< + S, + R, + SG extends GettersBase, + SA extends ActionsBase, + SM extends MutationsBase, + K extends keyof SA, +> = ( + this: Store, + injectee: DotNotationActionContext, + payload: Parameters[0], +) => ReturnType; +export interface DotNotationActionObject< + S, + R, + SG extends GettersBase, + SA extends ActionsBase, + SM extends MutationsBase, + K extends keyof SA, +> { + root?: boolean; + handler: DotNotationActionHandler; +} + export type Getter = ( state: S, getters: SG, @@ -186,6 +272,63 @@ export type Mutation = ( payload: M[K], ) => void; +export type DotNotationAction< + S, + R, + A extends ActionsBase, + K extends keyof A, + SG extends GettersBase, + SA extends ActionsBase, + SM extends MutationsBase, +> = + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + | DotNotationActionHandler + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + | DotNotationActionObject; + +// ドット記法のActionを通常のActionに変換する関数 +const unwrapDotNotationAction = < + S, + R, + A extends ActionsBase, + K extends keyof A, + SG extends GettersBase, + SA extends ActionsBase, + SM extends MutationsBase, +>( + dotNotationAction: DotNotationAction, +): Action => { + const wrappedHandler = + typeof dotNotationAction === "function" + ? dotNotationAction + : dotNotationAction.handler; + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const handler: ActionHandler = function ( + injectee, + payload, + ) { + const dotNotationInjectee = { + ...injectee, + actions: dotNotationDispatchProxy(injectee.dispatch), + mutations: dotNotationCommitProxy(injectee.commit), + }; + return wrappedHandler.call(this, dotNotationInjectee, payload); + }; + + if (typeof dotNotationAction === "function") { + return handler; + } else { + return { + ...dotNotationAction, + handler, + }; + } +}; + export type GetterTree< S, R, @@ -268,6 +411,30 @@ type PartialStoreOptions< }; }; +type DotNotationPartialStoreOptions< + S, + T extends StoreTypesBase, + G extends GettersBase, + A extends ActionsBase, + M extends MutationsBase, +> = { + [K in keyof T]: { + [GAM in keyof T[K]]: GAM extends "getter" + ? K extends keyof G + ? Getter + : never + : GAM extends "action" + ? K extends keyof A + ? DotNotationAction + : never + : GAM extends "mutation" + ? K extends keyof M + ? Mutation + : never + : never; + }; +}; + export const createPartialStore = < T extends StoreTypesBase, G extends GettersBase = StoreType, @@ -301,3 +468,37 @@ export const createPartialStore = < return obj; }; + +export const createDotNotationPartialStore = < + T extends StoreTypesBase, + G extends GettersBase = StoreType, + A extends ActionsBase = StoreType, + M extends MutationsBase = StoreType, +>( + options: DotNotationPartialStoreOptions, +): StoreOptions => { + const obj = Object.keys(options).reduce( + (acc, cur) => { + const option = options[cur]; + + if (option.getter) { + acc.getters[cur] = option.getter; + } + if (option.mutation) { + acc.mutations[cur] = option.mutation; + } + if (option.action) { + acc.actions[cur] = unwrapDotNotationAction(option.action); + } + + return acc; + }, + { + getters: Object.create(null), + mutations: Object.create(null), + actions: Object.create(null), + }, + ); + + return obj; +}; From de5a7263c0b97bb826bba852d7bbd098e45ce978 Mon Sep 17 00:00:00 2001 From: Sig Date: Wed, 29 May 2024 01:33:46 +0900 Subject: [PATCH 15/31] =?UTF-8?q?=E3=83=94=E3=83=83=E3=83=81=E7=B7=A8?= =?UTF-8?q?=E9=9B=86=E3=83=A2=E3=83=BC=E3=83=89=E3=81=A7=E3=83=86=E3=83=B3?= =?UTF-8?q?=E3=83=9D=E5=A4=89=E6=9B=B4=E3=82=92=E8=A1=8C=E3=81=86=E3=81=A8?= =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=81=8C=E6=8F=8F=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=83=94=E3=83=83=E3=83=81=E3=81=8C=E3=81=9A=E3=82=8C?= =?UTF-8?q?=E3=82=8B=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=20(#2101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ユーザーが描いたピッチがずれるのを修正、リファクタリング * ピッチ編集モードの時にノートの端や縁の色が変わらないようにした --- src/components/Sing/SequencerNote.vue | 32 ++- src/components/Sing/SequencerPitch.vue | 261 +++++++++++-------------- src/sing/utility.ts | 7 + src/sing/viewHelper.ts | 21 +- 4 files changed, 144 insertions(+), 177 deletions(-) diff --git a/src/components/Sing/SequencerNote.vue b/src/components/Sing/SequencerNote.vue index 56c13bb0ec..20d074779d 100644 --- a/src/components/Sing/SequencerNote.vue +++ b/src/components/Sing/SequencerNote.vue @@ -307,17 +307,23 @@ const onLyricInput = (event: Event) => { } } - &.selected { - // 色は仮 - .note-bar { - background-color: lab(95, -22.953, 14.365); - border-color: lab(65, -22.953, 14.365); - outline: solid 2px lab(70, -22.953, 14.365); + &:not(.below-pitch) { + .note-left-edge:hover { + // FIXME: hoverだとカーソル位置によって適用されないので、プレビュー中に明示的にクラス指定する + background-color: lab(80, -22.953, 14.365); } - &.below-pitch { + .note-right-edge:hover { + // FIXME: hoverだとカーソル位置によって適用されないので、プレビュー中に明示的にクラス指定する + background-color: lab(80, -22.953, 14.365); + } + + &.selected { + // 色は仮 .note-bar { - background-color: rgba(colors.$primary-rgb, 0.18); + background-color: lab(95, -22.953, 14.365); + border-color: lab(65, -22.953, 14.365); + outline: solid 2px lab(70, -22.953, 14.365); } } } @@ -395,11 +401,6 @@ const onLyricInput = (event: Event) => { left: -1px; width: 5px; height: 100%; - - &:hover { - // FIXME: hoverだとカーソル位置によって適用されないので、プレビュー中に明示的にクラス指定する - background-color: lab(80, -22.953, 14.365); - } } .note-right-edge { @@ -408,11 +409,6 @@ const onLyricInput = (event: Event) => { right: -1px; width: 5px; height: 100%; - - &:hover { - // FIXME: hoverだとカーソル位置によって適用されないので、プレビュー中に明示的にクラス指定する - background-color: lab(80, -22.953, 14.365); - } } .note-lyric-input { diff --git a/src/components/Sing/SequencerPitch.vue b/src/components/Sing/SequencerPitch.vue index 9e30597baa..5d475bdc89 100644 --- a/src/components/Sing/SequencerPitch.vue +++ b/src/components/Sing/SequencerPitch.vue @@ -15,9 +15,9 @@ import { secondToTick, } from "@/sing/domain"; import { - FramewiseDataSection, - FramewiseDataSectionHash, - calculateFramewiseDataSectionHash, + PitchData, + PitchDataHash, + calculatePitchDataHash, noteNumberToBaseY, tickToBaseX, } from "@/sing/viewHelper"; @@ -28,17 +28,15 @@ import { } from "@/composables/onMountOrActivate"; import { ExhaustiveError } from "@/type/utility"; import { createLogger } from "@/domain/frontend/log"; +import { getLast } from "@/sing/utility"; type PitchLine = { - readonly frameTicksArray: number[]; - readonly lineStrip: LineStrip; + readonly color: Color; + readonly width: number; + readonly pitchDataMap: Map; + readonly lineStripMap: Map; }; -const originalPitchLineColor = new Color(171, 201, 176, 255); -const originalPitchLineWidth = 1.2; -const pitchEditLineColor = new Color(146, 214, 154, 255); -const pitchEditLineWidth = 2; - const props = defineProps<{ offsetX: number; offsetY: number; @@ -49,6 +47,8 @@ const props = defineProps<{ const { warn, error } = createLogger("SequencerPitch"); const store = useStore(); +const tpqn = computed(() => store.state.tpqn); +const tempos = computed(() => [store.state.tempos[0]]); const singingGuides = computed(() => [...store.state.singingGuides.values()]); const pitchEditData = computed(() => { return store.getters.SELECTED_TRACK.pitchEditData; @@ -56,6 +56,19 @@ const pitchEditData = computed(() => { const previewPitchEdit = computed(() => props.previewPitchEdit); const editFrameRate = computed(() => store.state.editFrameRate); +const originalPitchLine: PitchLine = { + color: new Color(171, 201, 176, 255), + width: 1.2, + pitchDataMap: new Map(), + lineStripMap: new Map(), +}; +const pitchEditLine: PitchLine = { + color: new Color(146, 214, 154, 255), + width: 2, + pitchDataMap: new Map(), + lineStripMap: new Map(), +}; + const canvasContainer = ref(null); let resizeObserver: ResizeObserver | undefined; let canvasWidth: number | undefined; @@ -66,24 +79,7 @@ let stage: PIXI.Container | undefined; let requestId: number | undefined; let renderInNextFrame = false; -let originalPitchDataSectionMap = new Map< - FramewiseDataSectionHash, - FramewiseDataSection ->(); -let pitchEditDataSectionMap = new Map< - FramewiseDataSectionHash, - FramewiseDataSection ->(); - -const originalPitchLineMap = new Map(); -const pitchEditLineMap = new Map(); - -const updatePitchLines = ( - dataSectionMap: Map, - pitchLineMap: Map, - pitchLineColor: Color, - pitchLineWidth: number, -) => { +const updateLineStrips = (pitchLine: PitchLine) => { if (stage == undefined) { throw new Error("stage is undefined."); } @@ -91,7 +87,6 @@ const updatePitchLines = ( throw new Error("canvasWidth is undefined."); } const tpqn = store.state.tpqn; - const tempos = [store.state.tempos[0]]; const canvasWidthValue = canvasWidth; const zoomX = store.state.sequencerZoomX; const zoomY = store.state.sequencerZoomY; @@ -100,48 +95,37 @@ const updatePitchLines = ( const removedLineStrips: LineStrip[] = []; - // 無くなったデータ区間を調べて、そのデータ区間に対応するピッチラインを削除する - for (const [key, pitchLine] of pitchLineMap) { - if (!dataSectionMap.has(key)) { - stage.removeChild(pitchLine.lineStrip.displayObject); - removedLineStrips.push(pitchLine.lineStrip); - pitchLineMap.delete(key); + // 無くなったピッチデータを調べて、そのピッチデータに対応するLineStripを削除する + for (const [key, lineStrip] of pitchLine.lineStripMap) { + if (!pitchLine.pitchDataMap.has(key)) { + stage.removeChild(lineStrip.displayObject); + removedLineStrips.push(lineStrip); + pitchLine.lineStripMap.delete(key); } } - // データ区間に対応するピッチラインが無かったら生成する - for (const [key, dataSection] of dataSectionMap) { - if (pitchLineMap.has(key)) { + // ピッチデータに対応するLineStripが無かったら作成する + for (const [key, pitchData] of pitchLine.pitchDataMap) { + if (pitchLine.lineStripMap.has(key)) { continue; } - const startFrame = dataSection.startFrame; - const frameLength = dataSection.data.length; - const endFrame = startFrame + frameLength; - const frameRate = dataSection.frameRate; - - // 各フレームのticksは前もって計算しておく - const frameTicksArray: number[] = []; - for (let i = startFrame; i < endFrame; i++) { - const ticks = secondToTick(i / frameRate, tempos, tpqn); - frameTicksArray.push(ticks); - } + const dataLength = pitchData.data.length; // 再利用できるLineStripがあれば再利用し、なければLineStripを作成する let lineStrip = removedLineStrips.pop(); if (lineStrip != undefined) { if ( - !lineStrip.color.equals(pitchLineColor) || - lineStrip.width !== pitchLineWidth + !lineStrip.color.equals(pitchLine.color) || + lineStrip.width !== pitchLine.width ) { throw new Error("Color or width does not match."); } - lineStrip.numOfPoints = frameLength; + lineStrip.numOfPoints = dataLength; } else { - lineStrip = new LineStrip(frameLength, pitchLineColor, pitchLineWidth); + lineStrip = new LineStrip(dataLength, pitchLine.color, pitchLine.width); } stage.addChild(lineStrip.displayObject); - - pitchLineMap.set(key, { frameTicksArray, lineStrip }); + pitchLine.lineStripMap.set(key, lineStrip); } // 再利用されなかったLineStripは破棄する @@ -149,44 +133,38 @@ const updatePitchLines = ( lineStrip.destroy(); } - // ピッチラインを更新 - for (const [key, dataSection] of dataSectionMap) { - const pitchLine = pitchLineMap.get(key); - if (pitchLine == undefined) { - throw new Error("pitchLine is undefined."); - } - if (pitchLine.frameTicksArray.length !== dataSection.data.length) { - throw new Error( - "frameTicksArray.length and dataSection.length do not match.", - ); + // LineStripを更新 + for (const [key, pitchData] of pitchLine.pitchDataMap) { + const lineStrip = pitchLine.lineStripMap.get(key); + if (lineStrip == undefined) { + throw new Error("lineStrip is undefined."); } // カリングを行う - const startTicks = pitchLine.frameTicksArray[0]; + const startTicks = pitchData.ticksArray[0]; const startBaseX = tickToBaseX(startTicks, tpqn); const startX = startBaseX * zoomX - offsetX; - const lastIndex = pitchLine.frameTicksArray.length - 1; - const endTicks = pitchLine.frameTicksArray[lastIndex]; - const endBaseX = tickToBaseX(endTicks, tpqn); - const endX = endBaseX * zoomX - offsetX; - if (startX >= canvasWidthValue || endX <= 0) { - pitchLine.lineStrip.renderable = false; + const lastTicks = getLast(pitchData.ticksArray); + const lastBaseX = tickToBaseX(lastTicks, tpqn); + const lastX = lastBaseX * zoomX - offsetX; + if (startX >= canvasWidthValue || lastX <= 0) { + lineStrip.renderable = false; continue; } - pitchLine.lineStrip.renderable = true; + lineStrip.renderable = true; // ポイントを計算してlineStripに設定&更新 - for (let i = 0; i < dataSection.data.length; i++) { - const ticks = pitchLine.frameTicksArray[i]; + for (let i = 0; i < pitchData.data.length; i++) { + const ticks = pitchData.ticksArray[i]; const baseX = tickToBaseX(ticks, tpqn); const x = baseX * zoomX - offsetX; - const freq = dataSection.data[i]; + const freq = pitchData.data[i]; const noteNumber = frequencyToNoteNumber(freq); const baseY = noteNumberToBaseY(noteNumber); const y = baseY * zoomY - offsetY; - pitchLine.lineStrip.setPoint(i, x, y); + lineStrip.setPoint(i, x, y); } - pitchLine.lineStrip.update(); + lineStrip.update(); } }; @@ -201,66 +179,72 @@ const render = () => { // シンガーが未設定の場合はピッチラインをすべて非表示にして終了 const singer = store.getters.SELECTED_TRACK.singer; if (!singer) { - for (const originalPitchLine of originalPitchLineMap.values()) { - originalPitchLine.lineStrip.renderable = false; + for (const lineStrip of originalPitchLine.lineStripMap.values()) { + lineStrip.renderable = false; } - for (const pitchEditLine of pitchEditLineMap.values()) { - pitchEditLine.lineStrip.renderable = false; + for (const lineStrip of pitchEditLine.lineStripMap.values()) { + lineStrip.renderable = false; } renderer.render(stage); return; } - // ピッチラインを更新する - updatePitchLines( - originalPitchDataSectionMap, - originalPitchLineMap, - originalPitchLineColor, - originalPitchLineWidth, - ); - updatePitchLines( - pitchEditDataSectionMap, - pitchEditLineMap, - pitchEditLineColor, - pitchEditLineWidth, - ); + // ピッチラインのLineStripを更新する + updateLineStrips(originalPitchLine); + updateLineStrips(pitchEditLine); renderer.render(stage); }; -const generateDataSectionMap = async (data: number[], frameRate: number) => { - // データ区間(データがある区間)の配列を生成する - let dataSections: FramewiseDataSection[] = []; +const toPitchData = (framewiseData: number[], frameRate: number): PitchData => { + const data = framewiseData; + const ticksArray: number[] = []; + for (let i = 0; i < data.length; i++) { + const ticks = secondToTick(i / frameRate, tempos.value, tpqn.value); + ticksArray.push(ticks); + } + return { ticksArray, data }; +}; + +const splitPitchData = (pitchData: PitchData, delimiter: number) => { + const ticksArray = pitchData.ticksArray; + const data = pitchData.data; + const pitchDataArray: PitchData[] = []; for (let i = 0; i < data.length; i++) { - if (data[i] !== VALUE_INDICATING_NO_DATA) { - if (i === 0 || data[i - 1] === VALUE_INDICATING_NO_DATA) { - dataSections.push({ startFrame: i, frameRate, data: [] }); + if (data[i] !== delimiter) { + if (i === 0 || data[i - 1] === delimiter) { + pitchDataArray.push({ ticksArray: [], data: [] }); } - dataSections[dataSections.length - 1].data.push(data[i]); + const lastPitchData = getLast(pitchDataArray); + lastPitchData.ticksArray.push(ticksArray[i]); + lastPitchData.data.push(data[i]); } } - dataSections = dataSections.filter((value) => value.data.length >= 2); - - // データ区間のハッシュを計算して、ハッシュがキーのマップにする - const dataSectionMap = new Map< - FramewiseDataSectionHash, - FramewiseDataSection - >(); - for (const dataSection of dataSections) { - const hash = await calculateFramewiseDataSectionHash(dataSection); - dataSectionMap.set(hash, dataSection); - } - return dataSectionMap; + return pitchDataArray; }; -const updateOriginalPitchDataSectionMap = async () => { - // 歌い方のf0を結合して1次元のデータにし、 - // 1次元のデータからデータ区間のマップを生成して、originalPitchDataSectionMapに設定する +const setPitchDataToPitchLine = async ( + pitchData: PitchData, + pitchLine: PitchLine, +) => { + const partialPitchDataArray = splitPitchData( + pitchData, + VALUE_INDICATING_NO_DATA, + ).filter((value) => value.data.length >= 2); + + pitchLine.pitchDataMap.clear(); + for (const partialPitchData of partialPitchDataArray) { + const hash = await calculatePitchDataHash(partialPitchData); + pitchLine.pitchDataMap.set(hash, partialPitchData); + } +}; +const generateOriginalPitchData = () => { const unvoicedPhonemes = UNVOICED_PHONEMES; - const frameRate = editFrameRate.value; // f0(元のピッチ)は編集フレームレートで表示する const singingGuidesValue = singingGuides.value; + const frameRate = editFrameRate.value; // f0(元のピッチ)は編集フレームレートで表示する + // 歌い方のf0を結合してピッチデータを生成する const tempData: number[] = []; for (const singingGuide of singingGuidesValue) { // TODO: 補間を行うようにする @@ -307,20 +291,13 @@ const updateOriginalPitchDataSectionMap = async () => { } } } - - // データ区間(ピッチのデータがある区間)のマップを生成する - const dataSectionMap = await generateDataSectionMap(tempData, frameRate); - - originalPitchDataSectionMap = dataSectionMap; + return toPitchData(tempData, frameRate); }; -const updatePitchEditDataSectionMap = async () => { - // ピッチ編集データとプレビュー中のピッチ編集データを結合して1次元のデータにし、 - // 1次元のデータからデータ区間のマップを生成して、pitchEditDataSectionMapに設定する - +const generatePitchEditData = () => { const frameRate = editFrameRate.value; - const tempData = [...pitchEditData.value]; + const tempData = [...pitchEditData.value]; // プレビュー中のピッチ編集があれば、適用する if (previewPitchEdit.value != undefined) { const previewPitchEditType = previewPitchEdit.value.type; @@ -350,22 +327,19 @@ const updatePitchEditDataSectionMap = async () => { throw new ExhaustiveError(previewPitchEditType); } } - - // データ区間(ピッチ編集データがある区間)のマップを生成する - const dataSectionMap = await generateDataSectionMap(tempData, frameRate); - - pitchEditDataSectionMap = dataSectionMap; + return toPitchData(tempData, frameRate); }; const asyncLock = new AsyncLock({ maxPending: 1 }); watch( - singingGuides, + [singingGuides, tempos, tpqn], async () => { asyncLock.acquire( "originalPitch", async () => { - await updateOriginalPitchDataSectionMap(); + const originalPitchData = generateOriginalPitchData(); + await setPitchDataToPitchLine(originalPitchData, originalPitchLine); renderInNextFrame = true; }, (err) => { @@ -379,12 +353,13 @@ watch( ); watch( - [pitchEditData, previewPitchEdit], + [pitchEditData, previewPitchEdit, tempos, tpqn], async () => { asyncLock.acquire( "pitchEdit", async () => { - await updatePitchEditDataSectionMap(); + const pitchEditData = generatePitchEditData(); + await setPitchDataToPitchLine(pitchEditData, pitchEditLine); renderInNextFrame = true; }, (err) => { @@ -471,14 +446,10 @@ onUnmountedOrDeactivated(() => { window.cancelAnimationFrame(requestId); } stage?.destroy(); - originalPitchLineMap.forEach((value) => { - value.lineStrip.destroy(); - }); - originalPitchLineMap.clear(); - pitchEditLineMap.forEach((value) => { - value.lineStrip.destroy(); - }); - pitchEditLineMap.clear(); + originalPitchLine.lineStripMap.forEach((value) => value.destroy()); + originalPitchLine.lineStripMap.clear(); + pitchEditLine.lineStripMap.forEach((value) => value.destroy()); + pitchEditLine.lineStripMap.clear(); renderer?.destroy(true); resizeObserver?.disconnect(); }); diff --git a/src/sing/utility.ts b/src/sing/utility.ts index 91cd4d83cf..2d9f38b2cc 100644 --- a/src/sing/utility.ts +++ b/src/sing/utility.ts @@ -3,6 +3,13 @@ export function round(value: number, digits: number) { return Math.round(value * powerOf10) / powerOf10; } +export function getLast(array: T[]) { + if (array.length === 0) { + throw new Error("array.length is 0."); + } + return array[array.length - 1]; +} + export function linearInterpolation( x1: number, y1: number, diff --git a/src/sing/viewHelper.ts b/src/sing/viewHelper.ts index 781123f3da..d41460ec61 100644 --- a/src/sing/viewHelper.ts +++ b/src/sing/viewHelper.ts @@ -182,25 +182,18 @@ export class GridAreaInfo implements AreaInfo { } } -export type FramewiseDataSection = { - readonly startFrame: number; - readonly frameRate: number; +export type PitchData = { + readonly ticksArray: number[]; readonly data: number[]; }; -const framewiseDataSectionHashSchema = z - .string() - .brand<"FramewiseDataSectionHash">(); +const pitchDataHashSchema = z.string().brand<"PitchDataHash">(); -export type FramewiseDataSectionHash = z.infer< - typeof framewiseDataSectionHashSchema ->; +export type PitchDataHash = z.infer; -export async function calculateFramewiseDataSectionHash( - dataSection: FramewiseDataSection, -) { - const hash = await calculateHash(dataSection); - return framewiseDataSectionHashSchema.parse(hash); +export async function calculatePitchDataHash(pitchData: PitchData) { + const hash = await calculateHash(pitchData); + return pitchDataHashSchema.parse(hash); } export type MouseButton = "LEFT_BUTTON" | "RIGHT_BUTTON" | "OTHER_BUTTON"; From a07c776956987f8a44538089278449cd0469f022 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Wed, 29 May 2024 01:47:02 +0900 Subject: [PATCH 16/31] =?UTF-8?q?Improve:=20=E3=83=87=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E6=BA=96=E5=82=99=E3=82=92=E9=AB=98=E9=80=9F=E5=8C=96=20(#2090?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change: fast-base64を使う * Refactor: asyncComputedに統一 * Improve: キャッシュを追加 * Change: useEngineIconsにする * Code: コメントを足す * Fix: 代入忘れ * Change: computedにする * Fix: 型を足す * Add: コメントを追加 * Update src/components/Menu/MenuBar/MenuBar.vue --------- Co-authored-by: Hiroshiba --- package-lock.json | 6 +++++ package.json | 1 + src/@types/fast-base64.d.ts | 5 ++++ src/components/CharacterButton.vue | 11 ++------ src/components/Dialog/EngineManageDialog.vue | 12 +++------ src/components/Menu/MenuBar/MenuBar.vue | 5 ++-- .../Sing/CharacterMenuButton/MenuButton.vue | 11 ++------ src/composables/useEngineIcons.ts | 27 +++++++++++++++++++ src/helpers/base64Helper.ts | 21 ++++++++++----- src/store/audio.ts | 21 ++++++++------- 10 files changed, 76 insertions(+), 44 deletions(-) create mode 100644 src/@types/fast-base64.d.ts create mode 100644 src/composables/useEngineIcons.ts diff --git a/package-lock.json b/package-lock.json index b9b4e12bf3..6da39e115c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "electron-window-state": "5.0.3", "encoding-japanese": "1.0.30", "fast-array-diff": "1.1.0", + "fast-base64": "0.1.8", "glob": "8.0.3", "hotkeys-js": "3.13.6", "immer": "9.0.21", @@ -6613,6 +6614,11 @@ "resolved": "https://registry.npmjs.org/fast-array-diff/-/fast-array-diff-1.1.0.tgz", "integrity": "sha512-muSPyZa/yHCoDQhah9th57AmLENB1nekbrUoLAqOpQXdl1Kw8VbH24Syl5XLscaQJlx7KRU95bfTDPvVB5BJvw==" }, + "node_modules/fast-base64": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/fast-base64/-/fast-base64-0.1.8.tgz", + "integrity": "sha512-LICiPjlLyh7/P3gcJYDjKEIX41odzqny1VHSnPsAlBb/zcSJJPYrSNHs54e2TytDRTwHZl7KG5c33IMLdT+9Eg==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", diff --git a/package.json b/package.json index 30d8bbf1ed..10d7069c38 100644 --- a/package.json +++ b/package.json @@ -43,6 +43,7 @@ "electron-window-state": "5.0.3", "encoding-japanese": "1.0.30", "fast-array-diff": "1.1.0", + "fast-base64": "0.1.8", "glob": "8.0.3", "hotkeys-js": "3.13.6", "immer": "9.0.21", diff --git a/src/@types/fast-base64.d.ts b/src/@types/fast-base64.d.ts new file mode 100644 index 0000000000..a52a07fa05 --- /dev/null +++ b/src/@types/fast-base64.d.ts @@ -0,0 +1,5 @@ +// fast-base64の型定義が壊れているので、ここで型定義を追加する。 +declare module "fast-base64" { + export function toBytes(base64: string): Promise; + export function toBase64(bytes: Uint8Array): Promise; +} diff --git a/src/components/CharacterButton.vue b/src/components/CharacterButton.vue index 3e49c5f91a..2ca2304b50 100644 --- a/src/components/CharacterButton.vue +++ b/src/components/CharacterButton.vue @@ -196,11 +196,11 @@ diff --git a/src/components/Dialog/FileNamePatternDialog.vue b/src/components/Dialog/SettingDialog/FileNamePatternDialog.vue similarity index 100% rename from src/components/Dialog/FileNamePatternDialog.vue rename to src/components/Dialog/SettingDialog/FileNamePatternDialog.vue diff --git a/src/components/Dialog/SettingDialog.vue b/src/components/Dialog/SettingDialog/SettingDialog.vue similarity index 61% rename from src/components/Dialog/SettingDialog.vue rename to src/components/Dialog/SettingDialog/SettingDialog.vue index 7f776502ae..cd0defb5f4 100644 --- a/src/components/Dialog/SettingDialog.vue +++ b/src/components/Dialog/SettingDialog/SettingDialog.vue @@ -118,96 +118,37 @@

操作
- -
プリセット機能
-
- - - プリセット機能を有効にします。パラメータを登録したり適用したりできます。 - - -
- - - -
+
- -
スタイル変更時にデフォルトプリセットを適用
-
- - - ONの場合、キャラやスタイルの変更時にデフォルトプリセットが自動的に適用されます。 - - -
- - - -
+
- -
パラメータの引き継ぎ
-
- - - ONの場合、テキスト欄追加の際に、現在の話速等のパラメータが引き継がれます。 - - -
- - - -
+
再生位置を追従
- -
メモ機能
-
- - - ONの場合、テキストを [] - で囲むことで、テキスト中にメモを書けます。 - - -
- - - -
- -
ルビ機能
-
- - - ONの場合、テキストに {ルビ対象|よみかた} - と書くことで、テキストの読み方を変えられます。 - - -
- - - -
+ +
非表示にしたヒントを全て再表示
- -
上書き防止
-
- - - ONの場合、書き出す際に同名ファイルが既にあったとき、ファイル名に連番を付けて別名で保存されます。 - - -
- - - -
+
文字コード
- -
txtファイルを書き出し
-
- - - ONの場合、音声書き出しの際にテキストがtxtファイルとして書き出されます。 - - -
- - - -
- -
labファイルを書き出し
-
- - - ONの場合、音声書き出しの際にリップシンク用のlabファイルが書き出されます。 - - -
- - - -
+ + @@ -701,55 +543,18 @@ @update:model-value="changeEditorFont($event)" /> - -
行番号の表示
-
- - - ONの場合、テキスト欄の左側に行番号が表示されます。 - - -
- - - -
- -
テキスト追加ボタンの表示
-
- - - OFFの場合、右下にテキスト追加ボタンが表示されません。(テキスト欄は - Shift + Enter で追加できます) - - -
- - - -
+ +
@@ -757,54 +562,18 @@
高度な設定
- -
マルチエンジン機能
-
- - - 複数のVOICEVOX準拠エンジンを利用可能にする - - -
- - - -
- -
音声をステレオ化
-
- - - ONの場合、音声データがモノラルからステレオに変換されてから再生・保存が行われます。 - - -
- - - -
+ + 実験的機能
- -
疑問文を自動調整
-
- - - ONの場合、疑問文の語尾の音高が自動的に上げられます。 - - -
- - - -
- -
モーフィング機能
-
- - - モーフィング機能を有効にします。2つの音声混ぜられるようになります。 - - -
- - - -
- -
複数選択
-
- - - 複数のテキスト欄を選択できるようにします。 - - -
- - - -
- -
[開発時のみ機能] 調整結果の保持
-
- - ONの場合、テキスト変更時、同じ読みのアクセント区間内の調整結果を保持します。 - -
- - - -
- -
ソング:ピッチ編集機能
-
- - ピッチ編集機能を有効にします。ピッチ編集モードに切り替えられるようになります。 - -
- - - -
+ + + + +
データ収集
- -
ソフトウェア利用状況のデータ収集を許可
-
- - - ONの場合、各UIの利用率などのデータが送信され、VOICEVOXの改善に役立てられます。テキストデータ・音声データは送信されません。 - - -
- - -
+
@@ -1018,6 +684,7 @@ From b780977ff55cf1cad9bf749778a434e0e21c4ee6 Mon Sep 17 00:00:00 2001 From: Han Yeong-woo Date: Sun, 2 Jun 2024 11:42:09 +0900 Subject: [PATCH 20/31] Bump actions (#2109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Bump actions * "crate-ci/typos action"のエラーを修正 * Apply suggestions from code review --------- Co-authored-by: Hiroshiba --- .github/actions/setup-environment/action.yml | 8 ++--- .github/workflows/build.yml | 32 ++++++++++---------- .github/workflows/check_version.yml | 4 +-- .github/workflows/labeler.yml | 2 +- .github/workflows/release_latest_dev.yml | 2 +- .github/workflows/test.yml | 12 ++++---- .github/workflows/typos.yml | 6 ++-- _typos.toml | 8 ++++- docker-compose.yml | 8 ++--- 9 files changed, 44 insertions(+), 38 deletions(-) diff --git a/.github/actions/setup-environment/action.yml b/.github/actions/setup-environment/action.yml index 923fc26f6f..a15e5719d3 100644 --- a/.github/actions/setup-environment/action.yml +++ b/.github/actions/setup-environment/action.yml @@ -12,13 +12,13 @@ runs: echo "cache-version=1" >> $GITHUB_ENV - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: ".node-version" cache: "npm" - name: Cache Electron - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.ELECTRON_CACHE }} key: ${{ env.cache-version }}-${{ runner.os }}--electron-cache-${{ hashFiles('**/package-lock.json') }} @@ -26,7 +26,7 @@ runs: ${{ env.cache-version }}-${{ runner.os }}--electron-cache- - name: Cache Electron-Builder - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.ELECTRON_BUILDER_CACHE }} key: ${{ env.cache-version }}-${{ runner.os }}--electron-builder-cache-${{ hashFiles('**/package-lock.json') }} @@ -34,7 +34,7 @@ runs: ${{ env.cache-version }}-${{ runner.os }}--electron-builder-cache- - name: Cache external dependencies - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ./build/vendored key: ${{ env.cache-version }}-${{ runner.os }}--vendored-${{ hashFiles('build/*.js') }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 84ccda8b21..207a98f2b3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -124,7 +124,7 @@ jobs: runs-on: ${{ matrix.os }} steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 # NOTE: The default sed of macOS is BSD sed. # There is a difference in specification between BSD sed and GNU sed, @@ -149,13 +149,13 @@ jobs: $sed -i 's/"version": "999.999.999"/"version": "${{ env.VOICEVOX_EDITOR_VERSION }}"/' package.json - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: ".node-version" cache: "npm" - name: Cache Electron - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.ELECTRON_CACHE }} key: ${{ env.cache-version }}-${{ runner.os }}--electron-cache-${{ hashFiles('**/package-lock.json') }} @@ -163,7 +163,7 @@ jobs: ${{ env.cache-version }}-${{ runner.os }}--electron-cache- - name: Cache Electron-Builder - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ${{ env.ELECTRON_BUILDER_CACHE }} key: ${{ env.cache-version }}-${{ runner.os }}--electron-builder-cache-${{ hashFiles('**/package-lock.json') }} @@ -174,7 +174,7 @@ jobs: run: npm ci - name: Checkout Product Version Resource - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: VOICEVOX/voicevox_resource ref: ${{ env.VOICEVOX_RESOURCE_VERSION }} @@ -300,7 +300,7 @@ jobs: chmod +x "prepackage/VOICEVOX.app/Contents/Frameworks/VOICEVOX Helper (Renderer).app/Contents/MacOS/VOICEVOX Helper (Renderer)" chmod +x "prepackage/VOICEVOX.app/Contents/Frameworks/VOICEVOX Helper.app/Contents/MacOS/VOICEVOX Helper" - # NOTE: actions/upload-artifact@v3 does not upload `**.lproj` directories, which are an empty directory. + # NOTE: actions/upload-artifact@v4 does not upload `**.lproj` directories, which are an empty directory. # Make `ja.lproj` directory because it is necessary for Japanese localization on macOS. - name: Make .lproj directories in Resources directory of VOICEVOX.app if: startsWith(matrix.artifact_name, 'macos-') @@ -317,7 +317,7 @@ jobs: - 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 == 'true' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact_name }}-targz path: |- @@ -325,7 +325,7 @@ jobs: - name: Upload Linux tar.gz (without nvidia) to Release Assets if: startsWith(matrix.artifact_name, 'linux-') && !contains(matrix.artifact_name, 'nvidia') && (github.event.release.tag_name || github.event.inputs.version) != '' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: prerelease: ${{ github.event.inputs.prerelease }} tag_name: ${{ env.VOICEVOX_EDITOR_VERSION }} @@ -347,7 +347,7 @@ jobs: - 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 == 'true' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.artifact_name }}-zip path: |- @@ -355,7 +355,7 @@ jobs: - name: Upload Windows & Mac zip (without nvidia) to Release Assets if: (startsWith(matrix.artifact_name, 'windows-') || startsWith(matrix.artifact_name, 'macos-')) && !contains(matrix.artifact_name, 'nvidia') && (github.event.release.tag_name || github.event.inputs.version) != '' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: prerelease: ${{ github.event.inputs.prerelease }} tag_name: ${{ env.VOICEVOX_EDITOR_VERSION }} @@ -436,7 +436,7 @@ jobs: - name: Upload Linux AppImage split to Artifacts if: endsWith(matrix.installer_artifact_name, '-appimage') && github.event.inputs.upload_artifact == 'true' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.installer_artifact_name }}-release path: |- @@ -444,7 +444,7 @@ jobs: - name: Upload Linux AppImage split to Release Assets if: endsWith(matrix.installer_artifact_name, '-appimage') && (github.event.release.tag_name || github.event.inputs.version) != '' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: prerelease: ${{ github.event.inputs.prerelease }} tag_name: ${{ env.VOICEVOX_EDITOR_VERSION }} @@ -454,7 +454,7 @@ jobs: - name: Upload macOS dmg to Artifacts if: endsWith(matrix.installer_artifact_name, '-dmg') && github.event.inputs.upload_artifact == 'true' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.installer_artifact_name }}-release path: |- @@ -462,7 +462,7 @@ jobs: - name: Upload macOS dmg to Release Assets if: endsWith(matrix.installer_artifact_name, '-dmg') && (github.event.release.tag_name || github.event.inputs.version) != '' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: prerelease: ${{ github.event.inputs.prerelease }} tag_name: ${{ env.VOICEVOX_EDITOR_VERSION }} @@ -472,7 +472,7 @@ jobs: - name: Upload Windows NSIS Web to Artifacts if: endsWith(matrix.installer_artifact_name, '-nsis-web') && github.event.inputs.upload_artifact == 'true' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: ${{ matrix.installer_artifact_name }}-release path: |- @@ -481,7 +481,7 @@ jobs: - name: Upload Windows NSIS Web to Release Assets if: endsWith(matrix.installer_artifact_name, '-nsis-web') && (github.event.release.tag_name || github.event.inputs.version) != '' - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 with: prerelease: ${{ github.event.inputs.prerelease }} tag_name: ${{ env.VOICEVOX_EDITOR_VERSION }} diff --git a/.github/workflows/check_version.yml b/.github/workflows/check_version.yml index 75261619f0..6d0a4737da 100644 --- a/.github/workflows/check_version.yml +++ b/.github/workflows/check_version.yml @@ -10,12 +10,12 @@ on: defaults: run: shell: bash - + jobs: check_node_version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Check synchronize volta and .node_version uses: My-MC/check-sync-volta-and-node-version@v0.0.3 diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 5ec62249f5..43bad20938 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -12,7 +12,7 @@ jobs: triage: runs-on: ubuntu-latest steps: - - uses: github/issue-labeler@v2.0 + - uses: github/issue-labeler@v3.4 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" configuration-path: .github/labeler.yml diff --git a/.github/workflows/release_latest_dev.yml b/.github/workflows/release_latest_dev.yml index 5c86fe9fe9..e73a1a588d 100644 --- a/.github/workflows/release_latest_dev.yml +++ b/.github/workflows/release_latest_dev.yml @@ -17,7 +17,7 @@ jobs: if: github.repository_owner == 'VOICEVOX' steps: - name: Trigger workflow_dispatch - uses: actions/github-script@v6 + uses: actions/github-script@v7 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a0f1d8d9ae..b990e5d683 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -43,7 +43,7 @@ jobs: build-test: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup environment uses: ./.github/actions/setup-environment - run: npm run electron:build @@ -52,7 +52,7 @@ jobs: unit-test: runs-on: windows-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup environment uses: ./.github/actions/setup-environment @@ -74,7 +74,7 @@ jobs: - os: windows-latest voicevox_engine_asset_name: windows-cpu steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup environment uses: ./.github/actions/setup-environment @@ -135,7 +135,7 @@ jobs: - name: Upload playwright report to artifact if: failure() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: playwright-report-${{ matrix.os }} path: playwright-report @@ -161,7 +161,7 @@ jobs: needs: [config, e2e-test] if: needs.config.outputs.shouldUpdateSnapshots == 'true' steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Download artifacts uses: actions/download-artifact@v4 @@ -192,7 +192,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Setup environment uses: ./.github/actions/setup-environment diff --git a/.github/workflows/typos.yml b/.github/workflows/typos.yml index f00617a22a..ea745552b4 100644 --- a/.github/workflows/typos.yml +++ b/.github/workflows/typos.yml @@ -10,15 +10,15 @@ on: defaults: run: shell: bash - + jobs: typos: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: typos-action - uses: crate-ci/typos@v1.12.12 + uses: crate-ci/typos@v1.21.0 with: files: ". .github" diff --git a/_typos.toml b/_typos.toml index 42cd7949ad..ce09d78b7c 100644 --- a/_typos.toml +++ b/_typos.toml @@ -2,10 +2,16 @@ # Instruction: https://github.com/marketplace/actions/typos-action#getting-started [default.extend-identifiers] +splited = "splited" # コードで使う時、もっと自然な感じを与える [default.extend-words] ba = "ba" # 7zコマンドの-baオプション commitish = "commitish" # softprops/action-gh-releaseのオプションの1つ [files] -extend-exclude = ["package-lock.json", "src/domain/project/index.ts", "*.svg"] +extend-exclude = [ + "package-lock.json", + "src/domain/project/index.ts", + "*.svg", + "public/res/macos-big-sur-software-update-rosetta-alert.jpg", # macos-big-surは正しい固有名詞 +] diff --git a/docker-compose.yml b/docker-compose.yml index c7215ca192..77117ea2bd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,9 +1,9 @@ version: '3' services: test: - build: . + build: . working_dir: /work - # If you don't have node_modules, use "npm ci", install node_modules into the yor directory. + # If you don't have node_modules, use "npm ci", install node_modules into the your directory. # Use command bellow # `docker-compose up -d --build` # The container will stop automatically after install @@ -12,7 +12,7 @@ services: # command: "npm ci" - # After node_modules install, use command bellow to exec "/bin/sh" and the container will remain. + # After node_modules install, use command bellow to exec "/bin/sh" and the container will remain. # `docker-compose up -d` # To access container, use command bellow # `docker exec -it voicevox_test_1 /bin/bash` @@ -21,7 +21,7 @@ services: # If you want test automatically (test runs when you save any files), use this command property and view logs - # Use Command bellow + # Use Command bellow # `docker logs --tail 1000 -f voicevox_test_1` command: "npm run test-watch:unit " From 9d703cc8b9a927c043f8c85cfe2c332e80b3f94d Mon Sep 17 00:00:00 2001 From: Han Yeong-woo Date: Tue, 4 Jun 2024 06:54:48 +0900 Subject: [PATCH 21/31] =?UTF-8?q?"uuid"=E3=82=92=E7=B5=84=E3=81=BF?= =?UTF-8?q?=E8=BE=BC=E3=81=BF=E3=81=AE`crypto.randomUUID`=E3=81=AB?= =?UTF-8?q?=E7=BD=AE=E3=81=8D=E6=8F=9B=E3=81=88=E3=82=8B=20(#2113)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 16 ---------------- package.json | 2 -- src/components/Sing/ScoreSequencer.vue | 3 +-- src/store/audio.ts | 3 +-- src/store/preset.ts | 3 +-- src/store/singing.ts | 9 ++++----- tests/unit/lib/selectPriorPhrase.spec.ts | 3 +-- tests/unit/store/utility.spec.ts | 5 ++--- 8 files changed, 10 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6da39e115c..b8af0be914 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,7 +34,6 @@ "source-map-support": "0.5.19", "systeminformation": "5.21.15", "tree-kill": "1.2.2", - "uuid": "9.0.0", "vue": "3.4.26", "vuedraggable": "4.1.0", "vuex": "4.0.2", @@ -54,7 +53,6 @@ "@types/multistream": "4.1.0", "@types/semver": "7.3.9", "@types/unzipper": "0.10.5", - "@types/uuid": "8.3.4", "@types/wicg-file-system-access": "2020.9.6", "@types/yargs": "17.0.32", "@typescript-eslint/eslint-plugin": "7.6.0", @@ -2493,12 +2491,6 @@ "@types/node": "*" } }, - "node_modules/@types/uuid": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz", - "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==", - "dev": true - }, "node_modules/@types/verror": { "version": "1.10.10", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.10.tgz", @@ -11922,14 +11914,6 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, - "node_modules/uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", diff --git a/package.json b/package.json index 10d7069c38..876e9456c5 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,6 @@ "source-map-support": "0.5.19", "systeminformation": "5.21.15", "tree-kill": "1.2.2", - "uuid": "9.0.0", "vue": "3.4.26", "vuedraggable": "4.1.0", "vuex": "4.0.2", @@ -81,7 +80,6 @@ "@types/multistream": "4.1.0", "@types/semver": "7.3.9", "@types/unzipper": "0.10.5", - "@types/uuid": "8.3.4", "@types/wicg-file-system-access": "2020.9.6", "@types/yargs": "17.0.32", "@typescript-eslint/eslint-plugin": "7.6.0", diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index 94c6585f5c..b7f5b5400a 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -158,7 +158,6 @@ import { onActivated, onDeactivated, } from "vue"; -import { v4 as uuidv4 } from "uuid"; import ContextMenu, { ContextMenuItemData, } from "@/components/Menu/ContextMenu.vue"; @@ -705,7 +704,7 @@ const startPreview = (event: MouseEvent, mode: PreviewMode, note?: Note) => { return; } note = { - id: NoteId(uuidv4()), + id: NoteId(crypto.randomUUID()), position: guideLineTicks, duration: snapTicks.value, noteNumber: cursorNoteNumber, diff --git a/src/store/audio.ts b/src/store/audio.ts index 9b50f4d720..b1dc0d06aa 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -1,5 +1,4 @@ import path from "path"; -import { v4 as uuidv4 } from "uuid"; import Encoding from "encoding-japanese"; import { createUILockAction, withProgress } from "./ui"; import { @@ -59,7 +58,7 @@ import { base64ImageToUri, base64ToUri } from "@/helpers/base64Helper"; import { getValueOrThrow, ResultError } from "@/type/result"; function generateAudioKey() { - return AudioKey(uuidv4()); + return AudioKey(crypto.randomUUID()); } function parseTextFile( diff --git a/src/store/preset.ts b/src/store/preset.ts index ac0bbf5561..7ceaa9ccf9 100644 --- a/src/store/preset.ts +++ b/src/store/preset.ts @@ -1,4 +1,3 @@ -import { v4 as uuidv4 } from "uuid"; import { createPartialStore } from "./vuex"; import { PresetStoreState, PresetStoreTypes, State } from "@/store/type"; import { Preset, PresetKey, Voice, VoiceId } from "@/type/preload"; @@ -182,7 +181,7 @@ export const presetStore = createPartialStore({ ADD_PRESET: { async action(context, { presetData }: { presetData: Preset }) { - const newKey = PresetKey(uuidv4()); + const newKey = PresetKey(crypto.randomUUID()); const newPresetItems = { ...context.state.presetItems, [newKey]: presetData, diff --git a/src/store/singing.ts b/src/store/singing.ts index cbba5ead3d..446e43692b 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -1,5 +1,4 @@ import path from "path"; -import { v4 as uuidv4 } from "uuid"; import { toRaw } from "vue"; import { createPartialStore } from "./vuex"; import { createUILockAction } from "./ui"; @@ -1784,7 +1783,7 @@ export const singingStore = createPartialStore({ let notes = midiNotes.map((value): Note => { return { - id: NoteId(uuidv4()), + id: NoteId(crypto.randomUUID()), position: convertPosition(value.ticks, midiTpqn, tpqn), duration: convertDuration( value.ticks, @@ -2071,7 +2070,7 @@ export const singingStore = createPartialStore({ } const note: Note = { - id: NoteId(uuidv4()), + id: NoteId(crypto.randomUUID()), position, duration, noteNumber, @@ -2243,7 +2242,7 @@ export const singingStore = createPartialStore({ } else { // それ以外の場合はノートを追加 notes.push({ - id: NoteId(uuidv4()), + id: NoteId(crypto.randomUUID()), position, duration, noteNumber, @@ -2650,7 +2649,7 @@ export const singingStore = createPartialStore({ const quantizedPastePos = Math.round(pasteOriginPos / snapTicks) * snapTicks; return { - id: NoteId(uuidv4()), + id: NoteId(crypto.randomUUID()), position: quantizedPastePos, duration: Number(note.duration), noteNumber: Number(note.noteNumber), diff --git a/tests/unit/lib/selectPriorPhrase.spec.ts b/tests/unit/lib/selectPriorPhrase.spec.ts index 034b65bd07..4f8b2e2580 100644 --- a/tests/unit/lib/selectPriorPhrase.spec.ts +++ b/tests/unit/lib/selectPriorPhrase.spec.ts @@ -1,5 +1,4 @@ import { it, expect } from "vitest"; -import { v4 as uuidv4 } from "uuid"; import { Phrase, PhraseSourceHash, @@ -19,7 +18,7 @@ const createPhrase = ( firstRestDuration: firstRestDuration * DEFAULT_TPQN, notes: [ { - id: NoteId(uuidv4()), + id: NoteId(crypto.randomUUID()), position: start * DEFAULT_TPQN, duration: (end - start) * DEFAULT_TPQN, noteNumber: 60, diff --git a/tests/unit/store/utility.spec.ts b/tests/unit/store/utility.spec.ts index 20912cafd3..dff4ae2120 100644 --- a/tests/unit/store/utility.spec.ts +++ b/tests/unit/store/utility.spec.ts @@ -1,4 +1,3 @@ -import { v4 as uuidv4 } from "uuid"; import { AccentPhrase, Mora } from "@/openapi"; import { CharacterInfo, @@ -305,13 +304,13 @@ describe("filterCharacterInfosByStyleType", () => { const createCharacterInfo = ( styleTypes: (undefined | "talk" | "frame_decode" | "sing")[], ): CharacterInfo => { - const engineId = EngineId(uuidv4()); + const engineId = EngineId(crypto.randomUUID()); return { portraitPath: "path/to/portrait", metas: { policy: "policy", speakerName: "speakerName", - speakerUuid: SpeakerId(uuidv4()), + speakerUuid: SpeakerId(crypto.randomUUID()), styles: styleTypes.map((styleType) => ({ styleType, styleName: "styleName", From 95af27e4f339792f00fc09e284ca0740559c50ac Mon Sep 17 00:00:00 2001 From: Han Yeong-woo Date: Tue, 4 Jun 2024 07:15:04 +0900 Subject: [PATCH 22/31] =?UTF-8?q?"clone-deep"=E3=82=92=E7=B5=84=E3=81=BF?= =?UTF-8?q?=E8=BE=BC=E3=81=BF=E3=81=AE`structuredClone`=E3=81=AB=E7=BD=AE?= =?UTF-8?q?=E3=81=8D=E6=8F=9B=E3=81=88=E3=82=8B=20(#2114)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hiroshiba --- package-lock.json | 13 +++++-------- package.json | 2 -- src/components/Talk/TalkEditor.vue | 7 +++---- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index b8af0be914..c184a9ffe9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,6 @@ "@quasar/extras": "1.10.10", "async-lock": "1.4.0", "buffer": "6.0.3", - "clone-deep": "4.0.1", "dayjs": "1.10.7", "electron-log": "5.1.2", "electron-window-state": "5.0.3", @@ -44,7 +43,6 @@ "@playwright/test": "1.43.1", "@quasar/vite-plugin": "1.6.0", "@types/async-lock": "1.4.0", - "@types/clone-deep": "4.0.1", "@types/diff": "5.0.3", "@types/electron-devtools-installer": "2.2.2", "@types/encoding-japanese": "1.0.18", @@ -2287,12 +2285,6 @@ "@types/responselike": "^1.0.0" } }, - "node_modules/@types/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-bdkCSkyVHsgl3Goe1y16T9k6JuQx7SiDREkq728QjKmTZkGJZuS8R3gGcnGzVuGBP0mssKrzM/GlMOQxtip9cg==", - "dev": true - }, "node_modules/@types/css-font-loading-module": { "version": "0.0.12", "resolved": "https://registry.npmjs.org/@types/css-font-loading-module/-/css-font-loading-module-0.0.12.tgz", @@ -4604,6 +4596,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, "dependencies": { "is-plain-object": "^2.0.4", "kind-of": "^6.0.2", @@ -7883,6 +7876,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, "dependencies": { "isobject": "^3.0.1" }, @@ -8035,6 +8029,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -8346,6 +8341,7 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -10764,6 +10760,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, "dependencies": { "kind-of": "^6.0.2" }, diff --git a/package.json b/package.json index 876e9456c5..cc8f25de73 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,6 @@ "@quasar/extras": "1.10.10", "async-lock": "1.4.0", "buffer": "6.0.3", - "clone-deep": "4.0.1", "dayjs": "1.10.7", "electron-log": "5.1.2", "electron-window-state": "5.0.3", @@ -71,7 +70,6 @@ "@playwright/test": "1.43.1", "@quasar/vite-plugin": "1.6.0", "@types/async-lock": "1.4.0", - "@types/clone-deep": "4.0.1", "@types/diff": "5.0.3", "@types/electron-devtools-installer": "2.2.2", "@types/encoding-japanese": "1.0.18", diff --git a/src/components/Talk/TalkEditor.vue b/src/components/Talk/TalkEditor.vue index 1e9cfac3cb..abd43a2c8b 100644 --- a/src/components/Talk/TalkEditor.vue +++ b/src/components/Talk/TalkEditor.vue @@ -124,10 +124,9 @@ diff --git a/src/components/Dialog/SettingDialog/SettingDialog.vue b/src/components/Dialog/SettingDialog/SettingDialog.vue index 7285c7dc93..75574e3a90 100644 --- a/src/components/Dialog/SettingDialog/SettingDialog.vue +++ b/src/components/Dialog/SettingDialog/SettingDialog.vue @@ -43,47 +43,22 @@ />
- -
エンジンモード
-
- - - GPU モードの利用には GPU が必要です。Linux は - NVIDIA™ 製 GPU のみ対応しています。 - - -
- - + - - {{ - engineInfos[selectedEngineId].name - }}はCPU版のためGPUモードを利用できません。 - - -
+ {{ + engineInfos[selectedEngineId].name + }}はCPU版のためGPUモードを利用できません。 + +
音声のサンプリングレート
@@ -134,7 +109,7 @@ :modelValue=" experimentalSetting.shouldApplyDefaultPresetOnVoiceChanged " - @updated=" + @update:modelValue=" changeExperimentalSetting( 'shouldApplyDefaultPresetOnVoiceChanged', $event, @@ -147,137 +122,69 @@ title="パラメータの引き継ぎ" description="ONの場合、テキスト欄追加の際に、現在の話速等のパラメータが引き継がれます。" :modelValue="inheritAudioInfoMode" - @updated="changeinheritAudioInfo" + @update:modelValue="changeinheritAudioInfo" + /> + + - -
再生位置を追従
-
- - - 音声再生中の、詳細調整欄の自動スクロールのモードを選べます。 - - -
- - - - - - -
- -
テキスト自動分割
-
- - - テキスト貼り付けの際のテキストの分割箇所を選べます。 - - -
- - - - - - - -
非表示にしたヒントを全て再表示
@@ -429,54 +336,37 @@ title="上書き防止" description="ONの場合、書き出す際に同名ファイルが既にあったとき、ファイル名に連番を付けて別名で保存されます。" :modelValue="savingSetting.avoidOverwrite" - @updated="handleSavingSettingChange('avoidOverwrite', $event)" + @update:modelValue=" + handleSavingSettingChange('avoidOverwrite', $event) + " + /> + - -
文字コード
-
- - - テキストファイルを書き出す際の文字コードを選べます。 - - -
- - -
@@ -484,76 +374,33 @@
外観
- -
テーマ
-
- - - エディタの色を選べます。 - - -
- - -
- - -
フォント
-
- - - エディタのフォントを選べます。 - - -
- - -
+ + @@ -566,13 +413,15 @@ title="マルチエンジン機能" description="ONの場合、複数のVOICEVOX準拠エンジンを利用可能にします。" :modelValue="enableMultiEngine" - @updated="setEnableMultiEngine" + @update:modelValue="setEnableMultiEngine" /> @@ -644,7 +495,7 @@ title="[開発時のみ機能] 調整結果の保持" description="ONの場合、テキスト変更時、同じ読みのアクセント区間内の調整結果を保持します。" :modelValue="experimentalSetting.shouldKeepTuningOnTextChange" - @updated=" + @update:modelValue=" changeExperimentalSetting( 'shouldKeepTuningOnTextChange', $event, @@ -655,7 +506,7 @@ title="ソング:ピッチ編集機能" description="ONの場合、ピッチ編集モードに切り替えて音の高さを変えられるようになります。" :modelValue="experimentalSetting.enablePitchEditInSongEditor" - @updated=" + @update:modelValue=" changeExperimentalSetting( 'enablePitchEditInSongEditor', $event, @@ -671,7 +522,7 @@ title="ソフトウェア利用状況のデータ収集を許可" description="ONの場合、各UIの利用率などのデータが送信され、VOICEVOXの改善に役立てられます。テキストデータや音声データは送信されません。" :modelValue="acceptRetrieveTelemetryComputed" - @updated="acceptRetrieveTelemetryComputed = $event" + @update:modelValue="acceptRetrieveTelemetryComputed = $event" />
@@ -685,6 +536,7 @@ import { computed, ref, watchEffect } from "vue"; import FileNamePatternDialog from "./FileNamePatternDialog.vue"; import ToggleCell from "./ToggleCell.vue"; +import ButtonToggleCell from "./ButtonToggleCell.vue"; import { useStore } from "@/store"; import { isProduction, @@ -694,6 +546,7 @@ import { ActivePointScrollMode, RootMiscSettingType, EngineId, + EditorFontType, } from "@/type/preload"; import { createLogger } from "@/domain/frontend/log"; diff --git a/src/components/Dialog/SettingDialog/ToggleCell.vue b/src/components/Dialog/SettingDialog/ToggleCell.vue index c96694f551..2b8ef85850 100644 --- a/src/components/Dialog/SettingDialog/ToggleCell.vue +++ b/src/components/Dialog/SettingDialog/ToggleCell.vue @@ -1,16 +1,21 @@ + + From d1aff6146a5fc31c82d2085cd1ff0b1569fc0add Mon Sep 17 00:00:00 2001 From: Nanashi Date: Sat, 15 Jun 2024 22:41:58 +0900 Subject: [PATCH 27/31] =?UTF-8?q?Add:=20=E3=83=96=E3=83=A9=E3=82=A6?= =?UTF-8?q?=E3=82=B6=E7=89=88=E3=81=AB=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=81=BF=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=20(#2092)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add: ブラウザ版にファイル読み込みを追加 * Code: 意図を追加 * Change: uuidを足す * Code: コメントを足す * Refactor: checkOsにまとめる * Change: electron側と合わせる * Add: 型を足す * Change: パスの形式を変える * Add: ファイルパスをエラーに追加 Co-Authored-By: Hiroshiba * Code: メモを追加 Co-Authored-By: Hiroshiba * Apply suggestions from code review --------- Co-authored-by: Hiroshiba Co-authored-by: Hiroshiba --- src/backend/browser/fileImpl.ts | 45 +++++++++++++++++++++++++++++ src/backend/browser/sandbox.ts | 51 +++++++++++++++++++++++++-------- src/type/preload.ts | 21 ++++++++------ 3 files changed, 96 insertions(+), 21 deletions(-) diff --git a/src/backend/browser/fileImpl.ts b/src/backend/browser/fileImpl.ts index 25c3f6684c..d60f85f30f 100644 --- a/src/backend/browser/fileImpl.ts +++ b/src/backend/browser/fileImpl.ts @@ -3,6 +3,9 @@ import { directoryHandleStoreKey } from "./contract"; import { openDB } from "./browserConfig"; import { SandboxKey } from "@/type/preload"; import { failure, success } from "@/type/result"; +import { createLogger } from "@/domain/frontend/log"; + +const log = createLogger("fileImpl"); const storeDirectoryHandle = async ( directoryHandle: FileSystemDirectoryHandle, @@ -176,3 +179,45 @@ export const checkFileExistsImpl: (typeof window)[typeof SandboxKey]["checkFileE return Promise.resolve(fileEntries.includes(fileName)); }; + +// FileSystemFileHandleを保持するMap。キーは生成した疑似パス。 +const fileHandleMap: Map = new Map(); + +// ファイル選択ダイアログを開く +// 返り値はファイルパスではなく、疑似パスを返す +export const showOpenFilePickerImpl = async (options: { + multiple: boolean; + fileTypes: { + description: string; + accept: Record; + }[]; +}) => { + try { + const handles = await showOpenFilePicker({ + excludeAcceptAllOption: true, + multiple: options.multiple, + types: options.fileTypes, + }); + const paths = []; + for (const handle of handles) { + const fakePath = `-${handle.name}`; + fileHandleMap.set(fakePath, handle); + paths.push(fakePath); + } + return handles.length > 0 ? paths : undefined; + } catch (e) { + log.warn(`showOpenFilePicker error: ${e}`); + return undefined; + } +}; + +// 指定した疑似パスのファイルを読み込む +export const readFileImpl = async (filePath: string) => { + const fileHandle = fileHandleMap.get(filePath); + if (fileHandle == undefined) { + return failure(new Error(`ファイルが見つかりません: ${filePath}`)); + } + const file = await fileHandle.getFile(); + const buffer = await file.arrayBuffer(); + return success(buffer); +}; diff --git a/src/backend/browser/sandbox.ts b/src/backend/browser/sandbox.ts index f3e298054d..f2f5451085 100644 --- a/src/backend/browser/sandbox.ts +++ b/src/backend/browser/sandbox.ts @@ -1,7 +1,9 @@ import { defaultEngine } from "./contract"; import { checkFileExistsImpl, + readFileImpl, showOpenDirectoryDialogImpl, + showOpenFilePickerImpl, writeFileImpl, } from "./fileImpl"; import { getConfigManager } from "./browserConfig"; @@ -127,10 +129,18 @@ export const api: Sandbox = { } }); }, - showProjectLoadDialog(/* obj: { title: string } */) { - throw new Error( - "ブラウザ版では現在ファイルの読み込みをサポートしていません", - ); + async showProjectLoadDialog() { + return showOpenFilePickerImpl({ + multiple: false, + fileTypes: [ + { + description: "Voicevox Project File", + accept: { + "application/json": [".vvproj"], + }, + }, + ], + }); }, showMessageDialog(obj: { type: "none" | "info" | "error" | "question" | "warning"; @@ -156,18 +166,35 @@ export const api: Sandbox = { `Not implemented: showQuestionDialog, request: ${JSON.stringify(obj)}`, ); }, - showImportFileDialog(/* obj: { title: string } */) { - throw new Error( - "ブラウザ版では現在ファイルの読み込みをサポートしていません", - ); + async showImportFileDialog(obj: { + name?: string; + extensions?: string[]; + title: string; + }) { + const fileHandle = await showOpenFilePickerImpl({ + multiple: false, + fileTypes: [ + { + description: obj.name ?? "Text", + accept: obj.extensions + ? { + "application/octet-stream": obj.extensions.map( + (ext) => `.${ext}`, + ), + } + : { + "plain/text": [".txt"], + }, + }, + ], + }); + return fileHandle?.[0]; }, writeFile(obj: { filePath: string; buffer: ArrayBuffer }) { return writeFileImpl(obj); }, - readFile(/* obj: { filePath: string } */) { - throw new Error( - "ブラウザ版では現在ファイルの読み込みをサポートしていません", - ); + readFile(obj: { filePath: string }) { + return readFileImpl(obj.filePath); }, isAvailableGPUMode() { // TODO: WebAssembly版をサポートする時に実装する diff --git a/src/type/preload.ts b/src/type/preload.ts index 67b32c34cb..01a3bdecb7 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -7,25 +7,28 @@ export const isProduction = import.meta.env.MODE === "production"; export const isElectron = import.meta.env.VITE_TARGET === "electron"; export const isBrowser = import.meta.env.VITE_TARGET === "browser"; -// electronのメイン・レンダラープロセス内、ブラウザ内どこでも使用可能なmacOS判定 -function checkIsMac(): boolean { - let isMac: boolean | undefined = undefined; +// electronのメイン・レンダラープロセス内、ブラウザ内どこでも使用可能なOS判定 +function checkOs(os: "windows" | "mac"): boolean { + let isSpecifiedOs: boolean | undefined = undefined; if (process?.platform) { // electronのメインプロセス用 - isMac = process.platform === "darwin"; + isSpecifiedOs = + process.platform === (os === "windows" ? "win32" : "darwin"); } else if (navigator?.userAgentData) { // electronのレンダラープロセス用、Chrome系統が実装する実験的機能 - isMac = navigator.userAgentData.platform.toLowerCase().includes("mac"); + isSpecifiedOs = navigator.userAgentData.platform.toLowerCase().includes(os); } else if (navigator?.platform) { // ブラウザ用、非推奨機能 - isMac = navigator.platform.toLowerCase().includes("mac"); + isSpecifiedOs = navigator.platform.toLowerCase().includes(os); } else { // ブラウザ用、不正確 - isMac = navigator.userAgent.toLowerCase().includes("mac"); + isSpecifiedOs = navigator.userAgent.toLowerCase().includes(os); } - return isMac; + return isSpecifiedOs; } -export const isMac = checkIsMac(); + +export const isMac = checkOs("mac"); +export const isWindows = checkOs("windows"); const urlStringSchema = z.string().url().brand("URL"); export type UrlString = z.infer; From 343f188dfc430c5a2f915ef04d299a19235d25d2 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Sun, 16 Jun 2024 03:41:44 +0900 Subject: [PATCH 28/31] =?UTF-8?q?[docs]=20=E4=BD=BF=E3=81=84=E6=96=B9?= =?UTF-8?q?=E3=81=AB=E6=AD=8C=E8=A9=9E=E3=81=AE=E5=85=A5=E5=8A=9B=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=20(#2117)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 使い方に歌詞の入力を追加 * Apply suggestions from code review --- public/howtouse.md | 6 ++++++ public/res/song2.png | Bin 0 -> 11416 bytes 2 files changed, 6 insertions(+) create mode 100644 public/res/song2.png diff --git a/public/howtouse.md b/public/howtouse.md index e316c3a0ab..bb49033d75 100644 --- a/public/howtouse.md +++ b/public/howtouse.md @@ -299,6 +299,12 @@ VOICEVOX では、歌声合成機能がプロトタイプ版として提供さ ソング機能は鋭意制作中です。フィードバックをお待ちしています。 +### 歌詞の入力 + +ノートをダブルクリックすることで歌詞を入力できます。複数の文字を入力すれば一括入力できます。 + +ノートに複数の文字を入力することで、後ろのノートに歌詞が送られる様子が写っています + ### 音域調整 「音域調整」の値が大きいほど高い音域で、小さいほど低い音域でうまく歌えるようになります。 diff --git a/public/res/song2.png b/public/res/song2.png new file mode 100644 index 0000000000000000000000000000000000000000..be2014418c89e5a985e4e0158fec5d386f8d9c74 GIT binary patch literal 11416 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 J)ndl){|DcZ^Q`~? literal 0 HcmV?d00001 From 8c944eb202a7a18cee7e137f8802920fd43915b0 Mon Sep 17 00:00:00 2001 From: Nanashi Date: Sun, 16 Jun 2024 03:52:12 +0900 Subject: [PATCH 29/31] =?UTF-8?q?Add:=20UtaFormatix=E3=82=92=E5=B0=8E?= =?UTF-8?q?=E5=85=A5=E3=81=97=E3=81=A6=E3=82=A4=E3=83=B3=E3=83=9D=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=81=A7=E3=81=8D=E3=82=8B=E5=AF=BE=E5=BF=9C=E5=BD=A2?= =?UTF-8?q?=E5=BC=8F=E3=82=92=E5=A2=97=E3=82=84=E3=81=99=20(#2104)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add: @sevenc-nanashi/utaformatix-tsを入れる * Change: ImportExternalProjectDialogにする * Change: IMPORT_MIDI_FILEをIMPORT_EXTERNAL_PROJECT_FILEに * Add: 歌詞を変換する * Delete: midi-fileを依存から消す * Add: parseFailedのメッセージを追加 * Change: convertVowelConnectionsを有効にする * Add: vvprojを読み込めるように * Change: externalSongProjectにする * Change: 残っていたmidiを変える * Change: 0.2.3に更新 * Fix: ファイル名を修正 * Fix: 型エラーを修正 * Code: フォーマット * Add: 拡張子一覧を追加 * Update: DIの方を更新 * Change: プロジェクトファイルの順番を変える Co-authored-by: Hiroshiba * Change: CeVIO AI -> CeVIO * Refactor: projectFileErrorを良い感じにする * Change: SMFの表記を変える * Change: details-summaryで囲む * Change: Project -> UfProject * Change: PARSE_PROJECT_FILEに移動 * Change: ufDataの変換を別ファイルに移動 * Add: テストを追加 * Change: midi -> ufProjectToSongState * Fix: Add忘れ * Change: log.errorの引数を変える * Update: パスを更新 * Code: コメントを追加 * Change: プロジェクトファイルのインポート -> インポート * Refactor: 整理 * Fix: パスを更新 * Add: vvprojの読み込みお変える * Update: Update utaformatix-ts * Update: 本家の色々に追従 * Delete: 不要なフォールバックを削除 * Fix: 変換を修正 * Update: テスTを更新 * Change: uuidv4 -> crypto.randomUUID * Change: コンバーターの名前を変える * Change: SongState -> VoicevoxScore * Code: npm run fmt * Add: TODOを追加 * Code: TODOを追加 Co-Authored-By: Hiroshiba * Code: TODOを削除 * Code: コメントの解釈を一つに * Code: TODOを追加 * Refactor: 良い感じにする * Add: エクスポートのテストを追加 * 改変してみた * Delete: 不要なroundを削除 * Delete: 不要なimportを削除 --------- Co-authored-by: Hiroshiba Co-authored-by: Hiroshiba --- .npmrc | 1 + package-lock.json | 50 +- package.json | 2 +- src/components/Dialog/AllDialog.vue | 12 +- src/components/Dialog/ImportMidiDialog.vue | 180 ------ .../Dialog/ImportSongProjectDialog.vue | 302 +++++++++ src/components/Sing/menuBarData.ts | 34 +- src/domain/frontend/log.ts | 4 +- src/sing/midi.ts | 167 ----- src/sing/utaformatixProject/common.ts | 8 + src/sing/utaformatixProject/fromVoicevox.ts | 38 ++ src/sing/utaformatixProject/toVoicevox.ts | 121 ++++ src/store/project.ts | 80 ++- src/store/singing.ts | 588 ++---------------- src/store/type.ts | 24 +- src/store/ui.ts | 4 +- src/styles/_index.scss | 12 +- tests/unit/lib/midi/midi.spec.ts | 59 -- .../__snapshots__/export.spec.ts.snap | 37 ++ .../lib/{midi => utaformatixProject}/bpm.mid | Bin .../lib/utaformatixProject/export.spec.ts | 35 ++ .../lib/utaformatixProject/import.spec.ts | 61 ++ .../{midi => utaformatixProject}/synthv.mid | Bin .../{midi => utaformatixProject}/timeSig.mid | Bin 24 files changed, 752 insertions(+), 1067 deletions(-) delete mode 100644 src/components/Dialog/ImportMidiDialog.vue create mode 100644 src/components/Dialog/ImportSongProjectDialog.vue delete mode 100644 src/sing/midi.ts create mode 100644 src/sing/utaformatixProject/common.ts create mode 100644 src/sing/utaformatixProject/fromVoicevox.ts create mode 100644 src/sing/utaformatixProject/toVoicevox.ts delete mode 100644 tests/unit/lib/midi/midi.spec.ts create mode 100644 tests/unit/lib/utaformatixProject/__snapshots__/export.spec.ts.snap rename tests/unit/lib/{midi => utaformatixProject}/bpm.mid (100%) create mode 100644 tests/unit/lib/utaformatixProject/export.spec.ts create mode 100644 tests/unit/lib/utaformatixProject/import.spec.ts rename tests/unit/lib/{midi => utaformatixProject}/synthv.mid (100%) rename tests/unit/lib/{midi => utaformatixProject}/timeSig.mid (100%) diff --git a/.npmrc b/.npmrc index d9ca8860b0..335b2f1803 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,3 @@ engine-strict=true save-exact=true +@jsr:registry=https://npm.jsr.io diff --git a/package-lock.json b/package-lock.json index b8fe0a6ff0..f0b1d9500b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@gtm-support/vue-gtm": "1.2.3", "@quasar/extras": "1.10.10", + "@sevenc-nanashi/utaformatix-ts": "npm:@jsr/sevenc-nanashi__utaformatix-ts@0.3.0", "async-lock": "1.4.0", "dayjs": "1.10.7", "electron-log": "5.1.2", @@ -22,7 +23,6 @@ "hotkeys-js": "3.13.6", "immer": "9.0.21", "markdown-it": "13.0.2", - "midi-file": "1.2.4", "move-file": "3.0.0", "multistream": "4.1.0", "pixi.js": "7.4.0", @@ -2199,6 +2199,16 @@ "win32" ] }, + "node_modules/@sevenc-nanashi/utaformatix-ts": { + "name": "@jsr/sevenc-nanashi__utaformatix-ts", + "version": "0.3.0", + "resolved": "https://npm.jsr.io/~/11/@jsr/sevenc-nanashi__utaformatix-ts/0.3.0.tgz", + "integrity": "sha512-D6Y6lkxOawpv3LKSgrTGKLquuzaFvkwNThSGDXT5QBnfk7VE4bFbqJr9JlgjvAdh5sNQVGPku6uMdIdvSDxzAg==", + "dependencies": { + "jszip": "^3.10.1", + "utaformatix-data": "^1.1.0" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -4504,8 +4514,7 @@ "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "devOptional": true + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, "node_modules/crc": { "version": "3.8.0", @@ -7105,8 +7114,7 @@ "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", - "dev": true + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" }, "node_modules/immer": { "version": "9.0.21", @@ -7815,7 +7823,6 @@ "version": "3.10.1", "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", - "dev": true, "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", @@ -7826,14 +7833,12 @@ "node_modules/jszip/node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "node_modules/jszip/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -7847,14 +7852,12 @@ "node_modules/jszip/node_modules/safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "node_modules/jszip/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "dependencies": { "safe-buffer": "~5.1.0" } @@ -7982,7 +7985,6 @@ "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, "dependencies": { "immediate": "~3.0.5" } @@ -8320,11 +8322,6 @@ "node": ">=8.6" } }, - "node_modules/midi-file": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/midi-file/-/midi-file-1.2.4.tgz", - "integrity": "sha512-B5SnBC6i2bwJIXTY9MElIydJwAmnKx+r5eJ1jknTLetzLflEl0GWveuBB6ACrQpecSRkOB6fhTx1PwXk2BVxnA==" - }, "node_modules/miller-rabin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", @@ -8994,8 +8991,7 @@ "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "node_modules/parent-module": { "version": "1.0.1", @@ -9390,8 +9386,7 @@ "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "node_modules/progress": { "version": "2.0.3", @@ -10173,8 +10168,7 @@ "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", - "dev": true + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, "node_modules/sha.js": { "version": "2.4.11", @@ -11193,6 +11187,14 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" }, + "node_modules/utaformatix-data": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/utaformatix-data/-/utaformatix-data-1.1.0.tgz", + "integrity": "sha512-1AoxBvRMkXjifHqvIpTnvLLGo3Qyj1Q4PSQLgKd8e6RMq4HA5o6QNI4ila9BO1fkJAWA9Azj/hVANHWBkpbVvg==", + "engines": { + "node": ">=10" + } + }, "node_modules/utf8-byte-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.4.tgz", diff --git a/package.json b/package.json index 7f3d7e90ea..712f0241e7 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "dependencies": { "@gtm-support/vue-gtm": "1.2.3", "@quasar/extras": "1.10.10", + "@sevenc-nanashi/utaformatix-ts": "npm:@jsr/sevenc-nanashi__utaformatix-ts@0.3.0", "async-lock": "1.4.0", "dayjs": "1.10.7", "electron-log": "5.1.2", @@ -46,7 +47,6 @@ "hotkeys-js": "3.13.6", "immer": "9.0.21", "markdown-it": "13.0.2", - "midi-file": "1.2.4", "move-file": "3.0.0", "multistream": "4.1.0", "pixi.js": "7.4.0", diff --git a/src/components/Dialog/AllDialog.vue b/src/components/Dialog/AllDialog.vue index 4711b8aea6..30d538c65b 100644 --- a/src/components/Dialog/AllDialog.vue +++ b/src/components/Dialog/AllDialog.vue @@ -22,7 +22,7 @@ - + diff --git a/src/components/Dialog/ImportMidiDialog.vue b/src/components/Dialog/ImportMidiDialog.vue deleted file mode 100644 index 6abdf11d33..0000000000 --- a/src/components/Dialog/ImportMidiDialog.vue +++ /dev/null @@ -1,180 +0,0 @@ - - - diff --git a/src/components/Dialog/ImportSongProjectDialog.vue b/src/components/Dialog/ImportSongProjectDialog.vue new file mode 100644 index 0000000000..9b6153cba0 --- /dev/null +++ b/src/components/Dialog/ImportSongProjectDialog.vue @@ -0,0 +1,302 @@ + + + diff --git a/src/components/Sing/menuBarData.ts b/src/components/Sing/menuBarData.ts index 56af239d8a..b13733822e 100644 --- a/src/components/Sing/menuBarData.ts +++ b/src/components/Sing/menuBarData.ts @@ -7,23 +7,13 @@ export const useMenuBarData = () => { const uiLocked = computed(() => store.getters.UI_LOCKED); const isNotesSelected = computed(() => store.state.selectedNoteIds.size > 0); - const importMidiFile = async () => { + const importExternalSongProject = async () => { if (uiLocked.value) return; await store.dispatch("SET_DIALOG_OPEN", { - isImportMidiDialogOpen: true, + isImportSongProjectDialogOpen: true, }); }; - const importMusicXMLFile = async () => { - if (uiLocked.value) return; - 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", {}); @@ -41,25 +31,9 @@ export const useMenuBarData = () => { { type: "separator" }, { type: "button", - label: "MIDI読み込み", - onClick: () => { - importMidiFile(); - }, - disableWhenUiLocked: true, - }, - { - type: "button", - label: "MusicXML読み込み", - onClick: () => { - importMusicXMLFile(); - }, - disableWhenUiLocked: true, - }, - { - type: "button", - label: "UST読み込み", + label: "インポート", onClick: () => { - importUstFile(); + importExternalSongProject(); }, disableWhenUiLocked: true, }, diff --git a/src/domain/frontend/log.ts b/src/domain/frontend/log.ts index ef4ab4554a..3b7a224d9f 100644 --- a/src/domain/frontend/log.ts +++ b/src/domain/frontend/log.ts @@ -3,8 +3,8 @@ export function createLogger(scope: string) { const createInner = (method: "logInfo" | "logError" | "logWarn") => - (message: string, ...args: unknown[]) => { - window.backend[method](`[${scope}] ${message}`, ...args); + (...args: unknown[]) => { + window.backend[method](`[${scope}] ${args[0]}`, ...args.slice(1)); }; return { info: createInner("logInfo"), diff --git a/src/sing/midi.ts b/src/sing/midi.ts deleted file mode 100644 index c493d9c18c..0000000000 --- a/src/sing/midi.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { - MidiData, - parseMidi, - MidiTrackNameEvent, - MidiEvent, - MidiSetTempoEvent, - MidiTimeSignatureEvent, - MidiLyricsEvent, - MidiNoteOnEvent, - MidiNoteOffEvent, -} from "midi-file"; -type Tempo = { ticks: number; bpm: number }; -type TimeSignature = { - ticks: number; - numerator: number; - denominator: number; -}; -type Note = { - ticks: number; - noteNumber: number; - duration: number; - lyric?: string; -}; - -// BPMの精度。(小数点以下の桁数) -const bpmPrecision = 2; - -/** - * midi-fileの軽いラッパー。 - */ -export class Midi { - data: MidiData; - tracks: Track[]; - constructor(data: ArrayBuffer) { - this.data = parseMidi(new Uint8Array(data)); - this.tracks = this.data.tracks.map((track) => new Track(track)); - } - - get header() { - return this.data.header; - } - - get ticksPerBeat() { - const maybeTicksPerBeat = this.data.header.ticksPerBeat; - if (maybeTicksPerBeat == undefined) { - throw new Error("ticksPerBeat is undefined"); - } - return maybeTicksPerBeat; - } - - get tempos(): Tempo[] { - const tempos = this.tracks.flatMap((track) => track.tempos); - tempos.sort((a, b) => a.ticks - b.ticks); - return tempos; - } - - get timeSignatures(): TimeSignature[] { - const timeSignatures = this.tracks.flatMap((track) => track.timeSignatures); - timeSignatures.sort((a, b) => a.ticks - b.ticks); - return timeSignatures; - } -} - -type MidiEventWithTime = { - time: number; -} & T; -export class Track { - readonly data: MidiData["tracks"][0]; - readonly events: MidiEventWithTime[]; - readonly notes: Note[]; - constructor(data: MidiData["tracks"][0]) { - this.data = data; - let time = 0; - this.events = data.map((event) => { - time += event.deltaTime; - return { time, ...event }; - }); - const lyrics = this.events.filter( - (e) => e.type === "lyrics", - ) as MidiEventWithTime[]; - const lyricsMap = new Map( - lyrics.map((e) => { - // midi-fileはUTF-8としてデコードしてくれないので、ここでデコードする - const buffer = new Uint8Array( - e.text.split("").map((c) => c.charCodeAt(0)), - ); - const decoder = new TextDecoder("utf-8"); - return [e.time, decoder.decode(buffer)]; - }), - ); - - const noteOnOffs = this.events.filter( - (e) => e.type === "noteOn" || e.type === "noteOff", - ) as MidiEventWithTime[]; - noteOnOffs.sort((a, b) => a.time - b.time); - this.notes = []; - const temporaryNotes = new Map< - number, - { noteNumber: number; time: number } - >(); - for (const event of noteOnOffs) { - if (event.type === "noteOn") { - if (temporaryNotes.has(event.noteNumber)) { - throw new Error("noteOn without noteOff"); - } - temporaryNotes.set(event.noteNumber, { - noteNumber: event.noteNumber, - time: event.time, - }); - } else { - const note = temporaryNotes.get(event.noteNumber); - if (!note) { - throw new Error("noteOff without noteOn"); - } - temporaryNotes.delete(event.noteNumber); - this.notes.push({ - ticks: note.time, - noteNumber: note.noteNumber, - duration: event.time - note.time, - // 同じタイミングの歌詞をノートの歌詞として使う - lyric: lyricsMap.get(note.time), - }); - } - } - } - - get name() { - const nameEvent = this.data.find( - (e) => e.type === "trackName", - ) as MidiTrackNameEvent; - if (!nameEvent) { - return ""; - } - return nameEvent.text; - } - - get tempos(): Tempo[] { - const tempoEvents = this.events.filter( - (e) => e.type === "setTempo", - ) as MidiEventWithTime[]; - - const tempos = tempoEvents.map((e) => ({ - ticks: e.time, - bpm: - Math.round( - ((60 * 1000000) / e.microsecondsPerBeat) * 10 ** bpmPrecision, - ) / - 10 ** bpmPrecision, - })); - tempos.sort((a, b) => a.ticks - b.ticks); - return tempos; - } - - get timeSignatures(): TimeSignature[] { - const timeSignatureEvents = this.events.filter( - (e) => e.type === "timeSignature", - ) as MidiEventWithTime[]; - - const timeSignatures = timeSignatureEvents.map((e) => ({ - ticks: e.time, - numerator: e.numerator, - denominator: e.denominator, - })); - timeSignatures.sort((a, b) => a.ticks - b.ticks); - return timeSignatures; - } -} diff --git a/src/sing/utaformatixProject/common.ts b/src/sing/utaformatixProject/common.ts new file mode 100644 index 0000000000..dee6839344 --- /dev/null +++ b/src/sing/utaformatixProject/common.ts @@ -0,0 +1,8 @@ +import { Tempo, TimeSignature, Track } from "@/store/type"; + +export type VoicevoxScore = { + tracks: Track[]; + tpqn: number; + tempos: Tempo[]; + timeSignatures: TimeSignature[]; +}; diff --git a/src/sing/utaformatixProject/fromVoicevox.ts b/src/sing/utaformatixProject/fromVoicevox.ts new file mode 100644 index 0000000000..fba4a3ee9b --- /dev/null +++ b/src/sing/utaformatixProject/fromVoicevox.ts @@ -0,0 +1,38 @@ +// TODO: エクスポート機能を実装する + +import { Project as UfProject, UfData } from "@sevenc-nanashi/utaformatix-ts"; +import { VoicevoxScore } from "./common"; + +/** Voicevoxの楽譜データをUtaformatixのProjectに変換する */ +export const ufProjectFromVoicevox = ( + { tracks, tpqn, tempos, timeSignatures }: VoicevoxScore, + projectName: string, +): UfProject => { + const convertTicks = (ticks: number) => Math.round((ticks / tpqn) * 480); + const ufData: UfData = { + formatVersion: 1, + project: { + measurePrefix: 0, + name: projectName, + tempos: tempos.map((tempo) => ({ + tickPosition: convertTicks(tempo.position), + bpm: tempo.bpm, + })), + timeSignatures: timeSignatures.map((timeSignature) => ({ + measurePosition: timeSignature.measureNumber, + numerator: timeSignature.beats, + denominator: timeSignature.beatType, + })), + tracks: tracks.map((track) => ({ + name: `無名トラック`, + notes: track.notes.map((note) => ({ + key: note.noteNumber, + tickOn: convertTicks(note.position), + tickOff: convertTicks(note.position + note.duration), + lyric: note.lyric, + })), + })), + }, + }; + return new UfProject(ufData); +}; diff --git a/src/sing/utaformatixProject/toVoicevox.ts b/src/sing/utaformatixProject/toVoicevox.ts new file mode 100644 index 0000000000..f3e2448d67 --- /dev/null +++ b/src/sing/utaformatixProject/toVoicevox.ts @@ -0,0 +1,121 @@ +import { Project as UfProject } from "@sevenc-nanashi/utaformatix-ts"; +import { VoicevoxScore } from "./common"; +import { DEFAULT_TPQN, createDefaultTrack } from "@/sing/domain"; +import { getDoremiFromNoteNumber } from "@/sing/viewHelper"; +import { NoteId } from "@/type/preload"; +import { Note, Tempo, TimeSignature, Track } from "@/store/type"; + +/** UtaformatixのプロジェクトをVoicevoxの楽譜データに変換する */ +export const ufProjectToVoicevox = (project: UfProject): VoicevoxScore => { + const convertPosition = ( + position: number, + sourceTpqn: number, + targetTpqn: number, + ) => { + return Math.round(position * (targetTpqn / sourceTpqn)); + }; + + const convertDuration = ( + startPosition: number, + endPosition: number, + sourceTpqn: number, + targetTpqn: number, + ) => { + const convertedEndPosition = convertPosition( + endPosition, + sourceTpqn, + targetTpqn, + ); + const convertedStartPosition = convertPosition( + startPosition, + sourceTpqn, + targetTpqn, + ); + return Math.max(1, convertedEndPosition - convertedStartPosition); + }; + + const removeDuplicateTempos = (tempos: Tempo[]) => { + return tempos.filter((value, index, array) => { + return ( + index === array.length - 1 || + value.position !== array[index + 1].position + ); + }); + }; + + const removeDuplicateTimeSignatures = (timeSignatures: TimeSignature[]) => { + return timeSignatures.filter((value, index, array) => { + return ( + index === array.length - 1 || + value.measureNumber !== array[index + 1].measureNumber + ); + }); + }; + + // 歌詞をひらがなの単独音に変換する + const convertedProject = project.convertJapaneseLyrics("auto", "KanaCv", { + convertVowelConnections: true, + }); + + // 480は固定値。 + // https://github.com/sdercolin/utaformatix-data?tab=readme-ov-file#value-conventions + const projectTpqn = 480; + const projectTempos = convertedProject.tempos; + const projectTimeSignatures = convertedProject.timeSignatures; + + const tpqn = DEFAULT_TPQN; + + const tracks: Track[] = convertedProject.tracks.map((projectTrack) => { + const trackNotes = projectTrack.notes; + + trackNotes.sort((a, b) => a.tickOn - b.tickOn); + + const notes = trackNotes.map((value): Note => { + return { + id: NoteId(crypto.randomUUID()), + position: convertPosition(value.tickOn, projectTpqn, tpqn), + duration: convertDuration( + value.tickOn, + value.tickOff, + projectTpqn, + tpqn, + ), + noteNumber: value.key, + lyric: value.lyric || getDoremiFromNoteNumber(value.key), + }; + }); + + return { + ...createDefaultTrack(), + notes, + }; + }); + + let tempos = projectTempos.map((value): Tempo => { + return { + position: convertPosition(value.tickPosition, projectTpqn, tpqn), + bpm: value.bpm, + }; + }); + tempos = removeDuplicateTempos(tempos); + + let timeSignatures: TimeSignature[] = []; + for (const ts of projectTimeSignatures) { + const beats = ts.numerator; + const beatType = ts.denominator; + timeSignatures.push({ + // UtaFormatixは0から始まるので+1する + measureNumber: ts.measurePosition + 1, + beats, + beatType, + }); + } + timeSignatures = removeDuplicateTimeSignatures(timeSignatures); + + return { + tracks, + tpqn, + tempos, + timeSignatures, + }; +}; diff --git a/src/store/project.ts b/src/store/project.ts index 8b6feae799..64f14a2910 100755 --- a/src/store/project.ts +++ b/src/store/project.ts @@ -130,6 +130,29 @@ export const projectStore = createPartialStore({ ), }, + PARSE_PROJECT_FILE: { + async action({ dispatch, getters }, { projectJson }) { + const projectData = JSON.parse(projectJson); + + const characterInfos = getters.USER_ORDERED_CHARACTER_INFOS("talk"); + if (characterInfos == undefined) + throw new Error("characterInfos == undefined"); + + const parsedProjectData = await migrateProjectFileObject(projectData, { + fetchMoraData: (payload) => dispatch("FETCH_MORA_DATA", payload), + voices: characterInfos.flatMap((characterInfo) => + characterInfo.metas.styles.map((style) => ({ + engineId: style.engineId, + speakerId: characterInfo.metas.speakerUuid, + styleId: style.styleId, + })), + ), + }); + + return parsedProjectData; + }, + }, + LOAD_PROJECT_FILE: { /** * プロジェクトファイルを読み込む。読み込めたかの成否が返る。 @@ -137,7 +160,7 @@ export const projectStore = createPartialStore({ */ action: createUILockAction( async ( - context, + { dispatch, commit, getters }, { filePath, confirm }: { filePath?: string; confirm?: boolean }, ) => { if (!filePath) { @@ -157,58 +180,31 @@ export const projectStore = createPartialStore({ .readFile({ filePath }) .then(getValueOrThrow); - await context.dispatch("APPEND_RECENTLY_USED_PROJECT", { + await dispatch("APPEND_RECENTLY_USED_PROJECT", { filePath, }); const text = new TextDecoder("utf-8").decode(buf).trim(); - const projectData = JSON.parse(text); - - const characterInfos = - context.getters.USER_ORDERED_CHARACTER_INFOS("talk"); - if (characterInfos == undefined) - throw new Error("characterInfos == undefined"); - - const parsedProjectData = await migrateProjectFileObject( - projectData, - { - fetchMoraData: (payload) => - context.dispatch("FETCH_MORA_DATA", payload), - voices: characterInfos.flatMap((characterInfo) => - characterInfo.metas.styles.map((style) => ({ - engineId: style.engineId, - speakerId: characterInfo.metas.speakerUuid, - styleId: style.styleId, - })), - ), - }, - ); + const parsedProjectData = await dispatch("PARSE_PROJECT_FILE", { + projectJson: text, + }); - if (confirm !== false && context.getters.IS_EDITED) { - const result = await context.dispatch( - "SAVE_OR_DISCARD_PROJECT_FILE", - { - additionalMessage: - "プロジェクトをロードすると現在のプロジェクトは破棄されます。", - }, - ); + if (confirm !== false && getters.IS_EDITED) { + const result = await dispatch("SAVE_OR_DISCARD_PROJECT_FILE", { + additionalMessage: + "プロジェクトをロードすると現在のプロジェクトは破棄されます。", + }); if (result == "canceled") { return false; } } - await applyTalkProjectToStore( - context.dispatch, - parsedProjectData.talk, - ); - await applySongProjectToStore( - context.dispatch, - parsedProjectData.song, - ); + await applyTalkProjectToStore(dispatch, parsedProjectData.talk); + await applySongProjectToStore(dispatch, parsedProjectData.song); - context.commit("SET_PROJECT_FILEPATH", { filePath }); - context.commit("SET_SAVED_LAST_COMMAND_UNIX_MILLISEC", null); - context.commit("CLEAR_COMMANDS"); + commit("SET_PROJECT_FILEPATH", { filePath }); + commit("SET_SAVED_LAST_COMMAND_UNIX_MILLISEC", null); + commit("CLEAR_COMMANDS"); return true; } catch (err) { window.backend.logError(err); diff --git a/src/store/singing.ts b/src/store/singing.ts index 446e43692b..af159545d9 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -24,7 +24,6 @@ import { } from "./type"; import { sanitizeFileName } from "./utility"; import { EngineId, NoteId, StyleId } from "@/type/preload"; -import { Midi } from "@/sing/midi"; import { FrameAudioQuery, Note as NoteForRequestToEngine } from "@/openapi"; import { ResultError, getValueOrThrow } from "@/type/result"; import { @@ -43,7 +42,6 @@ import { } from "@/sing/audioRendering"; import { selectPriorPhrase, - getMeasureDuration, getNoteDuration, isValidNote, isValidSnapType, @@ -79,7 +77,6 @@ import { removeNotesFromOverlappingNoteInfos, updateNotesOfOverlappingNoteInfos, } from "@/sing/storeHelper"; -import { getDoremiFromNoteNumber } from "@/sing/viewHelper"; import { AnimationTimer, createPromiseThatResolvesWhen, @@ -90,6 +87,7 @@ import { getWorkaroundKeyRangeAdjustment } from "@/sing/workaroundKeyRangeAdjust import { createLogger } from "@/domain/frontend/log"; import { noteSchema } from "@/domain/project/schema"; import { getOrThrow } from "@/helpers/mapHelper"; +import { ufProjectToVoicevox } from "@/sing/utaformatixProject/toVoicevox"; const logger = createLogger("store/singing"); @@ -1692,140 +1690,21 @@ export const singingStore = createPartialStore({ }), }, - IMPORT_MIDI_FILE: { + // TODO: Undoできるようにする + IMPORT_UTAFORMATIX_PROJECT: { action: createUILockAction( - async ( - { state, dispatch }, - { filePath, trackIndex = 0 }: { filePath: string; trackIndex: number }, - ) => { - const convertPosition = ( - position: number, - sourceTpqn: number, - targetTpqn: number, - ) => { - return Math.round(position * (targetTpqn / sourceTpqn)); - }; - - const convertDuration = ( - startPosition: number, - endPosition: number, - sourceTpqn: number, - targetTpqn: number, - ) => { - const convertedEndPosition = convertPosition( - endPosition, - sourceTpqn, - targetTpqn, - ); - const convertedStartPosition = convertPosition( - startPosition, - sourceTpqn, - targetTpqn, - ); - return Math.max(1, convertedEndPosition - convertedStartPosition); - }; + async ({ state, commit, dispatch }, { project, trackIndex = 0 }) => { + const { tempos, timeSignatures, tracks, tpqn } = + ufProjectToVoicevox(project); - const getTopNotes = (notes: Note[]) => { - const topNotes: Note[] = []; - for (const note of notes) { - if (topNotes.length === 0) { - topNotes.push(note); - continue; - } - const topNote = topNotes[topNotes.length - 1]; - const topNoteEndPos = topNote.position + topNote.duration; - if (topNoteEndPos <= note.position) { - topNotes.push(note); - continue; - } - if (topNote.noteNumber < note.noteNumber) { - topNotes.pop(); - topNotes.push(note); - } - } - return topNotes; - }; - - const removeDuplicateTempos = (tempos: Tempo[]) => { - return tempos.filter((value, index, array) => { - return ( - index === array.length - 1 || - value.position !== array[index + 1].position - ); - }); - }; - - const removeDuplicateTimeSignatures = ( - timeSignatures: TimeSignature[], - ) => { - return timeSignatures.filter((value, index, array) => { - return ( - index === array.length - 1 || - value.measureNumber !== array[index + 1].measureNumber - ); - }); - }; - - // NOTE: トラック選択のために一度ファイルを読み込んでいるので、Midiを渡すなどでもよさそう - const midiData = getValueOrThrow( - await window.backend.readFile({ filePath }), - ); - const midi = new Midi(midiData); - const midiTpqn = midi.ticksPerBeat; - const midiTempos = midi.tempos; - const midiTimeSignatures = midi.timeSignatures; + const notes = tracks[trackIndex].notes; - const midiNotes = midi.tracks[trackIndex].notes; - - midiNotes.sort((a, b) => a.ticks - b.ticks); - - const tpqn = DEFAULT_TPQN; - - let notes = midiNotes.map((value): Note => { - return { - id: NoteId(crypto.randomUUID()), - position: convertPosition(value.ticks, midiTpqn, tpqn), - duration: convertDuration( - value.ticks, - value.ticks + value.duration, - midiTpqn, - tpqn, - ), - noteNumber: value.noteNumber, - lyric: value.lyric || getDoremiFromNoteNumber(value.noteNumber), - }; - }); - // ノートの重なりを考慮して、一番音が高いノート(トップノート)のみインポートする - notes = getTopNotes(notes); - - let tempos = midiTempos.map((value): Tempo => { - return { - position: convertPosition(value.ticks, midiTpqn, tpqn), - bpm: round(value.bpm, 2), - }; - }); - tempos.unshift(createDefaultTempo(0)); - tempos = removeDuplicateTempos(tempos); - - let timeSignatures: TimeSignature[] = []; - let tsPosition = 0; - let measureNumber = 1; - for (let i = 0; i < midiTimeSignatures.length; i++) { - const midiTs = midiTimeSignatures[i]; - const beats = midiTs.numerator; - const beatType = midiTs.denominator; - timeSignatures.push({ measureNumber, beats, beatType }); - if (i < midiTimeSignatures.length - 1) { - const nextTsTicks = midiTimeSignatures[i + 1].ticks; - const nextTsPos = convertPosition(nextTsTicks, midiTpqn, tpqn); - const tsDuration = nextTsPos - tsPosition; - const measureDuration = getMeasureDuration(beats, beatType, tpqn); - tsPosition = nextTsPos; - measureNumber += tsDuration / measureDuration; - } + if (tempos.length > 1) { + logger.warn("Multiple tempos are not supported."); + } + if (timeSignatures.length > 1) { + logger.warn("Multiple time signatures are not supported."); } - timeSignatures.unshift(createDefaultTimeSignature(1)); - timeSignatures = removeDuplicateTimeSignatures(timeSignatures); tempos.splice(1, tempos.length - 1); // TODO: 複数テンポに対応したら削除 timeSignatures.splice(1, timeSignatures.length - 1); // TODO: 複数拍子に対応したら削除 @@ -1833,432 +1712,59 @@ export const singingStore = createPartialStore({ if (tpqn !== state.tpqn) { throw new Error("TPQN does not match. Must be converted."); } + + // TODO: ここら辺のSET系の処理をまとめる + await dispatch("SET_TPQN", { tpqn }); await dispatch("SET_TEMPOS", { tempos }); await dispatch("SET_TIME_SIGNATURES", { timeSignatures }); await dispatch("SET_NOTES", { notes }); + + commit("SET_SAVED_LAST_COMMAND_UNIX_MILLISEC", null); + commit("CLEAR_COMMANDS"); + dispatch("RENDER"); }, ), }, - IMPORT_MUSICXML_FILE: { + // TODO: Undoできるようにする + IMPORT_VOICEVOX_PROJECT: { action: createUILockAction( - async ({ state, dispatch }, { filePath }: { filePath?: string }) => { - if (!filePath) { - filePath = await window.backend.showImportFileDialog({ - title: "MusicXML読み込み", - name: "MusicXML", - extensions: ["musicxml", "xml"], - }); - if (!filePath) return; - } + async ({ state, commit, dispatch }, { project, trackIndex = 0 }) => { + const { tempos, timeSignatures, tracks, tpqn } = project.song; - let xmlStr = new TextDecoder("utf-8").decode( - getValueOrThrow(await window.backend.readFile({ filePath })), - ); - if (xmlStr.indexOf("\ufffd") > -1) { - xmlStr = new TextDecoder("shift-jis").decode( - getValueOrThrow(await window.backend.readFile({ filePath })), - ); - } - - const tpqn = DEFAULT_TPQN; - const tempos = [createDefaultTempo(0)]; - const timeSignatures = [createDefaultTimeSignature(1)]; - const notes: Note[] = []; - - let divisions = 1; - let position = 0; - let measureNumber = 1; - let measurePosition = 0; - let measureDuration = getMeasureDuration( - timeSignatures[0].beats, - timeSignatures[0].beatType, - tpqn, - ); - let tieStartNote: Note | undefined; - - const getChild = (element: Element | undefined, tagName: string) => { - if (element) { - for (const childElement of element.children) { - if (childElement.tagName === tagName) { - return childElement; - } - } - } - return undefined; - }; - - const getValueAsNumber = (element: Element) => { - const value = Number(element.textContent); - if (Number.isNaN(value)) { - throw new Error("The value is invalid."); - } - return value; - }; - - const getAttributeAsNumber = ( - element: Element, - qualifiedName: string, - ) => { - const value = Number(element.getAttribute(qualifiedName)); - if (Number.isNaN(value)) { - throw new Error("The value is invalid."); - } - return value; - }; - - const getStepNumber = (stepElement: Element) => { - const stepNumberDict: { [key: string]: number } = { - C: 0, - D: 2, - E: 4, - F: 5, - G: 7, - A: 9, - B: 11, - }; - const stepChar = stepElement.textContent; - if (stepChar == null) { - throw new Error("The value is invalid."); - } - return stepNumberDict[stepChar]; - }; - - const getDuration = (durationElement: Element) => { - const duration = getValueAsNumber(durationElement); - return Math.round((tpqn * duration) / divisions); - }; - - const getTie = (elementThatMayBeTied: Element) => { - let tie: boolean | undefined; - for (const childElement of elementThatMayBeTied.children) { - if ( - childElement.tagName === "tie" || - childElement.tagName === "tied" - ) { - const tieType = childElement.getAttribute("type"); - if (tieType === "start") { - tie = true; - } else if (tieType === "stop") { - tie = false; - } else { - throw new Error("The value is invalid."); - } - } - } - return tie; - }; - - const parseSound = (soundElement: Element) => { - if (!soundElement.hasAttribute("tempo")) { - return; - } - if (tempos.length !== 0) { - const lastTempo = tempos[tempos.length - 1]; - if (lastTempo.position === position) { - tempos.pop(); - } - } - const tempo = getAttributeAsNumber(soundElement, "tempo"); - tempos.push({ - position: position, - bpm: round(tempo, 2), - }); - }; - - const parseDirection = (directionElement: Element) => { - for (const childElement of directionElement.children) { - if (childElement.tagName === "sound") { - parseSound(childElement); - } - } - }; - - const parseDivisions = (divisionsElement: Element) => { - divisions = getValueAsNumber(divisionsElement); - }; - - const parseTime = (timeElement: Element) => { - const beatsElement = getChild(timeElement, "beats"); - if (!beatsElement) { - throw new Error("beats element does not exist."); - } - const beatTypeElement = getChild(timeElement, "beat-type"); - if (!beatTypeElement) { - throw new Error("beat-type element does not exist."); - } - const beats = getValueAsNumber(beatsElement); - const beatType = getValueAsNumber(beatTypeElement); - measureDuration = getMeasureDuration(beats, beatType, tpqn); - if (timeSignatures.length !== 0) { - const lastTimeSignature = timeSignatures[timeSignatures.length - 1]; - if (lastTimeSignature.measureNumber === measureNumber) { - timeSignatures.pop(); - } - } - timeSignatures.push({ - measureNumber, - beats, - beatType, - }); - }; - - const parseAttributes = (attributesElement: Element) => { - for (const childElement of attributesElement.children) { - if (childElement.tagName === "divisions") { - parseDivisions(childElement); - } else if (childElement.tagName === "time") { - parseTime(childElement); - } else if (childElement.tagName === "sound") { - parseSound(childElement); - } - } - }; - - const parseNote = (noteElement: Element) => { - // TODO: ノートの重なり・和音を考慮していないので、 - // それらが存在する場合でも読み込めるようにする - - const durationElement = getChild(noteElement, "duration"); - if (!durationElement) { - throw new Error("duration element does not exist."); - } - let duration = getDuration(durationElement); - let noteEnd = position + duration; - const measureEnd = measurePosition + measureDuration; - if (noteEnd > measureEnd) { - // 小節に収まらない場合、ノートの長さを変えて小節に収まるようにする - duration = measureEnd - position; - noteEnd = position + duration; - } - - if (getChild(noteElement, "rest")) { - position += duration; - return; - } - - const pitchElement = getChild(noteElement, "pitch"); - if (!pitchElement) { - throw new Error("pitch element does not exist."); - } - const octaveElement = getChild(pitchElement, "octave"); - if (!octaveElement) { - throw new Error("octave element does not exist."); - } - const stepElement = getChild(pitchElement, "step"); - if (!stepElement) { - throw new Error("step element does not exist."); - } - const alterElement = getChild(pitchElement, "alter"); - - const octave = getValueAsNumber(octaveElement); - const stepNumber = getStepNumber(stepElement); - let noteNumber = 12 * (octave + 1) + stepNumber; - if (alterElement) { - noteNumber += getValueAsNumber(alterElement); - } - - const lyricElement = getChild(noteElement, "lyric"); - let lyric = getChild(lyricElement, "text")?.textContent ?? ""; - lyric = lyric.trim(); - - let tie = getTie(noteElement); - for (const childElement of noteElement.children) { - if (childElement.tagName === "notations") { - tie = getTie(childElement); - } - } - - const note: Note = { - id: NoteId(crypto.randomUUID()), - position, - duration, - noteNumber, - lyric, - }; - - if (tieStartNote) { - if (tie === false) { - tieStartNote.duration = noteEnd - tieStartNote.position; - notes.push(tieStartNote); - tieStartNote = undefined; - } - } else { - if (tie === true) { - tieStartNote = note; - } else { - notes.push(note); - } - } - position += duration; - }; - - const parseMeasure = (measureElement: Element) => { - measurePosition = position; - measureNumber = getAttributeAsNumber(measureElement, "number"); - for (const childElement of measureElement.children) { - if (childElement.tagName === "direction") { - parseDirection(childElement); - } else if (childElement.tagName === "sound") { - parseSound(childElement); - } else if (childElement.tagName === "attributes") { - parseAttributes(childElement); - } else if (childElement.tagName === "note") { - if (position < measurePosition + measureDuration) { - parseNote(childElement); - } - } - } - const measureEnd = measurePosition + measureDuration; - if (position !== measureEnd) { - tieStartNote = undefined; - position = measureEnd; - } - }; - - const parsePart = (partElement: Element) => { - for (const childElement of partElement.children) { - if (childElement.tagName === "measure") { - parseMeasure(childElement); - } - } - }; - - const parseMusicXml = (xmlStr: string) => { - const parser = new DOMParser(); - const dom = parser.parseFromString(xmlStr, "application/xml"); - const partElements = dom.getElementsByTagName("part"); - if (partElements.length === 0) { - throw new Error("part element does not exist."); - } - // TODO: UIで読み込むパートを選択できるようにする - parsePart(partElements[0]); - }; - - parseMusicXml(xmlStr); - - tempos.splice(1, tempos.length - 1); // TODO: 複数テンポに対応したら削除 - timeSignatures.splice(1, timeSignatures.length - 1); // TODO: 複数拍子に対応したら削除 + const track = tracks[trackIndex]; + const notes = track.notes.map((note) => ({ + ...note, + id: NoteId(crypto.randomUUID()), + })); if (tpqn !== state.tpqn) { throw new Error("TPQN does not match. Must be converted."); } - await dispatch("SET_TEMPOS", { tempos }); - await dispatch("SET_TIME_SIGNATURES", { timeSignatures }); - await dispatch("SET_NOTES", { notes }); - }, - ), - }, - - IMPORT_UST_FILE: { - action: createUILockAction( - async ({ state, 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 = [createDefaultTempo(0)]; - const timeSignatures = [createDefaultTimeSignature(1)]; - 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; - } - // ノートセクション - // #以降に数字の場合はノートセクション ex: #0, #0000 - if (params.sectionName.match(/^#\d+$/)) { - // テンポ変更があれば追加 - const tempo = Number(params["Tempo"]); - if (tempo) tempos.push({ position, bpm: tempo }); - const noteNumber = Number(params["NoteNum"]); - const duration = Number(params["Length"]); - let lyric = params["Lyric"].trim(); - // 歌詞の前に連続音が含まれている場合は除去 - if (lyric.includes(" ")) { - lyric = lyric.split(" ")[1]; - } - // 休符であればポジションを進めるのみ - if (lyric === "R") { - position += duration; - } else { - // それ以外の場合はノートを追加 - notes.push({ - id: NoteId(crypto.randomUUID()), - position, - duration, - noteNumber, - lyric, - }); - position += duration; - } - } + // TODO: ここら辺のSET系の処理をまとめる + await dispatch("SET_SINGER", { + singer: track.singer, }); - - if (tpqn !== state.tpqn) { - throw new Error("TPQN does not match. Must be converted."); - } + await dispatch("SET_KEY_RANGE_ADJUSTMENT", { + keyRangeAdjustment: track.keyRangeAdjustment, + }); + await dispatch("SET_VOLUME_RANGE_ADJUSTMENT", { + volumeRangeAdjustment: track.volumeRangeAdjustment, + }); + await dispatch("SET_TPQN", { tpqn }); await dispatch("SET_TEMPOS", { tempos }); await dispatch("SET_TIME_SIGNATURES", { timeSignatures }); await dispatch("SET_NOTES", { notes }); + await dispatch("CLEAR_PITCH_EDIT_DATA"); // FIXME: SET_PITCH_EDIT_DATAがセッターになれば不要 + await dispatch("SET_PITCH_EDIT_DATA", { + data: track.pitchEditData, + startFrame: 0, + }); + + commit("SET_SAVED_LAST_COMMAND_UNIX_MILLISEC", null); + commit("CLEAR_COMMANDS"); + dispatch("RENDER"); }, ), }, diff --git a/src/store/type.ts b/src/store/type.ts index a5873a13b5..f926511550 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -1,5 +1,6 @@ import { Patch } from "immer"; import { z } from "zod"; +import { Project as UfProject } from "@sevenc-nanashi/utaformatix-ts"; import { MutationTree, MutationsBase, @@ -61,6 +62,7 @@ import { } from "@/components/Dialog/Dialog"; import { OverlappingNoteInfos } from "@/sing/storeHelper"; import { + LatestProjectType, noteSchema, singerSchema, tempoSchema, @@ -1016,16 +1018,12 @@ export type SingingStoreTypes = { action(payload: { isDrag: boolean }): void; }; - IMPORT_MIDI_FILE: { - action(payload: { filePath: string; trackIndex: number }): void; + IMPORT_UTAFORMATIX_PROJECT: { + action(payload: { project: UfProject; trackIndex: number }): void; }; - IMPORT_MUSICXML_FILE: { - action(payload: { filePath?: string }): void; - }; - - IMPORT_UST_FILE: { - action(payload: { filePath?: string }): void; + IMPORT_VOICEVOX_PROJECT: { + action(payload: { project: LatestProjectType; trackIndex: number }): void; }; EXPORT_WAVE_FILE: { @@ -1489,6 +1487,10 @@ export type ProjectStoreTypes = { action(payload: { confirm?: boolean }): void; }; + PARSE_PROJECT_FILE: { + action(payload: { projectJson: string }): Promise; + }; + LOAD_PROJECT_FILE: { action(payload: { filePath?: string; confirm?: boolean }): boolean; }; @@ -1642,7 +1644,7 @@ export type UiStoreState = { isDictionaryManageDialogOpen: boolean; isEngineManageDialogOpen: boolean; isUpdateNotificationDialogOpen: boolean; - isImportMidiDialogOpen: boolean; + isImportSongProjectDialogOpen: boolean; isMaximized: boolean; isPinned: boolean; isFullscreen: boolean; @@ -1714,7 +1716,7 @@ export type UiStoreTypes = { isCharacterOrderDialogOpen?: boolean; isEngineManageDialogOpen?: boolean; isUpdateNotificationDialogOpen?: boolean; - isImportMidiDialogOpen?: boolean; + isImportExternalProjectDialogOpen?: boolean; }; action(payload: { isDefaultStyleSelectDialogOpen?: boolean; @@ -1728,7 +1730,7 @@ export type UiStoreTypes = { isCharacterOrderDialogOpen?: boolean; isEngineManageDialogOpen?: boolean; isUpdateNotificationDialogOpen?: boolean; - isImportMidiDialogOpen?: boolean; + isImportSongProjectDialogOpen?: boolean; }): void; }; diff --git a/src/store/ui.ts b/src/store/ui.ts index d83d9974db..59ac754d6d 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -66,7 +66,7 @@ export const uiStoreState: UiStoreState = { isDictionaryManageDialogOpen: false, isEngineManageDialogOpen: false, isUpdateNotificationDialogOpen: false, - isImportMidiDialogOpen: false, + isImportSongProjectDialogOpen: false, isMaximized: false, isPinned: false, isFullscreen: false, @@ -186,7 +186,7 @@ export const uiStore = createPartialStore({ isCharacterOrderDialogOpen?: boolean; isEngineManageDialogOpen?: boolean; isUpdateNotificationDialogOpen?: boolean; - isImportMidiDialogOpen?: boolean; + isImportExternalProjectDialogOpen?: boolean; }, ) { for (const [key, value] of Object.entries(dialogState)) { diff --git a/src/styles/_index.scss b/src/styles/_index.scss index 02912d5603..f598a5995a 100644 --- a/src/styles/_index.scss +++ b/src/styles/_index.scss @@ -1,5 +1,5 @@ -@use './variables' as vars; -@use './colors' as colors; +@use "./variables" as vars; +@use "./colors" as colors; @import "./fonts"; // 優先度を強引に上げる @@ -20,6 +20,14 @@ img { pointer-events: none; } +// detailsタグのスタイル +details { + summary { + display: list-item; + cursor: pointer; + } +} + // スクロールバーのデザイン ::-webkit-scrollbar { width: 12px; diff --git a/tests/unit/lib/midi/midi.spec.ts b/tests/unit/lib/midi/midi.spec.ts deleted file mode 100644 index 10df1ee4b3..0000000000 --- a/tests/unit/lib/midi/midi.spec.ts +++ /dev/null @@ -1,59 +0,0 @@ -// @vitest-environment node - -import { promises as fs } from "fs"; -import { it, expect } from "vitest"; -import { Midi } from "@/sing/midi"; - -// MIDIファイルの作成情報: -// - synthv.mid:SynthVで作成(Synthesizer V Studio Pro 1.11.0、プロジェクトファイルは https://github.com/VOICEVOX/voicevox/pull/1982 を参照) -// - timeSig.mid、bpm.mid:signalで作成(https://signal.vercel.app/edit) - -const midiRoot = "tests/unit/lib/midi/"; - -it("BPMをパースできる", async () => { - const bpmMid = await fs.readFile(midiRoot + "bpm.mid"); - const midi = new Midi(bpmMid); - const ticksPerBeat = midi.ticksPerBeat; - expect(midi.tempos).toEqual([ - { ticks: 0, bpm: 120 }, - { ticks: ticksPerBeat * 4, bpm: 180 }, - { ticks: ticksPerBeat * 8, bpm: 240 }, - ]); -}); - -const lyricExpectation: [noteNumber: number, lyric: string][] = [ - [60, "ど"], - [62, "れ"], - [64, "み"], - [65, "ふぁ"], - [67, "そ"], - [69, "ら"], - [71, "し"], - [72, "ど"], -]; - -it("SynthVのノートと歌詞をパースできる", async () => { - const synthvMid = await fs.readFile(midiRoot + "synthv.mid"); - const midi = new Midi(synthvMid); - const ticksPerBeat = midi.ticksPerBeat; - // SynthVのMIDIファイルの1トラック目はBPM情報のみなので、2トラック目を取得 - expect(midi.tracks[1].notes).toEqual( - lyricExpectation.map(([noteNumber, lyric], index) => ({ - ticks: index * ticksPerBeat, - noteNumber, - duration: ticksPerBeat, - lyric, - })), - ); -}); - -it("拍子をパースできる", async () => { - const timeSigMid = await fs.readFile(midiRoot + "timeSig.mid"); - const midi = new Midi(timeSigMid); - const ticksPerBeat = midi.ticksPerBeat; - expect(midi.timeSignatures).toEqual([ - { ticks: 0, numerator: 4, denominator: 4 }, - { ticks: ticksPerBeat * 4, numerator: 3, denominator: 4 }, - { ticks: ticksPerBeat * 7, numerator: 4, denominator: 8 }, - ]); -}); diff --git a/tests/unit/lib/utaformatixProject/__snapshots__/export.spec.ts.snap b/tests/unit/lib/utaformatixProject/__snapshots__/export.spec.ts.snap new file mode 100644 index 0000000000..f15a3be488 --- /dev/null +++ b/tests/unit/lib/utaformatixProject/__snapshots__/export.spec.ts.snap @@ -0,0 +1,37 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`トラックを変換できる 1`] = ` +{ + "formatVersion": 1, + "project": { + "measurePrefix": 0, + "name": "test", + "tempos": [ + { + "bpm": 120, + "tickPosition": 0, + }, + ], + "timeSignatures": [ + { + "denominator": 4, + "measurePosition": 1, + "numerator": 4, + }, + ], + "tracks": [ + { + "name": "無名トラック", + "notes": [ + { + "key": 60, + "lyric": "ど", + "tickOff": 480, + "tickOn": 0, + }, + ], + }, + ], + }, +} +`; diff --git a/tests/unit/lib/midi/bpm.mid b/tests/unit/lib/utaformatixProject/bpm.mid similarity index 100% rename from tests/unit/lib/midi/bpm.mid rename to tests/unit/lib/utaformatixProject/bpm.mid diff --git a/tests/unit/lib/utaformatixProject/export.spec.ts b/tests/unit/lib/utaformatixProject/export.spec.ts new file mode 100644 index 0000000000..1fad3469b5 --- /dev/null +++ b/tests/unit/lib/utaformatixProject/export.spec.ts @@ -0,0 +1,35 @@ +import { it, expect } from "vitest"; +import { ufProjectFromVoicevox } from "@/sing/utaformatixProject/fromVoicevox"; +import { + createDefaultTempo, + createDefaultTimeSignature, + createDefaultTrack, +} from "@/sing/domain"; +import { NoteId } from "@/type/preload"; + +const createNoteId = () => NoteId(crypto.randomUUID()); + +it("トラックを変換できる", async () => { + const track = createDefaultTrack(); + track.notes.push({ + id: createNoteId(), + noteNumber: 60, + position: 0, + duration: 480, + lyric: "ど", + }); + + const project = ufProjectFromVoicevox( + { + tracks: [track], + tpqn: 480, + tempos: [createDefaultTempo(0)], + timeSignatures: [createDefaultTimeSignature(1)], + }, + "test", + ); + + const ufData = project.toUfDataObject(); + + expect(ufData).toMatchSnapshot(); +}); diff --git a/tests/unit/lib/utaformatixProject/import.spec.ts b/tests/unit/lib/utaformatixProject/import.spec.ts new file mode 100644 index 0000000000..ea9632464e --- /dev/null +++ b/tests/unit/lib/utaformatixProject/import.spec.ts @@ -0,0 +1,61 @@ +// @vitest-environment node + +import { promises as fs } from "fs"; +import { it, expect } from "vitest"; +import { Project as UfProject } from "@sevenc-nanashi/utaformatix-ts"; +import { ufProjectToVoicevox } from "@/sing/utaformatixProject/toVoicevox"; + +// MIDIファイルの作成情報: +// - synthv.mid:SynthVで作成(Synthesizer V Studio Pro 1.11.0、プロジェクトファイルは https://github.com/VOICEVOX/voicevox/pull/1982 を参照) +// - timeSig.mid、bpm.mid:signalで作成(https://signal.vercel.app/edit) + +const midiRoot = "tests/unit/lib/utaformatixProject/"; + +const convertMidi = async (filename: string) => { + const midi = await fs.readFile(midiRoot + filename); + const project = await UfProject.fromStandardMid(midi); + return ufProjectToVoicevox(project); +}; + +it("BPMを変換できる", async () => { + const state = await convertMidi("bpm.mid"); + const tpqn = state.tpqn; + expect(state.tempos).toEqual([ + { position: 0, bpm: 120 }, + { position: tpqn * 4, bpm: 180 }, + { position: tpqn * 8, bpm: 240 }, + ]); +}); + +const lyricExpectation: [noteNumber: number, lyric: string][] = [ + [60, "ど"], + [62, "れ"], + [64, "み"], + [65, "ふぁ"], + [67, "そ"], + [69, "ら"], + [71, "し"], + [72, "ど"], +]; + +it("SynthVのノートと歌詞を変換できる", async () => { + const state = await convertMidi("synthv.mid"); + expect(state.tracks[0].notes).toMatchObject( + lyricExpectation.map(([noteNumber, lyric], index) => ({ + // id: string, + noteNumber, + position: index * state.tpqn, + duration: state.tpqn, + lyric, + })), + ); +}); + +it("拍子を変換できる", async () => { + const state = await convertMidi("timeSig.mid"); + expect(state.timeSignatures).toEqual([ + { measureNumber: 1, beats: 4, beatType: 4 }, + { measureNumber: 2, beats: 3, beatType: 4 }, + { measureNumber: 3, beats: 4, beatType: 8 }, + ]); +}); diff --git a/tests/unit/lib/midi/synthv.mid b/tests/unit/lib/utaformatixProject/synthv.mid similarity index 100% rename from tests/unit/lib/midi/synthv.mid rename to tests/unit/lib/utaformatixProject/synthv.mid diff --git a/tests/unit/lib/midi/timeSig.mid b/tests/unit/lib/utaformatixProject/timeSig.mid similarity index 100% rename from tests/unit/lib/midi/timeSig.mid rename to tests/unit/lib/utaformatixProject/timeSig.mid From 7a2e580a5ba5d9d6862f1ce7d306026642d200b9 Mon Sep 17 00:00:00 2001 From: nao Date: Sun, 16 Jun 2024 22:38:09 +0900 Subject: [PATCH 30/31] =?UTF-8?q?=E8=B2=A2=E7=8C=AE=E8=80=85=E3=82=AC?= =?UTF-8?q?=E3=82=A4=E3=83=89=E3=83=A9=E3=82=A4=E3=83=B3=E3=81=AE=E4=BD=9C?= =?UTF-8?q?=E6=88=90=20(#1202)?= 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 #1190) * レビュー結果の反映① * 着手周りの手順追記 * CONTRIBUTING.md として配置変更 * markdownlint のエラーを修正 * * ローカル実行時の markdownlint 検索範囲を修正 * Issueを閉じるタイミングを追記 * * ドラフトプルリクエストについての追記 * フォーマットの修正 * * プルリクエストの表記を英語に。 * WIPに付いてのトーンを弱めに。 * リンク切れの修正 * 「その他」の 追記 * * レビュー内容の反映 * * e2e部分の追記 * インデント修正 * 提案いただいた分のコミットと追記 * ・査読分の反映 ・README.mdに誘導リンクを追加 * Apply suggestions from code review * フォーマットを整える * 崩れてしまった部分を戻す * こう? * なぜか * に戻っていた * pythonはコメントアウトが // ではなかった --------- Co-authored-by: Hiroshiba --- CONTRIBUTING.md | 363 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 8 +- 2 files changed, 370 insertions(+), 1 deletion(-) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000000..5a974e6fb4 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,363 @@ +# 貢献者ガイドライン + +## 始めに + +まず初めに、VOICEVOXプロジェクトに関心を寄せて頂きありがとうございます。 +私たちは、あなたが積極的に参加してくれることを歓迎します。 + +実際に参加しようとすると、どんなコミュニティにもルールが存在し、そこを理解しないとハードルを高く感じてしまうことがあります。 + +このガイドラインは、その部分を出来るだけ分かりやすく文章として残し、コミュニティへ参画しやすい環境を提供するために執筆されました。なお、新たな貢献者が参入しやすいよう細かく解説しているため、慣れている方には不要な説明も含まれます。プロジェクトは意志ある貢献者を歓迎しておりますので、ドキュメントを読んで参画してみてください。 + +## 担当 + +| 役割 | 担当 | +| ------------------ | ---------------------------- | +| プロダクトオーナー | @Hiroshiba | +| メンテナー | @Hiroshiba、@y-chan、@qryxip | + +## 参加の心得 + +VOICEVOXプロジェクトは、いわゆる集団開発型のオープンソースソフトウェアにあたります。参加を望む方は、下記のことに注意して参加する必要があります。 + +- [VOICEVOXの目標](docs/ミッション・バリュー・ビジョン.md)に照らし合わせて提案を行うと会話がスムーズです。 + +- 実施する中身については、コミュニティ内で会話をしながら合意を取っていく必要があります。また、プロジェクト方針により採用が拒絶されるケースがあります。 + +- 集団開発では、会話をしながら物を作っていくことが1つの醍醐味でもあります。一人で作品を作る時に比べて丁寧なコミュニケーションが必要です。会話相手に対して常に敬意を払ってください。 + +- プロジェクトへの参画に当たっては、年齢、国籍、境遇、性別などは関係ありません。これらの差別を行うことをプロジェクトは容認しません。 + +- プロジェクトは著作者や著作物を尊重します。常に他者の権利やライセンスを順守するように意識しています。プロジェクトへの貢献にあたっては、盗用したプログラムの提出は行わないでください。 + +- コントリビューターとして提供したプログラムは、プロジェクトが定義するライセンスで取り扱われる事に注意してください。 + +- プライバシーに関わる実装や、コンピュータに危害を与える可能性がある実装に関しては慎重な議論が必要です。実装を先におこなうのではなく、必ず合意形成をしてください。 + +## 貢献の仕方 + +このドキュメントでは、主にプログラムの改良を手伝ってくださる方に向けた、「参加の仕方」をガイドします。 + +VOICEVOXには、下記のような貢献の仕方があります。 + +- ユーザとして使う +- 記事や動画を公開して広める +- プログラムの改良を手伝う +- ドキュメントなどを書く + +プログラムは3部構成に分かれているので、該当する部分に該当するプロジェクトに参加しましょう。 + +| 種類 | ページ | 役割 | +| --------------- | ------------------------------------------------------------ | ---------------------------------------- | +| VOICEVOX | [プロジェクト](https://github.com/VOICEVOX/voicevox/) | 主にユーザインタフェイス(エディタ)部分 | +| VOICEVOX_ENGINE | [プロジェクト](https://github.com/VOICEVOX/voicevox_engine/) | 主にWeb API実装部分 | +| VOICEVOX_CORE | [プロジェクト](https://github.com/VOICEVOX/voicevox_core/) | 主に音声合成・ライブラリ実装部分 | + +なお、全体構成を学びたい場合は、[こちら](docs/全体構成.md)が参考になることでしょう。 + +## 初心者歓迎タスク + +あなたがプログラム開発を学んだり、オープンソース開発コミュニティで活動することを実践したい場合は、既にコミュニティのIssuesで提案されている「初心者歓迎タスク」に参加することをお勧めします。 + +「初心者歓迎タスク」は、VOICEVOXプロジェクトとしては「難易度が比較的低い案件であるが、必要とされているもの」となっており、比較的一通りの工程をスマートに学びながら貢献することができます。 + +| 種類 | ページ | +| --------------- | ---------------------------------------------------------------------------------------------------------------------- | +| VOICEVOX | [初心者歓迎タスク](https://github.com/VOICEVOX/voicevox/issues?q=is%3Aissue+is%3Aopen+label%3A初心者歓迎タスク) | +| VOICEVOX_ENGINE | [初心者歓迎タスク](https://github.com/VOICEVOX/voicevox_engine/issues?q=is%3Aissue+is%3Aopen+label%3A初心者歓迎タスク) | +| VOICEVOX_CORE | [初心者歓迎タスク](https://github.com/VOICEVOX/voicevox_core/issues?q=is%3Aissue+is%3Aopen+label%3A初心者歓迎タスク) | + +## 事前準備 + +ここからはWindowsをお使いの方が、VOICEVOX(エディタ)の環境を作るケースを想定し、話を進めます。まず、テスト版VOICEVOXの環境を構築しましょう。 + +### 1. 製品版VOICEVOXを導入する + +- まず[VOICEVOXの製品版](https://voicevox.hiroshiba.jp/)を導入します。これによりすぐ使えるVOIECVOXエンジンを手に入れることができます。 + +### 2. 開発環境の構築 + +- 必須ツール + - [Node.js](https://nodejs.org/en/download/releases/)\ + [こちら](https://github.com/VOICEVOX/voicevox/blob/main/.node-version)に記載されているバージョンのインストーラを入手し、インストールします。 + +- 必要に応じて + - [Git](https://git-scm.com/downloads) + - [Visual Studio Code](https://code.visualstudio.com/) + - [GitHub CLI](https://github.com/cli/cli#installation) + - [typos](https://github.com/crate-ci/typos#install) (誤字チェックする場合) + - [Tortoise Git](https://tortoisegit.org/download/) + (エクスプローラ上で操作したい場合) + +### 3. フォークする + +- プロジェクトの複製をつくって自分のGitHubリポジトリにもってくる作業をフォークと言います。[こちら](https://github.com/VOICEVOX/voicevox/fork)を押して、フォークを実施します。 + +### 4. ソースコードを手に入れる(クローン) + +- 自分のGitHubリポジトリにあるソースコードをGitHubから作業用パソコンに持ってきます。 + +#### 4.1 コマンドラインで行う場合 + +- GitHub コマンド(GitHub CLI)を使う場合 + +```bash +gh repo clone https://github.com/(個人のGitHubアカウント名)/voicevox.git +``` + +- Git コマンド(Git CLI)を使う場合 + +```bash +git clone git@github.com:(個人のGitHubアカウント名)/voicevox.git +``` + +#### 4.2 GUIで行う場合 + +- Visual Studio CodeやTortoise Gitなどのツールを用いて入手します。 +- 指定するURLはツールによって異なりますが、`git@github.com:(個人のGitHubアカウント名)/voicevox.git`や`https://github.com/(個人のGitHubアカウント名)/voicevox.git`となります。 + +### 5. 必要なプログラムをダウンロードする + +- 手順4で手に入れたフォルダを開いて、コマンドプロンプトを開きます。 +- 環境を準備するコマンド `npm ci` + を実行してください。自動的にダウンロードされます。 +- ツールの組み合わせや実装に関する警告が表示されますが、開発環境を作るうえでは無視して差し支えありません。 + +### 6. エンジンを指定する + +- `.env.production`というファイルがありますので、コピーして、名前を`.env`にします。 +- ファイルをエディタでひらいて、`VITE_DEFAULT_ENGINE_INFOS`内の`executionFilePath`に手順1のフォルダ名をいれます。たとえば製品版をインストーラで導入し、インストール先を変更していない場合は、下記のように書き換えて保存します。 + +```ini +VITE_APP_NAME=voicevox +VITE_DEFAULT_ENGINE_INFOS=`[ + { + "uuid": "074fc39e-678b-4c13-8916-ffca8d505d1d", + "name": "VOICEVOX Engine", + "executionEnabled": true, + "executionFilePath": "vv-engine/run.exe", + "executionArgs": [], + "host": "http://127.0.0.1:50021" + } +]` +``` + +- あなたがVOICEVOX製品版のインストール先を変更している場合は個別で指定します。たとえば、`D:\VOICEVOX0.14.1`に製品版をインストールしている場合は、下記のように書き換えて保存します。 + +```text +"executionFilePath": "D:/VOICEVOX0.14.1/vv-engine/run.exe", +``` + +### 7. 始動してみる + +- `npm run electron:serve`を実行します。 +- 設定が正しければ、開発環境が起動するはずです。 + +## プロジェクトへの貢献手順 + +### 1. 提案と調整 + +まず、下記のことがあれば、Issueとして登録をしましょう。 + +- プログラムの仕様を変更したい +- 新機能を追加したい +- バグを確認した + +#### 1.1 提案 + +その際、VOICEVOXのどの部分に関して提案をしたいのかを考えて提案しましょう。また、個人がわかる範囲で問題ないので、「改良されることで良くなる点」や「悪くなる点・影響を受ける点」を書いて登録します。 + +#### 1.2 相談 + +この段階では、関係者と実装に関しての制約や影響範囲、プロジェクト方針として優先度や実施してよいかをすり合わせます。 + +コミュニティには様々な技術領域・技量の方が混在しています。会話の途中で分からないことが出てくることも多いかとおもいます。不明点は質問し、理解を深めていきましょう。 + +#### 1.3 着手宣言 + +既にIssueとして登録されている課題に着手したい場合は、他の貢献者と作業が重複しないように、当該Issueのページで「私が着手する」旨の宣言を行ってください。 + +なお、着手宣言をした後は下記手順で作業をすることになります。定期的に相談や進捗をレポートしましょう。また、作業時間や技量などにより、コードを書き終えられないケースはあります。その場合は、抱え込まずにIssueページで相談をしてください。 + +### 2. ブランチを作る + +- 自分の作業フォルダ内に、今回加工するために作業エリアを作ります。 +- いくつかの案件を並走するなら、この手順でブランチをいくつか作ります。 +- ブランチ名は、自分のわかりやすいもので構いません。 + +### 3. プログラムを加工する + +実際にプログラムを書きます。プログラムを書くにあたっては、いくつかの流儀があります。 + +- 関数名や変数名は極力キャメルケースで命名する必要があります。何かしらの制約上キャメルケースで命名出来ない場合は、コメントを残します。 + +```ts +// FIXME: ●●のため、キャメルケースが採用できない +``` + +- 今回コーディングするが、構造制約などで「本来ありたい構造と異なる」場合にも、コメントを残します。 + +```ts +// TODO: ●●を使わずに、●●となる実装にしたい +``` + +- 変数名や型名の命名にあたっては、その仕組みで一般的に使われる命名則があれば、それらを優先して採用します。 + +- 関数名は、動詞+役割となるように設定をします。 + + | 命名例 | 役割 | + | ----------- | --------------------------- | + | setVolume() | 音量を設定 | + | getVolume() | 音量を取得 | + | isMuted | ミュート状態の取得(boolean) | + +- 変数や関数名につける英語は極力省略しないようにします。 +- コードは分かりやすさや単純さを保つようにしてください。 +- 不必要な定義や、作業中のコードは、コード提出までに除去しましょう。 + +### 4. 事前テスト + +- 提出前にコードをテストします。テストにはいくつかのツールを使います。このガイドラインの手順で進んでいれば既に必要なものはそろっている + +- 記述コードがコーディングルールに沿っていることを確認します。(特に今回の作業によって警告やエラーが増えていないかどうかに注目してください) + + ```bash + npm run lint + npm run fmt + ``` + +- TypeScriptの型チェックを行います。 + + ```bash + npm run typecheck + ``` + +- Markdownの記述が正しいことを確認します。 + + ```bash + npm run markdownlint ./*/*.md + ``` + +- 命名に使っている英語が誤っていないことを確認します。 + + ```bash + typos + ``` + +- 個人環境でVOICEVOXを実行し、提出前に、一通り動くことを確認します。 + + ```bash + npm run electron:serve + ``` + +- 使用するライブラリのライセンスに使用出来ないものが使われていないことを確認します。 + + ```bash + npm run license:generate -- -o voicevox_licenses.json + ``` + +- e2eテストの内容を確認します。 + + ```bash + npm run test:unit + npm run test:browser-e2e + npm run test:electron-e2e + ``` + + - e2eテストは実際には自分が提出する範囲外の指摘をしたり、完全に警告が消えないことがあります。 + - 確認の目安としては、加工前後で + e2eテスト結果による指摘が増えていないことを確認してください。(チェックアウト時点でe2eテストの指摘が残っていることがあるため、前後の差分で判断するのが良いでしょう) + - 提出する範囲で指摘されているようであれば提出前に訂正しましょう。 + - e2eテスト結果を修正出来ない事情がある場合や判断に迷う場合は、レビュー時に相談をしましょう。 + +### 5. コードの提出 + +- 先に個人のリポジトリにコミットします。この時、詳細欄に変更に関する具体内容を確実に記入しましょう。タイトルは簡素で分かりやすいものが好まれます。 + +- コミットが終わったら、コードをコミュニティに提案しましょう。作成したコードをコミュニティへ提案する作業の事を「Pull + Request(プルリクエスト)」といいます。 + +- Pull Requestには2つの種類があります。 + + - Draft Pull Request + - Pull Request + +- Draft Pull + Requestは、進捗状況を共有するために使います。検討の歩みがわかるため、議論が必要な項目では特に有効な手段です。 + +- Draft Pull Requestの場合は、タイトルの先頭に `WIP:` + をつけると見た目に分かりやすいです。 + +- 凡そ問題ないと判断できたら、Pull Requestを提出します。 + この先は共同作業になるので、今一度作業忘れや問題がないか確認をしてください。 + +- Pull Requestを出すときには、関連するIssueのナンバーを記載しておきます。 + + 記入例: + + ```text + タイトル:起動時の待ち時間を低減する + 内容:初期化を並列処理することで待ち時間を短縮させる + 関連Issue:ref #0000 + ``` + +- 議論する場所が分散する事を防ぐため、Pull + Requestするタイミングで問題提起した方の番号を閉じましょう。コメントに下記のような表記をすることでIssue側の議論を閉じることができます。 + + ```text + close #0000 + ``` + +### 6. コードレビュー + +- レビュー担当やコミュニティメンバーによってソースの査読が行われます。課題が発見された場合には、記述修正の提案がおこなわれます。 + +- 納得できれば「提案通りに直す」方法もありますが、課題があると感じていれば、議論を行い最良点を探してください。 + +- この段階では、既にPull + Requestが出されていますので、自分のリポジトリに修正分をプッシュするだけで自動的に追跡されます。 + +- 現在のVOICEVOXのマージルールでは、基本的に1名以上の査読が必要となっています。 + +### 7. コンフリクト対応 + +- レビューが終わると、マージ(取り込み)準備が始まります。 + +- 作業中に他の修正が取り込まれている場合には、修正箇所が重なる「コンフリクト」という現象が発生することがあります。 + +- コンフリクトが発生した場合には、Pull + Requestのページに「コンフリクトが発生している」と表示されるので、次の手順で修正を行います。 + + 1. 自分の作業リポジトリにプルします。プル元は、自分のGitHubリポジトリではなく、[VOICEVOXのリポジトリ](https://github.com/VOICEVOX/voicevox.git)を指定します。 + + 2. 変更差分をみながら、コンフリクトしている部分を正しい実装に修正します。 + + 3. 変更がすべて終わったら、手順4にあった「事前テスト」を改めて実施します。 + + 4. 問題なければ、コミットします。 + + 5. VOICEVOXのPull + Requestページで自動検査処理が走ります。この画面で「コンフリクトが発生した」という表示が消えたことを確認してください。 + +- お疲れさまでした。この工程までいけば、貢献者の仕事はおわりです。 +- マージ担当者が確認後、マージ処理をします。 +- マージが終われば、個人のブランチは削除できます。 + +### その他 + +- VOICEVOXプロジェクトメンバーは、貢献者が活動しやすいようサポートや相談に応じています。 +- こまったり、わからないことが発生したら、プロジェクトメンバーに相談しましょう。 +- [Discordコミュニティ](https://discord.gg/gJamMrqFHg)もあります。議論しながら考えをまとめたい場合など、コミュニティをうまく活用するとよいでしょう。 +- 諸事情で処理が継続できなくなった場合や、技術的ハードルが高かったりして心が折れたときは、ギブアップを宣言することも可能です。調整が可能な場合もあるので、宣言する前に相談をすることをお勧めします。 + +## 参考情報 + +- 実装時のデザインに関しては、[UX・UIデザインの方針](docs/UX・UIデザインの方針.md)を参考に実装してください。 + +- 設計詳細については、[細かい設計方針](docs/細かい設計方針.md)を参考にしてください。 + +- VOICEVOXは、様々な技術を使って実装しており、技術の理解が無いと読みづらい部分があります。全体の構造については、[コードの歩き方](docs/コードの歩き方.md)を参考にしてみてください。 + +- 色については、[色の実装](docs/色について.md)についてのドキュメントを参考にしてみてください。 + +- VOICEVOXで使用するフォントは、[フォントについて](docs/フォントについて.md)に生成方法などが書いてあります。 diff --git a/README.md b/README.md index 48a7fd5a47..c65861f4ac 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,13 @@ こちらは開発用のページになります。利用方法に関しては[VOICEVOX 公式サイト](https://voicevox.hiroshiba.jp/) をご覧ください。 -## 貢献者の方へ +## プロジェクトに貢献したいと考えている方へ + +VOICEVOXプロジェクトは興味ある方の参画を歓迎しています。 +[貢献手順について説明したガイド](./CONTRIBUTING.md)をご用意しております。 + +貢献というとプログラム作成と思われがちですが、ドキュメント執筆、テスト生成、改善提案への議論参加など様々な参加方法があります。 +初心者歓迎タスクもありますので、皆様のご参加をお待ちしております。 VOICEVOX のエディタは Electron・TypeScript・Vue・Vuex などが活用されており、全体構成がわかりにくくなっています。 [コードの歩き方](./docs/コードの歩き方.md)で構成を紹介しているので、開発の一助になれば幸いです。 From 2a95df7a2bc3d9eda08fb012958e1759a61d2cfd Mon Sep 17 00:00:00 2001 From: Romot Date: Thu, 20 Jun 2024 02:15:21 +0900 Subject: [PATCH 31/31] =?UTF-8?q?=E3=82=B7=E3=83=B3=E3=82=B0=E5=81=B4?= =?UTF-8?q?=E3=81=AE=E3=83=9D=E3=83=BC=E3=83=88=E3=83=AC=E3=83=BC=E3=83=88?= =?UTF-8?q?=E4=BD=8D=E7=BD=AE=E3=82=92=E8=AA=BF=E6=95=B4=E3=81=99=E3=82=8B?= =?UTF-8?q?=20(#2122)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * シング側のポートレート位置を調整する * 微調整 --------- Co-authored-by: Romot Co-authored-by: Hiroshiba Kazuyuki --- src/components/Sing/CharacterPortrait.vue | 35 +++++++++++++++++++---- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/components/Sing/CharacterPortrait.vue b/src/components/Sing/CharacterPortrait.vue index 2ef0f90074..3b080f872b 100644 --- a/src/components/Sing/CharacterPortrait.vue +++ b/src/components/Sing/CharacterPortrait.vue @@ -33,24 +33,47 @@ const portraitPath = computed(() => { @use "@/styles/variables" as vars; @use "@/styles/colors" as colors; +// 表示変数 +$header-margin: vars.$toolbar-height + vars.$menubar-height + 30px; // 30pxはルーラーの高さ +$right-margin: 24px; +$portrait-max-width: 40vw; +$portrait-max-height: 60vh; +$portrait-min-height: 500px; + // 画面右下に固定表示 // 幅固定、高さ可変、画像のアスペクト比を保持、wrapのwidthに合わせてheightを調整 // bottom位置はスクロールバーの上に表示 .character-portrait-wrap { opacity: 0.55; - overflow: hidden; + overflow: visible; contain: layout; pointer-events: none; position: fixed; + display: grid; + place-items: end; bottom: 0; - right: 88px; - min-width: 200px; - max-width: 20vw; + right: $right-margin; } .character-portrait { - width: 100%; - height: auto; + width: auto; + height: $portrait-max-height; + min-height: $portrait-min-height; + max-width: $portrait-max-width; + overflow: visible; backface-visibility: hidden; + object-fit: cover; + object-position: top center; +} + +// ポートレートサイズが画面サイズを超えた場合、ヘッダーを考慮してポートレートを上部基準で表示させる +// ヘッダー高さ120px+ポートレート高さ500pxだとする +@media (max-height: #{calc(#{$portrait-min-height} + #{$header-margin})}) { + .character-portrait-wrap { + top: $header-margin; // ヘッダーの高さより下に位置させる + bottom: auto; + height: calc(100vh - #{$header-margin}); + place-items: start end; + } }