Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP:音量編集機能の追加 #2369

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Prev Previous commit
Next Next commit
レビュー指摘箇所を修正
rokujyushi committed Nov 9, 2024
commit 48e3da621bbe8c22ae8f82d3ee10c3f91aeaf719
20 changes: 7 additions & 13 deletions src/components/Sing/ScoreSequencer.vue
Original file line number Diff line number Diff line change
@@ -198,7 +198,6 @@ import {
noteNumberToBaseY,
baseYToNoteNumber,
viewYToDecibel,
decibelToViewY,
keyInfos,
getDoremiFromNoteNumber,
ZOOM_X_MIN,
@@ -429,14 +428,10 @@ const previewVolumeEdit = ref<
| { type: "erase"; startFrame: number; frameLength: number }
| undefined
>(undefined);
// 前のカーソル位置
// const prevCursorPos = ref<
// | { prevCursorFrame: number, prevCursorFrequency: number,prevCursorVolume: number }
// | undefined
// >(undefined);
let prevCursorFrame: number = 0;
let prevCursorFrequency: number = 0;
let prevCursorVolume: number = 0;
// 前のカーソル位置
let prevCursorFrame = 0;
let prevCursorFrequency = 0;
let prevCursorVolume = 0;

// 歌詞を編集中のノート
const editingLyricNote = computed(() => {
@@ -728,7 +723,6 @@ const previewErasePitch = () => {
prevCursorFrame = cursorFrame;
};


// ボリュームを描く処理を行う
const previewDrawVolume = () => {
if (previewVolumeEdit.value == undefined) {
@@ -757,7 +751,7 @@ const previewDrawVolume = () => {
tempVolumeEdit.data = new Array(numOfFramesToUnshift)
.fill(0)
.concat(tempVolumeEdit.data);
tempVolumeEdit.startFrame = cursorFrame;
tempVolumeEdit.startFrame = cursorFrame;
}

const lastFrame = tempVolumeEdit.startFrame + tempVolumeEdit.data.length - 1;
@@ -1094,7 +1088,7 @@ const endPreview = () => {
// 1フレームの変更はピッチ編集ラインとして表示されないので、無視する
if (previewVolumeEdit.value.data.length >= 2) {
// 平滑化を行う
let data = previewVolumeEdit.value.data;
const data = [...previewVolumeEdit.value.data];
store.dispatch("COMMAND_SET_VOLUME_EDIT_DATA", {
volumeArray: data,
startFrame: previewVolumeEdit.value.startFrame,
@@ -1765,7 +1759,7 @@ const contextMenuData = computed<ContextMenuItemData[]>(() => {
pointer-events: none;
}

.sequencer-volume,.sequencer-pitch {
.sequencer-volume .sequencer-pitch {
grid-row: 2;
grid-column: 2;
}
36 changes: 14 additions & 22 deletions src/components/Sing/SequencerVolume.vue
Original file line number Diff line number Diff line change
@@ -13,8 +13,6 @@ import {
secondToTick,
} from "@/sing/domain";
import {
DECIBEL_VIEW_OFFSET,
PIXELS_PER_DECIBEL,
decibelToViewY,
VolumeData,
VolumeDataHash,
@@ -40,7 +38,6 @@ type VolumeLine = {

const props = defineProps<{
offsetX: number;
offsetY: number;
previewVolumeEdit?:
| { type: "draw"; data: number[]; startFrame: number }
| { type: "erase"; startFrame: number; frameLength: number };
@@ -111,7 +108,7 @@ const updateLineStrips = (volumeLine: VolumeLine) => {

const removedLineStrips: LineStrip[] = [];

// 無くなったピッチデータを調べて、そのピッチデータに対応するLineStripを削除する
// 無くなったボリュームデータを調べて、そのボリュームデータに対応するLineStripを削除する
for (const [key, lineStrip] of volumeLine.lineStripMap) {
if (!volumeLine.volumeDataMap.has(key)) {
stage.removeChild(lineStrip.displayObject);
@@ -120,7 +117,7 @@ const updateLineStrips = (volumeLine: VolumeLine) => {
}
}

// ピッチデータに対応するLineStripが無かったら作成する
// ボリュームデータに対応するLineStripが無かったら作成する
for (const [key, volumeData] of volumeLine.volumeDataMap) {
if (volumeLine.lineStripMap.has(key)) {
continue;
@@ -175,11 +172,8 @@ const updateLineStrips = (volumeLine: VolumeLine) => {
const baseX = tickToBaseX(ticks, tpqn);
const x = baseX * zoomX - offsetX;
const linear = volumeData.data[i];
if(Number.isNaN(linear)||linear === undefined){
continue;
}
const db = linearToDecibel(linear);
const y = decibelToViewY(db);//- ;
const y = decibelToViewY(db);
lineStrip.setPoint(i, x, y);
}
lineStrip.update();
@@ -194,7 +188,7 @@ const render = () => {
throw new Error("stage is undefined.");
}

// シンガーが未設定の場合はピッチラインをすべて非表示にして終了
// シンガーが未設定の場合はボリュームラインをすべて非表示にして終了
const singer = store.getters.SELECTED_TRACK.singer;
if (!singer) {
for (const lineStrip of originalVolumeLine.lineStripMap.values()) {
@@ -207,14 +201,17 @@ const render = () => {
return;
}

// ピッチラインのLineStripを更新する
// ボリュームラインのLineStripを更新する
updateLineStrips(originalVolumeLine);
updateLineStrips(volumeEditLine);

renderer.render(stage);
};

const toVolumeData = (framewiseData: number[], frameRate: number): VolumeData => {
const toVolumeData = (
framewiseData: number[],
frameRate: number,
): VolumeData => {
const data = framewiseData;
const ticksArray: number[] = [];
for (let i = 0; i < data.length; i++) {
@@ -259,9 +256,9 @@ const setVolumeDataToVolumeLine = async (

const generateOriginalVolumeData = () => {
//const unvoicedPhonemes = UNVOICED_PHONEMES;
const frameRate = editFrameRate.value; // f0(元のピッチ)は編集フレームレートで表示する
const frameRate = editFrameRate.value; // volumeは編集フレームレートで表示する

// 選択中のトラックで使われている歌い方のf0を結合してピッチデータを生成する
// 選択中のトラックで使われている歌い方のvolumeを結合してボリュームデータを生成する
const tempData = [];
for (const singingGuide of singingGuidesInSelectedTrack.value) {
// TODO: 補間を行うようにする
@@ -305,7 +302,7 @@ const generateVolumeEditData = () => {
const frameRate = editFrameRate.value;

const tempData = [...volumeEditData.value];
// プレビュー中のピッチ編集があれば、適用する
// プレビュー中のボリューム編集があれば、適用する
if (previewVolumeEdit.value != undefined) {
const previewVolumeEditType = previewVolumeEdit.value.type;
if (previewVolumeEditType === "draw") {
@@ -380,12 +377,7 @@ watch(
);

watch(
() => [
store.state.sequencerZoomX,
store.state.sequencerZoomY,
props.offsetX,
props.offsetY,
],
() => [store.state.sequencerZoomX, store.state.sequencerZoomY, props.offsetX],
() => {
renderInNextFrame = true;
},
@@ -415,7 +407,7 @@ onMountedOrActivated(() => {
stage = new PIXI.Container();

// webGLVersionをチェックする
// 2未満の場合、ピッチの表示ができないのでエラーとしてロギングする
// 2未満の場合、ボリュームの表示ができないのでエラーとしてロギングする
const webGLVersion = renderer.context.webGLVersion;
if (webGLVersion < 2) {
error(`webGLVersion is less than 2. webGLVersion: ${webGLVersion}`);
4 changes: 3 additions & 1 deletion src/components/Sing/ToolBar/EditTargetSwicher.vue
Original file line number Diff line number Diff line change
@@ -38,7 +38,9 @@
</template>
<template #VOLUME>
<QTooltip :delay="500" anchor="bottom middle"
>ボリューム倍率編集<br />{{ !isMac ? "Ctrl" : "Cmd" }}+クリックで消去</QTooltip
>ボリューム倍率編集<br />{{
!isMac ? "Ctrl" : "Cmd"
}}+クリックで消去</QTooltip
>
</template>
</QBtnToggle>
2 changes: 1 addition & 1 deletion src/domain/project/schema.ts
Original file line number Diff line number Diff line change
@@ -91,7 +91,7 @@ export const trackSchema = z.object({
volumeRangeAdjustment: z.number(), // 声量調整量
notes: z.array(noteSchema),
pitchEditData: z.array(z.number()), // 値の単位はHzで、データが無いところはVALUE_INDICATING_NO_DATAの値
volumeEditData: z.array(z.number()), // 値の単位はdbで、データが無いところはVALUE_INDICATING_NO_DATAの値
volumeEditData: z.array(z.number()), // 値の単位はdBで、データが無いところはVALUE_INDICATING_NO_DATAの値

solo: z.boolean(),
mute: z.boolean(),
2 changes: 0 additions & 2 deletions src/sing/domain.ts
Original file line number Diff line number Diff line change
@@ -530,7 +530,6 @@ export function applyVolumeEdit(
"The frame rate between the singing guide and the edit data does not match.",
);
}
const unvoicedPhonemes = UNVOICED_PHONEMES;
const volume = singingGuide.query.volume;
const phonemes = singingGuide.query.phonemes;

@@ -551,7 +550,6 @@ export function applyVolumeEdit(
const startFrame = Math.max(0, singingGuideStartFrame);
const endFrame = Math.min(volumeEditData.length, singingGuideEndFrame);
for (let i = startFrame; i < endFrame; i++) {
const phoneme = framePhonemes[i - singingGuideStartFrame];
if (volumeEditData[i] !== VALUE_INDICATING_NO_DATA) {
volume[i - singingGuideStartFrame] = volumeEditData[i];
}
15 changes: 10 additions & 5 deletions src/store/singing.ts
Original file line number Diff line number Diff line change
@@ -770,7 +770,7 @@ export const singingStore = createPartialStore<SingingStoreTypes>({
throw new Error("startFrame must be greater than or equal to 0.");
}
if (!isValidVolumeEditData(volumeArray)) {
throw new Error("The pitch edit data is invalid.");
throw new Error("The volume edit data is invalid.");
}
commit("SET_VOLUME_EDIT_DATA", { volumeArray, startFrame, trackId });

@@ -790,10 +790,10 @@ export const singingStore = createPartialStore<SingingStoreTypes>({
},

CLEAR_VOLUME_EDIT_DATA: {
// ピッチ編集データを失くす
// ボリューム編集データを失くす
mutation(state, { trackId }) {
const track = getOrThrow(state.tracks, trackId);
track.pitchEditData = [];
track.volumeEditData = [];
},
async action({ dispatch, commit }, { trackId }) {
commit("CLEAR_VOLUME_EDIT_DATA", { trackId });
@@ -1684,10 +1684,14 @@ export const singingStore = createPartialStore<SingingStoreTypes>({
phrase.singingGuideKey,
);

// 歌い方をコピーして、ピッチ編集を適用する
// 歌い方をコピーして、ピッチ、ボリューム編集編集を適用する
singingGuide = structuredClone(toRaw(singingGuide));
applyPitchEdit(singingGuide, track.pitchEditData, editFrameRate);
applyVolumeEdit(singingGuide, track.volumeEditData, editFrameRate);
applyVolumeEdit(
singingGuide,
track.volumeEditData,
editFrameRate,
);

const calculatedHash = await calculateSingingVoiceSourceHash({
singer: singerAndFrameRate.singer,
@@ -1898,6 +1902,7 @@ export const singingStore = createPartialStore<SingingStoreTypes>({

// ピッチ編集を適用する
applyPitchEdit(singingGuide, track.pitchEditData, editFrameRate);
applyVolumeEdit(singingGuide, track.volumeEditData, editFrameRate);

// 歌声のキャッシュがあれば取得し、なければ音声合成を行う