From 8bdb46f30296d4017ed8a22e2afef325d01cae6b Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 6 Mar 2024 19:27:05 +0900 Subject: [PATCH 01/11] =?UTF-8?q?Improve:=20=E3=83=AC=E3=83=B3=E3=83=80?= =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=B0=E9=A0=86=E3=82=92=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/sing/domain.ts | 50 +++++++++++++++++++- src/store/singing.ts | 21 ++++----- tests/unit/lib/findPriorPhrase.spec.ts | 65 ++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 13 deletions(-) create mode 100644 tests/unit/lib/findPriorPhrase.spec.ts diff --git a/src/sing/domain.ts b/src/sing/domain.ts index 63952b58d7..ed04eb874c 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -1,4 +1,4 @@ -import { Note, Score, Tempo, TimeSignature } from "@/store/type"; +import { Note, Phrase, Score, Tempo, TimeSignature } from "@/store/type"; const BEAT_TYPES = [2, 4, 8, 16]; const MIN_BPM = 40; @@ -286,3 +286,51 @@ export function isValidVoiceKeyShift(voiceKeyShift: number) { voiceKeyShift >= -24 ); } + +export function sortPhrases(phrases: Map) { + return [...phrases.entries()].sort((a, b) => { + return a[1].startTicks - b[1].startTicks; + }); +} + +/** + * 次にレンダリングするべきPhraseを探す。 + * 存在しない場合は[undefined, undefined]を返す。 + * 優先順: + * - 再生位置が含まれるPhrase + * - 再生位置より後のPhrase + * - 再生位置より前のPhrase + * + */ +export function findPriorPhrase( + phrases: Map, + position: number +): [string, Phrase] | [undefined, undefined] { + const renderablePhrases = new Map( + [...phrases.entries()].filter(([, phrase]) => { + return phrase.state !== "PLAYABLE" && phrase.singer; + }) + ); + + if (renderablePhrases.size === 0) { + return [undefined, undefined]; + } + + // 再生位置が含まれるPhrase + for (const [phraseKey, phrase] of renderablePhrases) { + if (phrase.startTicks <= position && position <= phrase.endTicks) { + return [phraseKey, phrase]; + } + } + + const sortedPhrases = sortPhrases(renderablePhrases); + // 再生位置より後のPhrase + for (const [phraseKey, phrase] of sortedPhrases) { + if (phrase.startTicks > position) { + return [phraseKey, phrase]; + } + } + + // 再生位置より前のPhrase + return sortedPhrases[0]; +} diff --git a/src/store/singing.ts b/src/store/singing.ts index 4ba8f477cf..19b2ed2ef8 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -38,6 +38,7 @@ import { Transport, } from "@/sing/audioRendering"; import { + findPriorPhrase, getMeasureDuration, isValidNote, isValidScore, @@ -791,12 +792,6 @@ export const singingStore = createPartialStore({ return foundPhrases; }; - const getSortedPhrasesEntries = (phrases: Map) => { - return [...phrases.entries()].sort((a, b) => { - return a[1].startTicks - b[1].startTicks; - }); - }; - const fetchQuery = async ( engineId: EngineId, notes: Note[], @@ -1008,8 +1003,14 @@ export const singingStore = createPartialStore({ } // 各フレーズのレンダリングを行う - const sortedPhrasesEntries = getSortedPhrasesEntries(state.phrases); - for (const [phraseKey, phrase] of sortedPhrasesEntries) { + while (!(startRenderingRequested() || stopRenderingRequested())) { + const [phraseKey, phrase] = findPriorPhrase( + state.phrases, + playheadPosition.value + ); + if (!phrase) { + break; + } if (!phrase.singer) { continue; } @@ -1141,10 +1142,6 @@ export const singingStore = createPartialStore({ phraseState: "PLAYABLE", }); } - - if (startRenderingRequested() || stopRenderingRequested()) { - return; - } } }; diff --git a/tests/unit/lib/findPriorPhrase.spec.ts b/tests/unit/lib/findPriorPhrase.spec.ts new file mode 100644 index 0000000000..4b528117a7 --- /dev/null +++ b/tests/unit/lib/findPriorPhrase.spec.ts @@ -0,0 +1,65 @@ +import { it, expect } from "vitest"; +import { Phrase, PhraseState } from "@/store/type"; +import { DEFAULT_TPQN } from "@/sing/storeHelper"; +import { findPriorPhrase } from "@/sing/domain"; +import { EngineId, StyleId } from "@/type/preload"; + +const tempos = [ + { + position: 0, + bpm: 60, + }, +]; +const createPhrase = ( + start: number, + end: number, + state: PhraseState +): Phrase => { + return { + notes: [], + startTicks: start * DEFAULT_TPQN, + endTicks: end * DEFAULT_TPQN, + notesKeyShift: 0, + state, + tempos, + tpqn: DEFAULT_TPQN, + voiceKeyShift: 0, + singer: { + engineId: EngineId("00000000-0000-0000-0000-000000000000"), + styleId: StyleId(0), + }, + }; +}; +const basePhrases = new Map([ + ["1", createPhrase(0, 1, "WAITING_TO_BE_RENDERED")], + ["2", createPhrase(1, 2, "WAITING_TO_BE_RENDERED")], + ["3", createPhrase(2, 3, "WAITING_TO_BE_RENDERED")], + ["4", createPhrase(3, 4, "WAITING_TO_BE_RENDERED")], + ["5", createPhrase(4, 5, "WAITING_TO_BE_RENDERED")], +]); + +it("しっかり優先順位に従って探している", () => { + const phrases = structuredClone(basePhrases); + const position = 2.5 * DEFAULT_TPQN; + for (const expectation of [ + // 再生位置が含まれるPhrase + "3", + // 再生位置より後のPhrase + "4", // 早い方 + "5", // 遅い方 + // 再生位置より前のPhrase + "1", // 早い方 + "2", // 遅い方 + ]) { + const [key, phrase] = findPriorPhrase(phrases, position); + expect(key).toBe(expectation); + if (key == undefined) { + // 型アサーションのためにthrowを使う + throw new Error("key is undefined"); + } + phrases.set(key, { ...phrase, state: "PLAYABLE" }); + } + + // もう再生可能なPhraseがないのでundefined + expect(findPriorPhrase(phrases, position)).toBe(undefined); +}); From 9bd1d35252551f6e3304b77303bff4aeb477b35c Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Wed, 6 Mar 2024 20:29:58 +0900 Subject: [PATCH 02/11] =?UTF-8?q?Fix:=20=E5=80=A4=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/lib/findPriorPhrase.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/lib/findPriorPhrase.spec.ts b/tests/unit/lib/findPriorPhrase.spec.ts index 4b528117a7..1008d8d979 100644 --- a/tests/unit/lib/findPriorPhrase.spec.ts +++ b/tests/unit/lib/findPriorPhrase.spec.ts @@ -61,5 +61,5 @@ it("しっかり優先順位に従って探している", () => { } // もう再生可能なPhraseがないのでundefined - expect(findPriorPhrase(phrases, position)).toBe(undefined); + expect(findPriorPhrase(phrases, position)).toBe([undefined, undefined]); }); From 9774e027de32ce6ba8d0fa770a893295e7a9b22d Mon Sep 17 00:00:00 2001 From: Nanashi Date: Thu, 7 Mar 2024 00:23:26 +0900 Subject: [PATCH 03/11] =?UTF-8?q?Fix:=20toEqual=E3=82=92=E4=BD=BF=E3=81=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/lib/findPriorPhrase.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/lib/findPriorPhrase.spec.ts b/tests/unit/lib/findPriorPhrase.spec.ts index 1008d8d979..f3cdd593ba 100644 --- a/tests/unit/lib/findPriorPhrase.spec.ts +++ b/tests/unit/lib/findPriorPhrase.spec.ts @@ -52,7 +52,7 @@ it("しっかり優先順位に従って探している", () => { "2", // 遅い方 ]) { const [key, phrase] = findPriorPhrase(phrases, position); - expect(key).toBe(expectation); + expect(key).toEqual(expectation); if (key == undefined) { // 型アサーションのためにthrowを使う throw new Error("key is undefined"); @@ -61,5 +61,5 @@ it("しっかり優先順位に従って探している", () => { } // もう再生可能なPhraseがないのでundefined - expect(findPriorPhrase(phrases, position)).toBe([undefined, undefined]); + expect(findPriorPhrase(phrases, position)).toEqual([undefined, undefined]); }); From e6fc82ae382a909f353b52cca0751796f94e329d Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Wed, 6 Mar 2024 23:54:25 +0900 Subject: [PATCH 04/11] =?UTF-8?q?=E9=9F=B3=E5=9F=9F=E8=A3=9C=E6=AD=A3?= =?UTF-8?q?=E7=94=A8=E3=81=AE=E3=83=91=E3=83=A9=E3=83=A1=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=83=BC=E3=82=92=E5=A2=97=E3=82=84=E3=81=97=E3=81=A4=E3=81=A4?= =?UTF-8?q?=E3=80=81=E9=96=8B=E7=99=BA=E6=99=82=E3=81=AE=E3=81=BF=E3=81=AE?= =?UTF-8?q?=E6=A9=9F=E8=83=BD=E3=81=AB=20(#1902)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 音高補正にしつつ開発時のみ機能に * VoiceKey→GuidePitch * とりあえず実装としては完成 * keyRangeAdjustmentに * (note|guide)KeyShiftを消し、補正→調整にする * keyShiftを全てKeyRangeAdjustmentへ --- src/components/Sing/ToolBar.vue | 53 +++++++++++++++----------- src/sing/domain.ts | 8 ++-- src/sing/storeHelper.ts | 3 +- src/store/project.ts | 7 ++-- src/store/singing.ts | 66 ++++++++++++++++----------------- src/store/type.ts | 18 ++++----- 6 files changed, 79 insertions(+), 76 deletions(-) diff --git a/src/components/Sing/ToolBar.vue b/src/components/Sing/ToolBar.vue index fc3707f07a..d5cf21512a 100644 --- a/src/components/Sing/ToolBar.vue +++ b/src/components/Sing/ToolBar.vue @@ -3,16 +3,19 @@
- + + import { computed, watch, ref, onMounted, onUnmounted } from "vue"; import { useStore } from "@/store"; +import { isProduction } from "@/type/preload"; + import { getSnapTypes, isTriplet, isValidBeatType, isValidBeats, isValidBpm, - isValidVoiceKeyShift, + isValidKeyRangeAdjustment, } from "@/sing/domain"; import CharacterMenuButton from "@/components/Sing/CharacterMenuButton/MenuButton.vue"; import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; @@ -183,12 +188,14 @@ const redo = () => { const tempos = computed(() => store.state.tempos); const timeSignatures = computed(() => store.state.timeSignatures); -const keyShift = computed(() => store.getters.SELECTED_TRACK.voiceKeyShift); +const keyRangeAdjustment = computed( + () => store.getters.SELECTED_TRACK.keyRangeAdjustment +); const bpmInputBuffer = ref(120); const beatsInputBuffer = ref(4); const beatTypeInputBuffer = ref(4); -const keyShiftInputBuffer = ref(0); +const keyRangeAdjustmentInputBuffer = ref(0); watch( tempos, @@ -207,8 +214,8 @@ watch( { deep: true } ); -watch(keyShift, () => { - keyShiftInputBuffer.value = keyShift.value; +watch(keyRangeAdjustment, () => { + keyRangeAdjustmentInputBuffer.value = keyRangeAdjustment.value; }); const setBpmInputBuffer = (bpmStr: string | number | null) => { @@ -235,12 +242,14 @@ const setBeatTypeInputBuffer = (beatTypeStr: string | number | null) => { beatTypeInputBuffer.value = beatTypeValue; }; -const setKeyShiftInputBuffer = (keyShiftStr: string | number | null) => { - const keyShiftValue = Number(keyShiftStr); - if (!isValidVoiceKeyShift(keyShiftValue)) { +const setKeyRangeAdjustmentInputBuffer = ( + KeyRangeAdjustmentStr: string | number | null +) => { + const KeyRangeAdjustmentValue = Number(KeyRangeAdjustmentStr); + if (!isValidKeyRangeAdjustment(KeyRangeAdjustmentValue)) { return; } - keyShiftInputBuffer.value = keyShiftValue; + keyRangeAdjustmentInputBuffer.value = KeyRangeAdjustmentValue; }; const setTempo = () => { @@ -265,9 +274,9 @@ const setTimeSignature = () => { }); }; -const setKeyShift = () => { - const voiceKeyShift = keyShiftInputBuffer.value; - store.dispatch("COMMAND_SET_VOICE_KEY_SHIFT", { voiceKeyShift }); +const setKeyRangeAdjustment = () => { + const keyRangeAdjustment = keyRangeAdjustmentInputBuffer.value; + store.dispatch("COMMAND_SET_KEY_RANGE_ADJUSTMENT", { keyRangeAdjustment }); }; const playheadTicks = ref(0); diff --git a/src/sing/domain.ts b/src/sing/domain.ts index ed04eb874c..0c231c0a2f 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -279,11 +279,11 @@ export function isValidSnapType(snapType: number, tpqn: number) { return getSnapTypes(tpqn).some((value) => value === snapType); } -export function isValidVoiceKeyShift(voiceKeyShift: number) { +export function isValidKeyRangeAdjustment(keyRangeAdjustment: number) { return ( - Number.isInteger(voiceKeyShift) && - voiceKeyShift <= 24 && - voiceKeyShift >= -24 + Number.isInteger(keyRangeAdjustment) && + keyRangeAdjustment <= 24 && + keyRangeAdjustment >= -24 ); } diff --git a/src/sing/storeHelper.ts b/src/sing/storeHelper.ts index 1b8345ef95..91548d1ebe 100644 --- a/src/sing/storeHelper.ts +++ b/src/sing/storeHelper.ts @@ -8,8 +8,7 @@ export const DEFAULT_BEAT_TYPE = 4; export const generatePhraseHash = async (obj: { singer: Singer | undefined; - notesKeyShift: number; - voiceKeyShift: number; + keyRangeAdjustment: number; tpqn: number; tempos: Tempo[]; notes: Note[]; diff --git a/src/store/project.ts b/src/store/project.ts index 2064844eef..7ab4c0b6a4 100755 --- a/src/store/project.ts +++ b/src/store/project.ts @@ -111,8 +111,8 @@ const applySongProjectToStore = async ( await dispatch("SET_SINGER", { singer: tracks[0].singer, }); - await dispatch("SET_VOICE_KEY_SHIFT", { - voiceKeyShift: tracks[0].voiceKeyShift, + await dispatch("SET_KEY_RANGE_ADJUSTMENT", { + keyRangeAdjustment: tracks[0].keyRangeAdjustment, }); await dispatch("SET_SCORE", { score: { @@ -431,8 +431,7 @@ export const projectStore = createPartialStore({ tracks: [ { singer: undefined, - notesKeyShift: 0, - voiceKeyShift: 0, + keyRangeAdjustment: 0, notes: [], }, ], diff --git a/src/store/singing.ts b/src/store/singing.ts index 19b2ed2ef8..437d266dc4 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -45,7 +45,7 @@ import { isValidSnapType, isValidTempo, isValidTimeSignature, - isValidVoiceKeyShift, + isValidKeyRangeAdjustment, secondToTick, tickToSecond, } from "@/sing/domain"; @@ -145,8 +145,7 @@ export const generateSingingStoreInitialScore = () => { tracks: [ { singer: undefined, - notesKeyShift: 0, - voiceKeyShift: 0, + keyRangeAdjustment: 0, notes: [], }, ], @@ -231,18 +230,18 @@ export const singingStore = createPartialStore({ }, }, - SET_VOICE_KEY_SHIFT: { - mutation(state, { voiceKeyShift }: { voiceKeyShift: number }) { - state.tracks[selectedTrackIndex].voiceKeyShift = voiceKeyShift; + SET_KEY_RANGE_ADJUSTMENT: { + mutation(state, { keyRangeAdjustment }: { keyRangeAdjustment: number }) { + state.tracks[selectedTrackIndex].keyRangeAdjustment = keyRangeAdjustment; }, async action( { dispatch, commit }, - { voiceKeyShift }: { voiceKeyShift: number } + { keyRangeAdjustment }: { keyRangeAdjustment: number } ) { - if (!isValidVoiceKeyShift(voiceKeyShift)) { - throw new Error("The voiceKeyShift is invalid."); + if (!isValidKeyRangeAdjustment(keyRangeAdjustment)) { + throw new Error("The keyRangeAdjustment is invalid."); } - commit("SET_VOICE_KEY_SHIFT", { voiceKeyShift }); + commit("SET_KEY_RANGE_ADJUSTMENT", { keyRangeAdjustment }); dispatch("RENDER"); }, @@ -747,8 +746,7 @@ export const singingStore = createPartialStore({ async action({ state, getters, commit, dispatch }) { const searchPhrases = async ( singer: Singer | undefined, - notesKeyShift: number, - voiceKeyShift: number, + keyRangeAdjustment: number, tpqn: number, tempos: Tempo[], notes: Note[] @@ -768,16 +766,14 @@ export const singingStore = createPartialStore({ const phraseLastNote = phraseNotes[phraseNotes.length - 1]; const hash = await generatePhraseHash({ singer, - notesKeyShift, - voiceKeyShift, + keyRangeAdjustment, tpqn, tempos, notes: phraseNotes, }); foundPhrases.set(hash, { singer, - notesKeyShift, - voiceKeyShift, + keyRangeAdjustment, tpqn, tempos, notes: phraseNotes, @@ -797,7 +793,7 @@ export const singingStore = createPartialStore({ notes: Note[], tempos: Tempo[], tpqn: number, - notesKeyShift: number, + keyRangeAdjustment: number, frameRate: number, restDurationSeconds: number ) => { @@ -829,8 +825,10 @@ export const singingStore = createPartialStore({ .replace("うぉ", "ウォ") .replace("は", "ハ") .replace("へ", "ヘ"); + // トランスポーズする + const key = note.noteNumber - keyRangeAdjustment; notesForRequestToEngine.push({ - key: note.noteNumber + notesKeyShift, + key, frameLength: noteFrameLength, lyric, }); @@ -872,12 +870,12 @@ export const singingStore = createPartialStore({ return frameAudioQuery.phonemes.map((value) => value.phoneme).join(" "); }; - const shiftVoiceKey = ( - voiceKeyShift: number, + const shiftGuidePitch = ( + pitchShift: number, frameAudioQuery: FrameAudioQuery ) => { frameAudioQuery.f0 = frameAudioQuery.f0.map((value) => { - return value * Math.pow(2, voiceKeyShift / 12); + return value * Math.pow(2, pitchShift / 12); }); }; @@ -937,8 +935,7 @@ export const singingStore = createPartialStore({ const tempos = state.tempos.map((value) => ({ ...value })); const track = getters.SELECTED_TRACK; const singer = track.singer ? { ...track.singer } : undefined; - const notesKeyShift = track.notesKeyShift; - const voiceKeyShift = track.voiceKeyShift; + const keyRangeAdjustment = track.keyRangeAdjustment; const notes = track.notes .map((value) => ({ ...value })) .filter((value) => !state.overlappingNoteIds.has(value.id)); @@ -946,8 +943,7 @@ export const singingStore = createPartialStore({ // フレーズを更新する const foundPhrases = await searchPhrases( singer, - notesKeyShift, - voiceKeyShift, + keyRangeAdjustment, tpqn, tempos, notes @@ -1037,7 +1033,7 @@ export const singingStore = createPartialStore({ phrase.notes, phrase.tempos, phrase.tpqn, - phrase.notesKeyShift, + phrase.keyRangeAdjustment, frameRate, restDurationSeconds ).catch((error) => { @@ -1053,7 +1049,7 @@ export const singingStore = createPartialStore({ `Fetched frame audio query. Phonemes are "${phonemes}".` ); - shiftVoiceKey(phrase.voiceKeyShift, frameAudioQuery); + shiftGuidePitch(phrase.keyRangeAdjustment, frameAudioQuery); const startTime = calcStartTime( phrase.notes, @@ -1974,18 +1970,20 @@ export const singingCommandStore = transformCommandStore( dispatch("RENDER"); }, }, - COMMAND_SET_VOICE_KEY_SHIFT: { - mutation(draft, { voiceKeyShift }) { - singingStore.mutations.SET_VOICE_KEY_SHIFT(draft, { voiceKeyShift }); + COMMAND_SET_KEY_RANGE_ADJUSTMENT: { + mutation(draft, { keyRangeAdjustment }) { + singingStore.mutations.SET_KEY_RANGE_ADJUSTMENT(draft, { + keyRangeAdjustment, + }); }, async action( { dispatch, commit }, - { voiceKeyShift }: { voiceKeyShift: number } + { keyRangeAdjustment }: { keyRangeAdjustment: number } ) { - if (!isValidVoiceKeyShift(voiceKeyShift)) { - throw new Error("The voiceKeyShift is invalid."); + if (!isValidKeyRangeAdjustment(keyRangeAdjustment)) { + throw new Error("The keyRangeAdjustment is invalid."); } - commit("COMMAND_SET_VOICE_KEY_SHIFT", { voiceKeyShift }); + commit("COMMAND_SET_KEY_RANGE_ADJUSTMENT", { keyRangeAdjustment }); dispatch("RENDER"); }, diff --git a/src/store/type.ts b/src/store/type.ts index 0f2df9fb0b..efeb26839a 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -751,8 +751,7 @@ export type Singer = z.infer; export const trackSchema = z.object({ singer: singerSchema.optional(), - notesKeyShift: z.number(), - voiceKeyShift: z.number(), + keyRangeAdjustment: z.number(), // 音域調整量 notes: z.array(noteSchema), }); export type Track = z.infer; @@ -765,8 +764,7 @@ export type PhraseState = export type Phrase = { singer?: Singer; - notesKeyShift: number; - voiceKeyShift: number; + keyRangeAdjustment: number; tpqn: number; tempos: Tempo[]; notes: Note[]; @@ -818,9 +816,9 @@ export type SingingStoreTypes = { action(payload: { singer?: Singer }): void; }; - SET_VOICE_KEY_SHIFT: { - mutation: { voiceKeyShift: number }; - action(payload: { voiceKeyShift: number }): void; + SET_KEY_RANGE_ADJUSTMENT: { + mutation: { keyRangeAdjustment: number }; + action(payload: { keyRangeAdjustment: number }): void; }; SET_SCORE: { @@ -1033,9 +1031,9 @@ export type SingingCommandStoreTypes = { action(payload: { singer: Singer }): void; }; - COMMAND_SET_VOICE_KEY_SHIFT: { - mutation: { voiceKeyShift: number }; - action(payload: { voiceKeyShift: number }): void; + COMMAND_SET_KEY_RANGE_ADJUSTMENT: { + mutation: { keyRangeAdjustment: number }; + action(payload: { keyRangeAdjustment: number }): void; }; COMMAND_SET_TEMPO: { From 1ff8b6dc490703a617a5fb40e43f86c1766addeb Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 7 Mar 2024 00:28:37 +0900 Subject: [PATCH 05/11] =?UTF-8?q?Update:=20main=E3=81=AB=E8=BF=BD=E5=BE=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/lib/findPriorPhrase.spec.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/lib/findPriorPhrase.spec.ts b/tests/unit/lib/findPriorPhrase.spec.ts index f3cdd593ba..701735bc25 100644 --- a/tests/unit/lib/findPriorPhrase.spec.ts +++ b/tests/unit/lib/findPriorPhrase.spec.ts @@ -19,11 +19,10 @@ const createPhrase = ( notes: [], startTicks: start * DEFAULT_TPQN, endTicks: end * DEFAULT_TPQN, - notesKeyShift: 0, + keyRangeAdjustment: 0, state, tempos, tpqn: DEFAULT_TPQN, - voiceKeyShift: 0, singer: { engineId: EngineId("00000000-0000-0000-0000-000000000000"), styleId: StyleId(0), From 5f541fae5a15918b0b1ffbb9e279d7395accf269 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 7 Mar 2024 20:01:53 +0900 Subject: [PATCH 06/11] =?UTF-8?q?Improve:=20=E3=83=AC=E3=83=93=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E3=82=92=E5=8F=8D=E6=98=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Hiroshiba --- src/sing/domain.ts | 21 +++++++-------------- src/store/singing.ts | 20 +++++++++++++++----- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/src/sing/domain.ts b/src/sing/domain.ts index 0c231c0a2f..22e15f58ce 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -287,7 +287,7 @@ export function isValidKeyRangeAdjustment(keyRangeAdjustment: number) { ); } -export function sortPhrases(phrases: Map) { +export function toSortedPhrases(phrases: Map) { return [...phrases.entries()].sort((a, b) => { return a[1].startTicks - b[1].startTicks; }); @@ -295,7 +295,7 @@ export function sortPhrases(phrases: Map) { /** * 次にレンダリングするべきPhraseを探す。 - * 存在しない場合は[undefined, undefined]を返す。 + * phrasesが空の場合はエ * 優先順: * - 再生位置が含まれるPhrase * - 再生位置より後のPhrase @@ -305,25 +305,18 @@ export function sortPhrases(phrases: Map) { export function findPriorPhrase( phrases: Map, position: number -): [string, Phrase] | [undefined, undefined] { - const renderablePhrases = new Map( - [...phrases.entries()].filter(([, phrase]) => { - return phrase.state !== "PLAYABLE" && phrase.singer; - }) - ); - - if (renderablePhrases.size === 0) { - return [undefined, undefined]; +): [string, Phrase] { + if (phrases.size === 0) { + throw new Error("phrases is empty"); } - // 再生位置が含まれるPhrase - for (const [phraseKey, phrase] of renderablePhrases) { + for (const [phraseKey, phrase] of phrases) { if (phrase.startTicks <= position && position <= phrase.endTicks) { return [phraseKey, phrase]; } } - const sortedPhrases = sortPhrases(renderablePhrases); + const sortedPhrases = toSortedPhrases(phrases); // 再生位置より後のPhrase for (const [phraseKey, phrase] of sortedPhrases) { if (phrase.startTicks > position) { diff --git a/src/store/singing.ts b/src/store/singing.ts index 5a121d09a3..a2e0e1230e 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -998,18 +998,28 @@ export const singingStore = createPartialStore({ return; } + const phrasesToBeRendered = new Map( + [...state.phrases.entries()].filter(([, phrase]) => { + return ( + (phrase.state === "WAITING_TO_BE_RENDERED" || + phrase.state === "COULD_NOT_RENDER") && + phrase.singer + ); + }) + ); // 各フレーズのレンダリングを行う - while (!(startRenderingRequested() || stopRenderingRequested())) { + while ( + !(startRenderingRequested() || stopRenderingRequested()) && + phrasesToBeRendered.size > 0 + ) { const [phraseKey, phrase] = findPriorPhrase( - state.phrases, + phrasesToBeRendered, playheadPosition.value ); if (!phrase) { break; } - if (!phrase.singer) { - continue; - } + phrasesToBeRendered.delete(phraseKey); if ( phrase.state === "WAITING_TO_BE_RENDERED" || From 1b36def7c92f071d0c0e8944cacc9a11ed13ed6b Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 7 Mar 2024 20:03:58 +0900 Subject: [PATCH 07/11] Change: findPriorPhrases -> selectPriorPhrases --- src/sing/domain.ts | 4 ++-- src/store/singing.ts | 4 ++-- ...PriorPhrase.spec.ts => selectPriorPhrase.spec.ts} | 12 +++++++----- 3 files changed, 11 insertions(+), 9 deletions(-) rename tests/unit/lib/{findPriorPhrase.spec.ts => selectPriorPhrase.spec.ts} (83%) diff --git a/src/sing/domain.ts b/src/sing/domain.ts index 22e15f58ce..5a8539e9b2 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -302,12 +302,12 @@ export function toSortedPhrases(phrases: Map) { * - 再生位置より前のPhrase * */ -export function findPriorPhrase( +export function selectPriorPhrase( phrases: Map, position: number ): [string, Phrase] { if (phrases.size === 0) { - throw new Error("phrases is empty"); + throw new Error("Received empty phrases"); } // 再生位置が含まれるPhrase for (const [phraseKey, phrase] of phrases) { diff --git a/src/store/singing.ts b/src/store/singing.ts index a2e0e1230e..b5db32fb2d 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -38,7 +38,7 @@ import { Transport, } from "@/sing/audioRendering"; import { - findPriorPhrase, + selectPriorPhrase, getMeasureDuration, isValidNote, isValidScore, @@ -1012,7 +1012,7 @@ export const singingStore = createPartialStore({ !(startRenderingRequested() || stopRenderingRequested()) && phrasesToBeRendered.size > 0 ) { - const [phraseKey, phrase] = findPriorPhrase( + const [phraseKey, phrase] = selectPriorPhrase( phrasesToBeRendered, playheadPosition.value ); diff --git a/tests/unit/lib/findPriorPhrase.spec.ts b/tests/unit/lib/selectPriorPhrase.spec.ts similarity index 83% rename from tests/unit/lib/findPriorPhrase.spec.ts rename to tests/unit/lib/selectPriorPhrase.spec.ts index 701735bc25..dcdc46d272 100644 --- a/tests/unit/lib/findPriorPhrase.spec.ts +++ b/tests/unit/lib/selectPriorPhrase.spec.ts @@ -1,7 +1,7 @@ import { it, expect } from "vitest"; import { Phrase, PhraseState } from "@/store/type"; import { DEFAULT_TPQN } from "@/sing/storeHelper"; -import { findPriorPhrase } from "@/sing/domain"; +import { selectPriorPhrase } from "@/sing/domain"; import { EngineId, StyleId } from "@/type/preload"; const tempos = [ @@ -50,15 +50,17 @@ it("しっかり優先順位に従って探している", () => { "1", // 早い方 "2", // 遅い方 ]) { - const [key, phrase] = findPriorPhrase(phrases, position); + const [key] = selectPriorPhrase(phrases, position); expect(key).toEqual(expectation); if (key == undefined) { // 型アサーションのためにthrowを使う throw new Error("key is undefined"); } - phrases.set(key, { ...phrase, state: "PLAYABLE" }); + phrases.delete(key); } - // もう再生可能なPhraseがないのでundefined - expect(findPriorPhrase(phrases, position)).toEqual([undefined, undefined]); + // もう再生可能なPhraseがないのでthrow + expect(() => { + selectPriorPhrase(phrases, position); + }).toThrow("Received empty phrases"); }); From bb88573ab2c51b9f52c9ad8a28c6416a97299a85 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 7 Mar 2024 22:27:07 +0900 Subject: [PATCH 08/11] =?UTF-8?q?Delete:=20=E4=B8=8D=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E5=9E=8B=E3=82=A2=E3=82=B5=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: sigprogramming --- src/store/singing.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/store/singing.ts b/src/store/singing.ts index b5db32fb2d..58a06b1ea1 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -1016,9 +1016,6 @@ export const singingStore = createPartialStore({ phrasesToBeRendered, playheadPosition.value ); - if (!phrase) { - break; - } phrasesToBeRendered.delete(phraseKey); if ( From 32705ab00b4994ad3d6c86e614311ece7612a45d Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Thu, 7 Mar 2024 22:27:59 +0900 Subject: [PATCH 09/11] =?UTF-8?q?Add:=20=E5=9E=8B=E3=82=A2=E3=82=B5?= =?UTF-8?q?=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/store/singing.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/store/singing.ts b/src/store/singing.ts index 58a06b1ea1..0a5f654a80 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -1016,6 +1016,9 @@ export const singingStore = createPartialStore({ phrasesToBeRendered, playheadPosition.value ); + if (!phrase.singer) { + throw new Error("assert: phrase.singer != undefined"); + } phrasesToBeRendered.delete(phraseKey); if ( From 86d203a259c6790d7a1e00470d6ca3d6aa6fbe53 Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Mon, 18 Mar 2024 19:46:44 +0900 Subject: [PATCH 10/11] Update src/sing/domain.ts --- src/sing/domain.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sing/domain.ts b/src/sing/domain.ts index 5a8539e9b2..43eef87595 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -300,7 +300,6 @@ export function toSortedPhrases(phrases: Map) { * - 再生位置が含まれるPhrase * - 再生位置より後のPhrase * - 再生位置より前のPhrase - * */ export function selectPriorPhrase( phrases: Map, From d4fc5af63fa785f6847e674e4e891c801de6f41f Mon Sep 17 00:00:00 2001 From: Hiroshiba Date: Mon, 18 Mar 2024 19:46:51 +0900 Subject: [PATCH 11/11] Update src/sing/domain.ts --- src/sing/domain.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sing/domain.ts b/src/sing/domain.ts index 43eef87595..a56aed928d 100644 --- a/src/sing/domain.ts +++ b/src/sing/domain.ts @@ -295,7 +295,7 @@ export function toSortedPhrases(phrases: Map) { /** * 次にレンダリングするべきPhraseを探す。 - * phrasesが空の場合はエ + * phrasesが空の場合はエラー * 優先順: * - 再生位置が含まれるPhrase * - 再生位置より後のPhrase