Skip to content

Commit

Permalink
FrameAudioQueryの変更を反映できるようにする (#1961)
Browse files Browse the repository at this point in the history
* レンダリング処理をリファクタリング、データ構造を変更

修正

* コメントを少し修正

* 歌い方と音声をキャッシュするように

* リファクタリング

* キャッシュ周りが正しく実装できていなかったので修正

* SingingStyleからSingingGuideに変更、コメントを追加

* コメントの追加・修正

* ブランド型にした

* singerAndFrameRateに変更

* hashとcalculatedHashに変更

* コメントを追加、処理を共通化

* レンダリング中に前の音声のキャッシュを再生しないように変更

* リファクタリング

* ノートシーケンスを作成して接続するタイミングを変更、コメントを修正

---------

Co-authored-by: Hiroshiba <hihokaruta@gmail.com>
  • Loading branch information
sigprogramming and Hiroshiba authored Apr 12, 2024
1 parent 020b87d commit e6ee7e5
Show file tree
Hide file tree
Showing 8 changed files with 554 additions and 321 deletions.
8 changes: 6 additions & 2 deletions src/components/Sing/ScoreSequencer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -229,9 +229,11 @@ import { isMac } from "@/type/preload";
import { useStore } from "@/store";
import { Note } from "@/store/type";
import {
getEndTicksOfPhrase,
getMeasureDuration,
getNoteDuration,
getNumOfMeasures,
getStartTicksOfPhrase,
} from "@/sing/domain";
import {
getKeyBaseHeight,
Expand Down Expand Up @@ -372,8 +374,10 @@ const playheadX = computed(() => {
// フレーズ
const phraseInfos = computed(() => {
return [...state.phrases.entries()].map(([key, phrase]) => {
const startBaseX = tickToBaseX(phrase.startTicks, tpqn.value);
const endBaseX = tickToBaseX(phrase.endTicks, tpqn.value);
const startTicks = getStartTicksOfPhrase(phrase);
const endTicks = getEndTicksOfPhrase(phrase);
const startBaseX = tickToBaseX(startTicks, tpqn.value);
const endBaseX = tickToBaseX(endTicks, tpqn.value);
const startX = startBaseX * zoomX.value;
const endX = endBaseX * zoomX.value;
return { key, x: startX, width: endX - startX };
Expand Down
51 changes: 30 additions & 21 deletions src/components/Sing/SequencerPitch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
</template>

<script setup lang="ts">
import { ref, watch, toRaw, computed } from "vue";
import { ref, watch, computed } from "vue";
import * as PIXI from "pixi.js";
import { useStore } from "@/store";
import { frequencyToNoteNumber, secondToTick } from "@/sing/domain";
Expand All @@ -14,6 +14,7 @@ import {
onMountedOrActivated,
onUnmountedOrDeactivated,
} from "@/composables/onMountOrActivate";
import { SingingGuideSourceHash } from "@/store/type";
type VoicedSection = {
readonly startFrame: number;
Expand All @@ -34,8 +35,8 @@ const props = defineProps<{ offsetX: number; offsetY: number }>();
const store = useStore();
const queries = computed(() => {
const phrases = [...store.state.phrases.values()];
return phrases.map((value) => value.query);
const singingGuides = [...store.state.singingGuides.values()];
return singingGuides.map((value) => value.query);
});
const canvasContainer = ref<HTMLElement | null>(null);
Expand All @@ -48,7 +49,7 @@ let stage: PIXI.Container | undefined;
let requestId: number | undefined;
let renderInNextFrame = false;
const pitchLinesMap = new Map<string, PitchLine[]>();
const pitchLinesMap = new Map<SingingGuideSourceHash, PitchLine[]>();
const searchVoicedSections = (phonemes: FramePhoneme[]) => {
const voicedSections: VoicedSection[] = [];
Expand Down Expand Up @@ -89,35 +90,43 @@ const render = () => {
throw new Error("stage is undefined.");
}
const phrases = toRaw(store.state.phrases);
const tpqn = store.state.tpqn;
const tempos = [store.state.tempos[0]];
const singer = store.getters.SELECTED_TRACK.singer;
const singingGuides = store.state.singingGuides;
const zoomX = store.state.sequencerZoomX;
const zoomY = store.state.sequencerZoomY;
const offsetX = props.offsetX;
const offsetY = props.offsetY;
// 無くなったフレーズを調べて、そのフレーズに対応するピッチラインを削除する
for (const [phraseKey, pitchLines] of pitchLinesMap) {
if (!phrases.has(phraseKey)) {
for (const [singingGuideKey, pitchLines] of pitchLinesMap) {
if (!singingGuides.has(singingGuideKey)) {
for (const pitchLine of pitchLines) {
stage.removeChild(pitchLine.lineStrip.displayObject);
pitchLine.lineStrip.destroy();
}
pitchLinesMap.delete(phraseKey);
pitchLinesMap.delete(singingGuideKey);
}
}
// ピッチラインの生成・更新を行う
for (const [phraseKey, phrase] of phrases) {
if (!phrase.singer || !phrase.query || phrase.startTime == undefined) {
continue;
// シンガーが未設定の場合はピッチラインをすべて非表示にして終了
if (!singer) {
for (const pitchLines of pitchLinesMap.values()) {
for (const pitchLine of pitchLines) {
pitchLine.lineStrip.renderable = false;
}
}
const tempos = [toRaw(phrase.tempos[0])];
const tpqn = phrase.tpqn;
const startTime = phrase.startTime;
const f0 = phrase.query.f0;
const phonemes = phrase.query.phonemes;
const engineId = phrase.singer.engineId;
const frameRate = store.state.engineManifests[engineId].frameRate;
let pitchLines = pitchLinesMap.get(phraseKey);
renderer.render(stage);
return;
}
// ピッチラインの生成・更新を行う
for (const [singingGuideKey, singingGuide] of singingGuides) {
const f0 = singingGuide.query.f0;
const frameRate = singingGuide.frameRate;
const startTime = singingGuide.startTime;
const phonemes = singingGuide.query.phonemes;
let pitchLines = pitchLinesMap.get(singingGuideKey);
// フレーズに対応するピッチラインが無かったら生成する
if (!pitchLines) {
Expand Down Expand Up @@ -154,7 +163,7 @@ const render = () => {
for (const pitchLine of pitchLines) {
stage.addChild(pitchLine.lineStrip.displayObject);
}
pitchLinesMap.set(phraseKey, pitchLines);
pitchLinesMap.set(singingGuideKey, pitchLines);
}
// ピッチラインを更新
Expand Down
57 changes: 53 additions & 4 deletions src/sing/domain.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
import { Note, Phrase, Score, Tempo, TimeSignature } from "@/store/type";
import { calculateHash } from "./utility";
import {
Note,
Phrase,
Score,
SingingGuideSource,
SingingVoiceSource,
Tempo,
TimeSignature,
singingGuideSourceHashSchema,
singingVoiceSourceHashSchema,
} from "@/store/type";

const BEAT_TYPES = [2, 4, 8, 16];
const MIN_BPM = 40;
Expand Down Expand Up @@ -295,9 +306,44 @@ export function isValidvolumeRangeAdjustment(volumeRangeAdjustment: number) {
);
}

export const calculateNotesHash = async (notes: Note[]) => {
return await calculateHash({ notes });
};

export const calculateSingingGuideSourceHash = async (
singingGuideSource: SingingGuideSource
) => {
const hash = await calculateHash(singingGuideSource);
return singingGuideSourceHashSchema.parse(hash);
};

export const calculateSingingVoiceSourceHash = async (
singingVoiceSource: SingingVoiceSource
) => {
const hash = await calculateHash(singingVoiceSource);
return singingVoiceSourceHashSchema.parse(hash);
};

export function getStartTicksOfPhrase(phrase: Phrase) {
if (phrase.notes.length === 0) {
throw new Error("phrase.notes.length is 0.");
}
return phrase.notes[0].position;
}

export function getEndTicksOfPhrase(phrase: Phrase) {
if (phrase.notes.length === 0) {
throw new Error("phrase.notes.length is 0.");
}
const lastNote = phrase.notes[phrase.notes.length - 1];
return lastNote.position + lastNote.duration;
}

export function toSortedPhrases(phrases: Map<string, Phrase>) {
return [...phrases.entries()].sort((a, b) => {
return a[1].startTicks - b[1].startTicks;
const startTicksOfPhraseA = getStartTicksOfPhrase(a[1]);
const startTicksOfPhraseB = getStartTicksOfPhrase(b[1]);
return startTicksOfPhraseA - startTicksOfPhraseB;
});
}

Expand All @@ -318,15 +364,18 @@ export function selectPriorPhrase(
}
// 再生位置が含まれるPhrase
for (const [phraseKey, phrase] of phrases) {
if (phrase.startTicks <= position && position <= phrase.endTicks) {
if (
getStartTicksOfPhrase(phrase) <= position &&
position <= getEndTicksOfPhrase(phrase)
) {
return [phraseKey, phrase];
}
}

const sortedPhrases = toSortedPhrases(phrases);
// 再生位置より後のPhrase
for (const [phraseKey, phrase] of sortedPhrases) {
if (phrase.startTicks > position) {
if (getStartTicksOfPhrase(phrase) > position) {
return [phraseKey, phrase];
}
}
Expand Down
14 changes: 1 addition & 13 deletions src/sing/storeHelper.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
import { Note, Singer, Tempo } from "@/store/type";
import { generateHash } from "@/sing/utility";
import { Note } from "@/store/type";

export const DEFAULT_TPQN = 480;
export const DEFAULT_BPM = 120;
export const DEFAULT_BEATS = 4;
export const DEFAULT_BEAT_TYPE = 4;

export const generatePhraseHash = async (obj: {
singer: Singer | undefined;
keyRangeAdjustment: number;
volumeRangeAdjustment: number;
tpqn: number;
tempos: Tempo[];
notes: Note[];
}) => {
return generateHash(obj);
};

/**
* 頻繁に変更される値を保持します。
* 値変更時に実行する関数を登録できます。
Expand Down
2 changes: 1 addition & 1 deletion src/sing/utility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ export function round(value: number, digits: number) {
return Math.round(value * powerOf10) / powerOf10;
}

export const generateHash = async <T>(obj: T) => {
export const calculateHash = async <T>(obj: T) => {
const textEncoder = new TextEncoder();
const data = textEncoder.encode(JSON.stringify(obj));
const digest = await crypto.subtle.digest("SHA-256", data);
Expand Down
Loading

0 comments on commit e6ee7e5

Please sign in to comment.