diff --git a/src/components/Sing/CharacterMenuButton.vue b/src/components/Sing/CharacterMenuButton.vue index 03d20c6c07..b16b4ed4e9 100644 --- a/src/components/Sing/CharacterMenuButton.vue +++ b/src/components/Sing/CharacterMenuButton.vue @@ -198,15 +198,11 @@ const getDefaultStyle = (speakerUuid: string) => { }; const selectedCharacterInfo = computed(() => { - if ( - userOrderedCharacterInfos.value == undefined || - store.state.singer == undefined - ) + const singer = store.state.tracks[0].singer; + if (userOrderedCharacterInfos.value == undefined || !singer) { return undefined; - return store.getters.CHARACTER_INFO( - store.state.singer.engineId, - store.state.singer.styleId - ); + } + return store.getters.CHARACTER_INFO(singer.engineId, singer.styleId); }); const selectedSpeakerUuid = computed(() => { @@ -217,8 +213,8 @@ const selectedStyleId = computed( () => selectedCharacterInfo.value?.metas.styles.find( (style) => - style.styleId === store.state.singer?.styleId && - style.engineId === store.state.singer?.engineId + style.styleId === store.state.tracks[0].singer?.styleId && + style.engineId === store.state.tracks[0].singer?.engineId )?.styleId ); diff --git a/src/components/Sing/CharacterPortrait.vue b/src/components/Sing/CharacterPortrait.vue index f998d581cb..2612ab4e3a 100644 --- a/src/components/Sing/CharacterPortrait.vue +++ b/src/components/Sing/CharacterPortrait.vue @@ -14,7 +14,7 @@ const isShowSinger = computed(() => store.state.isShowSinger); const portraitPath = computed(() => { const userOrderedCharacterInfos = store.getters.USER_ORDERED_CHARACTER_INFOS("singerLike"); - const singer = store.state.singer; + const singer = store.state.tracks[0].singer; if (!userOrderedCharacterInfos || !singer) { return undefined; } diff --git a/src/components/Sing/ScoreSequencer.vue b/src/components/Sing/ScoreSequencer.vue index 8a2f94f504..3786fd2733 100644 --- a/src/components/Sing/ScoreSequencer.vue +++ b/src/components/Sing/ScoreSequencer.vue @@ -228,13 +228,13 @@ const isSelfEventTarget = (event: UIEvent) => { const store = useStore(); const state = store.state; // 分解能(Ticks Per Quarter Note) -const tpqn = computed(() => state.score.tpqn); +const tpqn = computed(() => state.tpqn); // テンポ -const tempos = computed(() => state.score.tempos); +const tempos = computed(() => state.tempos); // 拍子 -const timeSignatures = computed(() => state.score.timeSignatures); +const timeSignatures = computed(() => state.timeSignatures); // ノート -const notes = computed(() => state.score.notes); +const notes = computed(() => state.tracks[0].notes); const unselectedNotes = computed(() => { const selectedNoteIds = state.selectedNoteIds; return notes.value.filter((value) => !selectedNoteIds.has(value.id)); @@ -330,7 +330,7 @@ let dragStartNoteNumber = 0; let dragStartGuideLineTicks = 0; let draggingNoteId = ""; // FIXME: 無効状態はstring以外の型にする let executePreviewProcess = false; -let edited = false; // プレビュー終了時にScoreの変更を行うかどうかを表す変数 +let edited = false; // プレビュー終了時にstore.stateの更新を行うかどうかを表す変数 // ダブルクリック let mouseDownNoteId: string | undefined; const clickedNoteIds: [string | undefined, string | undefined] = [ diff --git a/src/components/Sing/SequencerNote.vue b/src/components/Sing/SequencerNote.vue index adf4f5efe6..c5840ec843 100644 --- a/src/components/Sing/SequencerNote.vue +++ b/src/components/Sing/SequencerNote.vue @@ -73,7 +73,7 @@ const emit = const store = useStore(); const state = store.state; -const tpqn = computed(() => state.score.tpqn); +const tpqn = computed(() => state.tpqn); const zoomX = computed(() => state.sequencerZoomX); const zoomY = computed(() => state.sequencerZoomY); const positionX = computed(() => { @@ -150,7 +150,7 @@ const onLyricInputKeyDown = (event: KeyboardEvent) => { if (event.key === "Tab") { event.preventDefault(); const noteId = props.note.id; - const notes = state.score.notes; + const notes = state.tracks[0].notes; const index = notes.findIndex((value) => value.id === noteId); if (index === -1) { return; diff --git a/src/components/Sing/SequencerRuler.vue b/src/components/Sing/SequencerRuler.vue index 88ceb54e77..55f854592d 100644 --- a/src/components/Sing/SequencerRuler.vue +++ b/src/components/Sing/SequencerRuler.vue @@ -76,8 +76,8 @@ const store = useStore(); const state = store.state; const height = ref(32); const playheadTicks = ref(0); -const tpqn = computed(() => state.score.tpqn); -const timeSignatures = computed(() => state.score.timeSignatures); +const tpqn = computed(() => state.tpqn); +const timeSignatures = computed(() => state.timeSignatures); const zoomX = computed(() => state.sequencerZoomX); const measureWidth = computed(() => { const measureDuration = getMeasureDuration( diff --git a/src/components/Sing/ToolBar.vue b/src/components/Sing/ToolBar.vue index 2fdc54081d..ffb5c49c25 100644 --- a/src/components/Sing/ToolBar.vue +++ b/src/components/Sing/ToolBar.vue @@ -125,32 +125,30 @@ const userOrderedCharacterInfos = computed(() => store.getters.USER_ORDERED_CHARACTER_INFOS("singerLike") ); const selectedCharacterInfo = computed(() => { - if (!userOrderedCharacterInfos.value || !store.state.singer) { + const singer = store.state.tracks[0].singer; + if (!userOrderedCharacterInfos.value || !singer) { return undefined; } - return store.getters.CHARACTER_INFO( - store.state.singer.engineId, - store.state.singer.styleId - ); + return store.getters.CHARACTER_INFO(singer.engineId, singer.styleId); }); const selectedCharacterName = computed(() => { return selectedCharacterInfo.value?.metas.speakerName; }); const selectedCharacterStyleDescription = computed(() => { const style = selectedCharacterInfo.value?.metas.styles.find((style) => { + const singer = store.state.tracks[0].singer; return ( - style.styleId === store.state.singer?.styleId && - style.engineId === store.state.singer?.engineId + style.styleId === singer?.styleId && style.engineId === singer?.engineId ); }); return style != undefined ? getStyleDescription(style) : ""; }); const selectedStyleIconPath = computed(() => { const styles = selectedCharacterInfo.value?.metas.styles; + const singer = store.state.tracks[0].singer; return styles?.find((style) => { return ( - style.styleId === store.state.singer?.styleId && - style.engineId === store.state.singer?.engineId + style.styleId === singer?.styleId && style.engineId === singer?.engineId ); })?.iconPath; }); @@ -197,8 +195,8 @@ const playheadPositionStr = computed(() => { return `${minStr}:${secStr}.${milliSecStr}`; }); -const tempos = computed(() => store.state.score.tempos); -const timeSignatures = computed(() => store.state.score.timeSignatures); +const tempos = computed(() => store.state.tempos); +const timeSignatures = computed(() => store.state.timeSignatures); const nowPlaying = computed(() => store.state.nowPlaying); watch( @@ -263,7 +261,7 @@ const volume = computed({ }); const snapTypeSelectOptions = computed(() => { - const tpqn = store.state.score.tpqn; + const tpqn = store.state.tpqn; return getSnapTypes(tpqn) .sort((a, b) => { if (isTriplet(a) === isTriplet(b)) { diff --git a/src/sing/storeHelper.ts b/src/sing/storeHelper.ts index 35e998b11d..9ce1628fa2 100644 --- a/src/sing/storeHelper.ts +++ b/src/sing/storeHelper.ts @@ -1,4 +1,4 @@ -import { Note, Score, Singer } from "@/store/type"; +import { Note, Singer, Tempo } from "@/store/type"; import { generateHash } from "@/sing/utility"; export const DEFAULT_TPQN = 480; @@ -6,28 +6,11 @@ export const DEFAULT_BPM = 120; export const DEFAULT_BEATS = 4; export const DEFAULT_BEAT_TYPE = 4; -export const copyScore = (score: Score): Score => { - return { - tpqn: score.tpqn, - tempos: score.tempos.map((value) => ({ ...value })), - timeSignatures: score.timeSignatures.map((value) => ({ ...value })), - notes: score.notes.map((value) => ({ ...value })), - }; -}; - -export const copySinger = (singer?: Singer): Singer | undefined => { - if (!singer) { - return undefined; - } - return { - engineId: singer.engineId, - styleId: singer.styleId, - }; -}; - -export const generateSingerAndScoreHash = async (obj: { +export const generatePhraseHash = async (obj: { singer: Singer | undefined; - score: Score; + tpqn: number; + tempos: Tempo[]; + notes: Note[]; }) => { return generateHash(obj); }; diff --git a/src/store/singing.ts b/src/store/singing.ts index d0bb60c7a2..71ff2f8416 100644 --- a/src/store/singing.ts +++ b/src/store/singing.ts @@ -50,9 +50,7 @@ import { DEFAULT_TPQN, FrequentlyUpdatedState, OverlappingNotesDetector, - copyScore, - copySinger, - generateSingerAndScoreHash, + generatePhraseHash, } from "@/sing/storeHelper"; import { getDoremiFromNoteNumber } from "@/sing/viewHelper"; import { @@ -118,24 +116,26 @@ const phraseAudioBlobCache = new Map(); const animationTimer = new AnimationTimer(); export const singingStoreState: SingingStoreState = { - singer: undefined, - score: { - tpqn: DEFAULT_TPQN, - tempos: [ - { - position: 0, - bpm: DEFAULT_BPM, - }, - ], - timeSignatures: [ - { - measureNumber: 1, - beats: DEFAULT_BEATS, - beatType: DEFAULT_BEAT_TYPE, - }, - ], - notes: [], - }, + tpqn: DEFAULT_TPQN, + tempos: [ + { + position: 0, + bpm: DEFAULT_BPM, + }, + ], + timeSignatures: [ + { + measureNumber: 1, + beats: DEFAULT_BEATS, + beatType: DEFAULT_BEAT_TYPE, + }, + ], + tracks: [ + { + singer: undefined, + notes: [], + }, + ], phrases: new Map(), // NOTE: UIの状態は試行のためsinging.tsに局所化する+Hydrateが必要 isShowSinger: true, @@ -169,7 +169,7 @@ export const singingStore = createPartialStore({ SET_SINGER: { mutation(state, { singer }: { singer?: Singer }) { - state.singer = singer; + state.tracks[0].singer = singer; }, async action( { state, getters, dispatch, commit }, @@ -217,7 +217,10 @@ export const singingStore = createPartialStore({ state.overlappingNoteIds.clear(); state.editingLyricNoteId = undefined; state.selectedNoteIds.clear(); - state.score = score; + state.tpqn = score.tpqn; + state.tempos = score.tempos; + state.timeSignatures = score.timeSignatures; + state.tracks[0].notes = score.notes; overlappingNotesDetector.addNotes(score.notes); state.overlappingNoteIds = overlappingNotesDetector.getOverlappingNoteIds(); @@ -244,17 +247,17 @@ export const singingStore = createPartialStore({ SET_TEMPO: { mutation(state, { tempo }: { tempo: Tempo }) { - const index = state.score.tempos.findIndex((value) => { + const index = state.tempos.findIndex((value) => { return value.position === tempo.position; }); - const tempos = [...state.score.tempos]; + const tempos = [...state.tempos]; if (index !== -1) { tempos.splice(index, 1, tempo); } else { tempos.push(tempo); tempos.sort((a, b) => a.position - b.position); } - state.score.tempos = tempos; + state.tempos = tempos; }, // テンポを設定する。既に同じ位置にテンポが存在する場合は置き換える。 async action( @@ -280,13 +283,13 @@ export const singingStore = createPartialStore({ REMOVE_TEMPO: { mutation(state, { position }: { position: number }) { - const index = state.score.tempos.findIndex((value) => { + const index = state.tempos.findIndex((value) => { return value.position === position; }); if (index === -1) { throw new Error("The tempo does not exist."); } - const tempos = [...state.score.tempos]; + const tempos = [...state.tempos]; if (index === 0) { tempos.splice(index, 1, { position: 0, @@ -295,14 +298,14 @@ export const singingStore = createPartialStore({ } else { tempos.splice(index, 1); } - state.score.tempos = tempos; + state.tempos = tempos; }, // テンポを削除する。先頭のテンポの場合はデフォルトのテンポに置き換える。 async action( { state, getters, commit, dispatch }, { position }: { position: number } ) { - const exists = state.score.tempos.some((value) => { + const exists = state.tempos.some((value) => { return value.position === position; }); if (!exists) { @@ -323,17 +326,17 @@ export const singingStore = createPartialStore({ SET_TIME_SIGNATURE: { mutation(state, { timeSignature }: { timeSignature: TimeSignature }) { - const index = state.score.timeSignatures.findIndex((value) => { + const index = state.timeSignatures.findIndex((value) => { return value.measureNumber === timeSignature.measureNumber; }); - const timeSignatures = [...state.score.timeSignatures]; + const timeSignatures = [...state.timeSignatures]; if (index !== -1) { timeSignatures.splice(index, 1, timeSignature); } else { timeSignatures.push(timeSignature); timeSignatures.sort((a, b) => a.measureNumber - b.measureNumber); } - state.score.timeSignatures = timeSignatures; + state.timeSignatures = timeSignatures; }, // 拍子を設定する。既に同じ位置に拍子が存在する場合は置き換える。 async action( @@ -349,13 +352,13 @@ export const singingStore = createPartialStore({ REMOVE_TIME_SIGNATURE: { mutation(state, { measureNumber }: { measureNumber: number }) { - const index = state.score.timeSignatures.findIndex((value) => { + const index = state.timeSignatures.findIndex((value) => { return value.measureNumber === measureNumber; }); if (index === -1) { throw new Error("The time signature does not exist."); } - const timeSignatures = [...state.score.timeSignatures]; + const timeSignatures = [...state.timeSignatures]; if (index === 0) { timeSignatures.splice(index, 1, { measureNumber: 1, @@ -365,14 +368,14 @@ export const singingStore = createPartialStore({ } else { timeSignatures.splice(index, 1); } - state.score.timeSignatures = timeSignatures; + state.timeSignatures = timeSignatures; }, // 拍子を削除する。先頭の拍子の場合はデフォルトの拍子に置き換える。 async action( { state, commit }, { measureNumber }: { measureNumber: number } ) { - const exists = state.score.timeSignatures.some((value) => { + const exists = state.timeSignatures.some((value) => { return value.measureNumber === measureNumber; }); if (!exists) { @@ -384,16 +387,16 @@ export const singingStore = createPartialStore({ NOTE_IDS: { getter(state) { - const noteIds = state.score.notes.map((value) => value.id); + const noteIds = state.tracks[0].notes.map((value) => value.id); return new Set(noteIds); }, }, ADD_NOTES: { mutation(state, { notes }: { notes: Note[] }) { - const scoreNotes = [...state.score.notes, ...notes]; - scoreNotes.sort((a, b) => a.position - b.position); - state.score.notes = scoreNotes; + const newNotes = [...state.tracks[0].notes, ...notes]; + newNotes.sort((a, b) => a.position - b.position); + state.tracks[0].notes = newNotes; overlappingNotesDetector.addNotes(notes); state.overlappingNoteIds = overlappingNotesDetector.getOverlappingNoteIds(); @@ -418,11 +421,11 @@ export const singingStore = createPartialStore({ for (const note of notes) { notesMap.set(note.id, note); } - const scoreNotes = state.score.notes.map((value) => { + const newNotes = state.tracks[0].notes.map((value) => { return notesMap.get(value.id) ?? value; }); - scoreNotes.sort((a, b) => a.position - b.position); - state.score.notes = scoreNotes; + newNotes.sort((a, b) => a.position - b.position); + state.tracks[0].notes = newNotes; overlappingNotesDetector.updateNotes(notes); state.overlappingNoteIds = overlappingNotesDetector.getOverlappingNoteIds(); @@ -444,7 +447,7 @@ export const singingStore = createPartialStore({ REMOVE_NOTES: { mutation(state, { noteIds }: { noteIds: string[] }) { const noteIdsSet = new Set(noteIds); - const notes = state.score.notes.filter((value) => { + const notes = state.tracks[0].notes.filter((value) => { return noteIdsSet.has(value.id); }); overlappingNotesDetector.removeNotes(notes); @@ -459,7 +462,7 @@ export const singingStore = createPartialStore({ for (const noteId of noteIds) { state.selectedNoteIds.delete(noteId); } - state.score.notes = state.score.notes.filter((value) => { + state.tracks[0].notes = state.tracks[0].notes.filter((value) => { return !noteIdsSet.has(value.id); }); }, @@ -597,7 +600,7 @@ export const singingStore = createPartialStore({ state.sequencerSnapType = snapType; }, async action({ state, commit }, { snapType }) { - const tpqn = state.score.tpqn; + const tpqn = state.tpqn; if (!isValidSnapType(snapType, tpqn)) { throw new Error("The snap type is invalid."); } @@ -629,17 +632,13 @@ export const singingStore = createPartialStore({ TICK_TO_SECOND: { getter: (state) => (position) => { - const tpqn = state.score.tpqn; - const tempos = state.score.tempos; - return tickToSecond(position, tempos, tpqn); + return tickToSecond(position, state.tempos, state.tpqn); }, }, SECOND_TO_TICK: { getter: (state) => (time) => { - const tpqn = state.score.tpqn; - const tempos = state.score.tempos; - return secondToTick(time, tempos, tpqn); + return secondToTick(time, state.tempos, state.tpqn); }, }, @@ -809,20 +808,12 @@ export const singingStore = createPartialStore({ */ RENDER: { async action({ state, getters, commit, dispatch }) { - const deleteOverlappingNotes = ( - score: Score, - overlappingNoteIds: Set - ) => { - score.notes = score.notes.filter((value) => { - return !overlappingNoteIds.has(value.id); - }); - }; - const searchPhrases = async ( singer: Singer | undefined, - score: Score + tpqn: number, + tempos: Tempo[], + notes: Note[] ) => { - const notes = score.notes; const foundPhrases = new Map(); let phraseNotes: Note[] = []; for (let i = 0; i < notes.length; i++) { @@ -834,19 +825,19 @@ export const singingStore = createPartialStore({ i === notes.length - 1 || note.position + note.duration !== notes[i + 1].position ) { - const phraseScore = { - ...score, - notes: phraseNotes, - }; const phraseFirstNote = phraseNotes[0]; const phraseLastNote = phraseNotes[phraseNotes.length - 1]; - const hash = await generateSingerAndScoreHash({ + const hash = await generatePhraseHash({ singer, - score: phraseScore, // NOTE: とりあえず拍子も含めてハッシュ生成する + tpqn, + tempos, + notes: phraseNotes, }); foundPhrases.set(hash, { singer, - score: phraseScore, + tpqn, + tempos, + notes: phraseNotes, startTicks: phraseFirstNote.position, endTicks: phraseLastNote.position + phraseLastNote.duration, state: "WAITING_TO_BE_RENDERED", @@ -866,35 +857,29 @@ export const singingStore = createPartialStore({ const fetchQuery = async ( engineId: EngineId, - score: Score, + notes: Note[], + tempos: Tempo[], + tpqn: number, frameRate: number, restDurationSeconds: number ) => { - if (!getters.IS_ENGINE_READY(engineId)) { - throw new Error("Engine not ready."); - } - const restFrameLength = Math.round(restDurationSeconds * frameRate); + const notesForRequestToEngine: NoteForRequestToEngine[] = []; - const notes: NoteForRequestToEngine[] = []; // 先頭に休符を追加 - notes.push({ + notesForRequestToEngine.push({ key: undefined, frameLength: restFrameLength, lyric: "", }); // ノートを変換 - const firstNoteOnTime = tickToSecond( - score.notes[0].position, - score.tempos, - score.tpqn - ); + const firstNoteOnTime = tickToSecond(notes[0].position, tempos, tpqn); let frame = 0; - for (const note of score.notes) { + for (const note of notes) { const noteOffTime = tickToSecond( note.position + note.duration, - score.tempos, - score.tpqn + tempos, + tpqn ); const noteOffFrame = Math.round( (noteOffTime - firstNoteOnTime) * frameRate @@ -906,7 +891,7 @@ export const singingStore = createPartialStore({ .replace("うぉ", "ウォ") .replace("は", "ハ") .replace("へ", "ヘ"); - notes.push({ + notesForRequestToEngine.push({ key: note.noteNumber, frameLength: noteFrameLength, lyric, @@ -914,24 +899,29 @@ export const singingStore = createPartialStore({ frame += noteFrameLength; } // 末尾に休符を追加 - notes.push({ + notesForRequestToEngine.push({ key: undefined, frameLength: restFrameLength, lyric: "", }); try { + if (!getters.IS_ENGINE_READY(engineId)) { + throw new Error("Engine not ready."); + } const instance = await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { engineId, }); return await instance.invoke( "singFrameAudioQuerySingFrameAudioQueryPost" )({ - score: { notes }, + score: { notes: notesForRequestToEngine }, speaker: 6000, // TODO: 設定できるようにする }); } catch (error) { - const lyrics = notes.map((value) => value.lyric).join(""); + const lyrics = notesForRequestToEngine + .map((value) => value.lyric) + .join(""); window.electron.logError( error, `Failed to fetch FrameAudioQuery. Lyrics of score are "${lyrics}".` @@ -944,12 +934,13 @@ export const singingStore = createPartialStore({ return frameAudioQuery.phonemes.map((value) => value.phoneme).join(" "); }; - const calcStartTime = (score: Score, restDurationSeconds: number) => { - let startTime = tickToSecond( - score.notes[0].position, - score.tempos, - score.tpqn - ); + const calcStartTime = ( + notes: Note[], + tempos: Tempo[], + tpqn: number, + restDurationSeconds: number + ) => { + let startTime = tickToSecond(notes[0].position, tempos, tpqn); startTime -= restDurationSeconds; return startTime; }; @@ -994,14 +985,18 @@ export const singingStore = createPartialStore({ const channelStripRef = channelStrip; // レンダリング中に変更される可能性のあるデータをコピーする - const score = copyScore(state.score); - const singer = copySinger(state.singer); - - deleteOverlappingNotes(score, state.overlappingNoteIds); - - // Score -> Phrases - - const foundPhrases = await searchPhrases(singer, score); + // 重なっているノートの削除も行う + const tpqn = state.tpqn; + const tempos = state.tempos.map((value) => ({ ...value })); + const notes = state.tracks[0].notes + .map((value) => ({ ...value })) + .filter((value) => !state.overlappingNoteIds.has(value.id)); + const singer = state.tracks[0].singer + ? { ...state.tracks[0].singer } + : undefined; + + // フレーズを更新する + const foundPhrases = await searchPhrases(singer, tpqn, tempos, notes); for (const [hash, phrase] of foundPhrases) { const phraseKey = hash; if (!state.phrases.has(phraseKey)) { @@ -1009,9 +1004,9 @@ export const singingStore = createPartialStore({ // フレーズ追加時の処理 const noteEvents = generateNoteEvents( - phrase.score.notes, - phrase.score.tempos, - phrase.score.tpqn + phrase.notes, + phrase.tempos, + phrase.tpqn ); const polySynth = new PolySynth(audioContextRef); polySynth.output.connect(channelStripRef.input); @@ -1052,6 +1047,7 @@ export const singingStore = createPartialStore({ return; } + // 各フレーズのレンダリングを行う const sortedPhrasesEntries = getSortedPhrasesEntries(state.phrases); for (const [phraseKey, phrase] of sortedPhrasesEntries) { if (!phrase.singer) { @@ -1068,8 +1064,7 @@ export const singingStore = createPartialStore({ }); } - // Singer & Score -> AudioQuery - // Score & AudioQuery -> StartTime + // 推論(クエリのフェッチ)とフレーズの開始時刻の算出を行う if (!phrase.query) { const engineId = phrase.singer.engineId; @@ -1078,7 +1073,9 @@ export const singingStore = createPartialStore({ const frameAudioQuery = await fetchQuery( phrase.singer.engineId, - phrase.score, + phrase.notes, + phrase.tempos, + phrase.tpqn, frameRate, restDurationSeconds ).catch((error) => { @@ -1094,7 +1091,12 @@ export const singingStore = createPartialStore({ `Fetched frame audio query. Phonemes are "${phonemes}".` ); - const startTime = calcStartTime(phrase.score, restDurationSeconds); + const startTime = calcStartTime( + phrase.notes, + phrase.tempos, + phrase.tpqn, + restDurationSeconds + ); commit("SET_FRAME_AUDIO_QUERY_TO_PHRASE", { phraseKey, @@ -1113,8 +1115,7 @@ export const singingStore = createPartialStore({ return; } - // AudioQuery -> Blob - // Blob & StartTime -> AudioSequence + // 音声合成を行って、音声を再生できるようにする const phraseData = phraseDataMap.get(phraseKey); if (!phraseData) { @@ -1811,7 +1812,7 @@ export const singingStore = createPartialStore({ }; const calcRenderDuration = () => { - const notes = state.score.notes; + const notes = state.tracks[0].notes; if (notes.length === 0) { return 1; } diff --git a/src/store/type.ts b/src/store/type.ts index 6303454208..0b23a4fb97 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -747,6 +747,11 @@ export type Singer = { styleId: StyleId; }; +export type Track = { + singer?: Singer; + notes: Note[]; +}; + export type PhraseState = | "WAITING_TO_BE_RENDERED" | "NOW_RENDERING" @@ -755,7 +760,9 @@ export type PhraseState = export type Phrase = { singer?: Singer; - score: Score; + tpqn: number; + tempos: Tempo[]; + notes: Note[]; startTicks: number; endTicks: number; state: PhraseState; @@ -764,8 +771,10 @@ export type Phrase = { }; export type SingingStoreState = { - singer?: Singer; - score: Score; + tpqn: number; + tempos: Tempo[]; + timeSignatures: TimeSignature[]; + tracks: Track[]; phrases: Map; // NOTE: UIの状態などは分割・統合した方がよさそうだが、ボイス側と混在させないためいったん局所化する isShowSinger: boolean; diff --git a/tests/unit/store/Vuex.spec.ts b/tests/unit/store/Vuex.spec.ts index 9bc57014da..d0f1a6ba17 100644 --- a/tests/unit/store/Vuex.spec.ts +++ b/tests/unit/store/Vuex.spec.ts @@ -152,23 +152,25 @@ describe("store/vuex.js test", () => { progress: -1, isVuexReady: false, defaultPresetKeys: {}, - score: { - tpqn: 480, - tempos: [ - { - position: 0, - bpm: 120, - }, - ], - timeSignatures: [ - { - measureNumber: 1, - beats: 4, - beatType: 4, - }, - ], - notes: [], - }, + tpqn: 480, + tempos: [ + { + position: 0, + bpm: 120, + }, + ], + timeSignatures: [ + { + measureNumber: 1, + beats: 4, + beatType: 4, + }, + ], + tracks: [ + { + notes: [], + }, + ], phrases: new Map(), isShowSinger: true, sequencerZoomX: 1,