Skip to content

Commit

Permalink
ソング:レンダリング順を改善 (#1909)
Browse files Browse the repository at this point in the history
* Improve: レンダリング順を改善

* Fix: 値を修正

* Fix: toEqualを使う

* 音域補正用のパラメーターを増やしつつ、開発時のみの機能に (#1902)

* 音高補正にしつつ開発時のみ機能に

* VoiceKey→GuidePitch

* とりあえず実装としては完成

* keyRangeAdjustmentに

* (note|guide)KeyShiftを消し、補正→調整にする

* keyShiftを全てKeyRangeAdjustmentへ

* Update: mainに追従

* Improve: レビューを反映

Co-Authored-By: Hiroshiba <[email protected]>

* Change: findPriorPhrases -> selectPriorPhrases

* Delete: 不要な型アサーションを削除

Co-Authored-By: sigprogramming <[email protected]>

* Add: 型アサーションを追加

* Update src/sing/domain.ts

* Update src/sing/domain.ts

---------

Co-authored-by: Hiroshiba <[email protected]>
Co-authored-by: Hiroshiba <[email protected]>
Co-authored-by: sigprogramming <[email protected]>
  • Loading branch information
4 people authored Mar 18, 2024
1 parent f5053ba commit 102d8a9
Show file tree
Hide file tree
Showing 3 changed files with 127 additions and 14 deletions.
42 changes: 41 additions & 1 deletion src/sing/domain.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -294,3 +294,43 @@ export function isValidvolumeRangeAdjustment(volumeRangeAdjustment: number) {
volumeRangeAdjustment >= -20
);
}

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

/**
* 次にレンダリングするべきPhraseを探す。
* phrasesが空の場合はエラー
* 優先順:
* - 再生位置が含まれるPhrase
* - 再生位置より後のPhrase
* - 再生位置より前のPhrase
*/
export function selectPriorPhrase(
phrases: Map<string, Phrase>,
position: number
): [string, Phrase] {
if (phrases.size === 0) {
throw new Error("Received empty phrases");
}
// 再生位置が含まれるPhrase
for (const [phraseKey, phrase] of phrases) {
if (phrase.startTicks <= position && position <= phrase.endTicks) {
return [phraseKey, phrase];
}
}

const sortedPhrases = toSortedPhrases(phrases);
// 再生位置より後のPhrase
for (const [phraseKey, phrase] of sortedPhrases) {
if (phrase.startTicks > position) {
return [phraseKey, phrase];
}
}

// 再生位置より前のPhrase
return sortedPhrases[0];
}
33 changes: 20 additions & 13 deletions src/store/singing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {
Transport,
} from "@/sing/audioRendering";
import {
selectPriorPhrase,
getMeasureDuration,
isValidNote,
isValidScore,
Expand Down Expand Up @@ -795,12 +796,6 @@ export const singingStore = createPartialStore<SingingStoreTypes>({
return foundPhrases;
};

const getSortedPhrasesEntries = (phrases: Map<string, Phrase>) => {
return [...phrases.entries()].sort((a, b) => {
return a[1].startTicks - b[1].startTicks;
});
};

const fetchQuery = async (
engineId: EngineId,
notes: Note[],
Expand Down Expand Up @@ -1022,12 +1017,28 @@ export const singingStore = createPartialStore<SingingStoreTypes>({
return;
}

const phrasesToBeRendered = new Map(
[...state.phrases.entries()].filter(([, phrase]) => {
return (
(phrase.state === "WAITING_TO_BE_RENDERED" ||
phrase.state === "COULD_NOT_RENDER") &&
phrase.singer
);
})
);
// 各フレーズのレンダリングを行う
const sortedPhrasesEntries = getSortedPhrasesEntries(state.phrases);
for (const [phraseKey, phrase] of sortedPhrasesEntries) {
while (
!(startRenderingRequested() || stopRenderingRequested()) &&
phrasesToBeRendered.size > 0
) {
const [phraseKey, phrase] = selectPriorPhrase(
phrasesToBeRendered,
playheadPosition.value
);
if (!phrase.singer) {
continue;
throw new Error("assert: phrase.singer != undefined");
}
phrasesToBeRendered.delete(phraseKey);

if (
phrase.state === "WAITING_TO_BE_RENDERED" ||
Expand Down Expand Up @@ -1157,10 +1168,6 @@ export const singingStore = createPartialStore<SingingStoreTypes>({
phraseState: "PLAYABLE",
});
}

if (startRenderingRequested() || stopRenderingRequested()) {
return;
}
}
};

Expand Down
66 changes: 66 additions & 0 deletions tests/unit/lib/selectPriorPhrase.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { it, expect } from "vitest";
import { Phrase, PhraseState } from "@/store/type";
import { DEFAULT_TPQN } from "@/sing/storeHelper";
import { selectPriorPhrase } 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 {

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / lint

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (linux-cpu-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (linux-nvidia-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-test

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (windows-cpu-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (linux-nvidia-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (linux-cpu-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (windows-directml-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (windows-nvidia-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (macos-cpu-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (windows-cpu-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (windows-directml-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (windows-nvidia-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.

Check failure on line 18 in tests/unit/lib/selectPriorPhrase.spec.ts

View workflow job for this annotation

GitHub Actions / build-and-upload (macos-cpu-prepackage)

Property 'volumeRangeAdjustment' is missing in type '{ notes: never[]; startTicks: number; endTicks: number; keyRangeAdjustment: number; state: PhraseState; tempos: { position: number; bpm: number; }[]; tpqn: number; singer: { engineId: string & BRAND<...>; styleId: number & BRAND<...>; }; }' but required in type 'Phrase'.
notes: [],
startTicks: start * DEFAULT_TPQN,
endTicks: end * DEFAULT_TPQN,
keyRangeAdjustment: 0,
state,
tempos,
tpqn: DEFAULT_TPQN,
singer: {
engineId: EngineId("00000000-0000-0000-0000-000000000000"),
styleId: StyleId(0),
},
};
};
const basePhrases = new Map<string, Phrase>([
["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] = selectPriorPhrase(phrases, position);
expect(key).toEqual(expectation);
if (key == undefined) {
// 型アサーションのためにthrowを使う
throw new Error("key is undefined");
}
phrases.delete(key);
}

// もう再生可能なPhraseがないのでthrow
expect(() => {
selectPriorPhrase(phrases, position);
}).toThrow("Received empty phrases");
});

0 comments on commit 102d8a9

Please sign in to comment.