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] エンジンのモック作成+それを使ったコンポーネントテスト #2152

Draft
wants to merge 56 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
34dcfc2
stash
Hiroshiba Jun 30, 2024
c05f3b6
scssが読み込めない!!!
Hiroshiba Jul 2, 2024
b482e48
stash
Hiroshiba Jul 2, 2024
0168fb7
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Jul 6, 2024
409ca08
mainに近づける
Hiroshiba Jul 6, 2024
1712692
テーマ周りをリファクタリング
Hiroshiba Jul 6, 2024
b1b10e4
stash
Hiroshiba Jul 7, 2024
0631c14
なぜかspec.ts側でStoryが動かせない・・・
Hiroshiba Jul 27, 2024
79d4a66
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Aug 3, 2024
602fdd4
スナップショットできた!
Hiroshiba Aug 3, 2024
e29a8f4
8.2.7
Hiroshiba Aug 3, 2024
4d16516
typo
Hiroshiba Aug 3, 2024
aa38200
いらなかった
Hiroshiba Aug 3, 2024
36382bd
ThemeConf[]
Hiroshiba Aug 3, 2024
1240838
トーク音声を再生できるように
Hiroshiba Aug 10, 2024
4d4524e
addActionsWithEmitsを削除
Hiroshiba Aug 10, 2024
518354c
snapshot更新
Hiroshiba Aug 10, 2024
067a356
Merge branch 'main' into エンジンのmockを作る
Hiroshiba Aug 11, 2024
c83f5cb
ちょっと型調整
Hiroshiba Aug 11, 2024
2009e2d
stash
Hiroshiba Aug 12, 2024
a47e3a3
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Aug 17, 2024
d7eee81
stash
Hiroshiba Aug 18, 2024
2bc525f
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Aug 24, 2024
315fff6
openapi更新
Hiroshiba Aug 24, 2024
37c17ca
たぶんsing engineのモックができた、SingEditorのstoriesを作って試す
Hiroshiba Aug 24, 2024
208d9a5
stash
Hiroshiba Aug 24, 2024
cc641d6
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Aug 31, 2024
3134578
package-lockをもどす
Hiroshiba Aug 31, 2024
97b8e1f
package-lockを最新へ
Hiroshiba Aug 31, 2024
0dd1d5f
忘れてた
Hiroshiba Aug 31, 2024
f83ddf2
定数を移動
Hiroshiba Aug 31, 2024
5a80811
TODO: SingEditorの縦幅を変わらないようにする
Hiroshiba Aug 31, 2024
52c3e72
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Sep 7, 2024
cdec85a
エディタ表示できた!
Hiroshiba Sep 7, 2024
5b706a5
たぶんドラッグができなくてテスト不可能
Hiroshiba Sep 7, 2024
9758fd8
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Sep 15, 2024
2ddde64
storybookへの追加やめる
Hiroshiba Sep 15, 2024
5c58de9
忘れてた
Hiroshiba Sep 15, 2024
24cde26
stash。GENERATE_AUDIO_ITEMを解体するか、モックを刺す。
Hiroshiba Sep 15, 2024
0b277a4
メモ:ソフトウェアが正しく起動したあとのstateをモックとして用意しておく。speakerUuidとか。
Hiroshiba Sep 21, 2024
0cccb9a
audio.spec.ts追加
Hiroshiba Sep 28, 2024
3cbb332
これでいいのかなぁ
Hiroshiba Sep 28, 2024
47688dd
不要な変更を戻す
Hiroshiba Sep 28, 2024
aa84925
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Oct 5, 2024
8aa41e8
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Oct 12, 2024
eb87e62
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Oct 12, 2024
0d972b9
convertToUrlString関数導入
Hiroshiba Oct 12, 2024
581fc30
いろいろ調整
Hiroshiba Oct 12, 2024
79882dc
微調整
Hiroshiba Oct 12, 2024
df5efb7
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Oct 19, 2024
3144503
(スナップショットを更新)
github-actions[bot] Oct 19, 2024
0080948
ブラウザ版e2eがたぶんエンジンなしで動く!
Hiroshiba Oct 19, 2024
6f03c4f
dicPathを外部に指定
Hiroshiba Oct 26, 2024
d59a090
revert
Hiroshiba Oct 26, 2024
9ddecc6
Merge remote-tracking branch 'upstream/main' into エンジンのmockを作る
Hiroshiba Oct 26, 2024
968a2bc
とりあえず起動や再生はできるようになった
Hiroshiba Oct 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"@types/async-lock": "1.4.0",
"@types/encoding-japanese": "1.0.18",
"@types/glob": "8.0.0",
"@types/kuromoji": "0.1.3",
"@types/markdown-it": "12.2.0",
"@types/multistream": "4.1.0",
"@types/semver": "7.3.9",
Expand Down Expand Up @@ -114,6 +115,7 @@
"eslint-plugin-storybook": "0.8.0",
"eslint-plugin-vue": "9.26.0",
"happy-dom": "8.4.2",
"kuromoji": "0.1.2",
"license-checker-rseidelsohn": "4.3.0",
"markdownlint-cli": "0.37.0",
"patch-package": "8.0.0",
Expand Down
12 changes: 7 additions & 5 deletions src/backend/electron/manager/RuntimeInfoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import AsyncLock from "async-lock";
import log from "electron-log/main";
import type { AltPortInfos } from "@/store/type";
import { EngineId, EngineInfo } from "@/type/preload";
import { convertToUrlString } from "@/domain/url";

/**
* ランタイム情報書き出しに必要なEngineInfo
Expand Down Expand Up @@ -85,13 +86,14 @@ export class RuntimeInfoManager {
const altPort: string | undefined =
this.altportInfos[engineInfo.uuid];
const port = altPort ?? engineInfo.defaultPort;
// NOTE: URLを正規化する
const url = new URL(`${engineInfo.protocol}//${engineInfo.hostname}`);
url.port = port;
return {
uuid: engineInfo.uuid,
// NOTE: URLインターフェースは"pathname"が空文字でも"/"を付けるので手動で結合する。
url: `${url.origin}${engineInfo.pathname}`,
url: convertToUrlString({
protocol: engineInfo.protocol,
hostname: engineInfo.hostname,
port,
pathname: engineInfo.pathname,
}),
name: engineInfo.name,
};
}),
Expand Down
15 changes: 15 additions & 0 deletions src/domain/japanese/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,18 @@ export const convertLongVowel = (text: string): string => {
.replace(/(?<=[ん]ー*)ー/g, "ん")
.replace(/(?<=[っ]ー*)ー/g, "っ");
};

// 参考:https://github.com/VOICEVOX/voicevox_core/blob/0848630d81ae3e917c6ff2038f0b15bbd4270702/crates/voicevox_core/src/user_dict/word.rs#L83-L90
export const moraPattern = new RegExp(
"(?:" +
"[イ][ェ]|[ヴ][ャュョ]|[トド][ゥ]|[テデ][ィャュョ]|[デ][ェ]|[クグ][ヮ]|" + // rule_others
"[キシチニヒミリギジビピ][ェャュョ]|" + // rule_line_i
"[ツフヴ][ァ]|[ウスツフヴズ][ィ]|[ウツフヴ][ェォ]|" + // rule_line_u
"[ァ-ヴー]|" + // rule_one_mora
"[い][ぇ]|[ゃゅょ]|[とど][ぅ]|[てで][ぃゃゅょ]|[で][ぇ]|[くぐ][ゎ]|" + // rule_others
"[きしちにひみりぎじびぴ][ぇゃゅょ]|" + // rule_line_i
"[つふゔ][ぁ]|[うすつふゔず][ぃ]|[うつふゔ][ぇぉ]|" + // rule_line_u
"[ぁ-ゔー]" + // rule_one_mora
")",
"g",
);
17 changes: 17 additions & 0 deletions src/domain/url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export type EngineUrlParams = {
protocol: string; // `http:`など
hostname: string; // `example.com`など
port: string; // `50021`など。空文字列もありえる。
pathname: string; // `/engine`など。空文字列もありえる。
};

/**
* URLを構成するパラメータから、VOICEVOXエディタが初期から想定しているURL文字列を組み立てる。
* pathnameが空文字の場合は末尾にスラッシュを付与しない、などがビルトインのURLクラスと異なる。
*/
export function convertToUrlString(params: EngineUrlParams) {
const { protocol, hostname, port, pathname } = params;
const url = new URL(`${protocol}//${hostname}`); // NOTE: URLを正規化する
url.port = port;
return `${url.origin}${pathname}`; // NOTE: URLインターフェースは"pathname"が空文字でも"/"を付けるので手動で結合する。
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
export const convertToWavFileData = (audioBuffer: AudioBuffer) => {
export const convertToWavFileData = (
audioBuffer:
| AudioBuffer
| {
sampleRate: number;
length: number;
numberOfChannels: number;
getChannelData(channel: number): Float32Array;
},
) => {
const bytesPerSample = 4; // Float32
const formatCode = 3; // WAVE_FORMAT_IEEE_FLOAT

Expand Down
195 changes: 195 additions & 0 deletions src/mock/engineMock/audioQueryMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
/**
* AudioQueryとFrameAudioQueryのモック。
* VOICEVOX ENGINEリポジトリの処理とほぼ同じ。
*/

import { AccentPhrase, AudioQuery, FrameAudioQuery, Mora } from "@/openapi";

function generateSilenceMora(length: number): Mora {
return {
text: " ",
vowel: "sil",
vowelLength: length,
pitch: 0.0,
};
}

function toFlattenMoras(accentPhrases: AccentPhrase[]): Mora[] {
let moras: Mora[] = [];
accentPhrases.forEach((accentPhrase) => {
moras = moras.concat(accentPhrase.moras);
if (accentPhrase.pauseMora) {
moras.push(accentPhrase.pauseMora);
}
});
return moras;
}

function toFlattenPhonemes(moras: Mora[]): string[] {
const phonemes: string[] = [];
for (const mora of moras) {
if (mora.consonant) {
phonemes.push(mora.consonant);
}
phonemes.push(mora.vowel);
}
return phonemes;
}

/** 前後の無音モーラを追加する */
function applyPrePostSilence(moras: Mora[], query: AudioQuery): Mora[] {
const preSilenceMoras = [generateSilenceMora(query.prePhonemeLength)];
const postSilenceMoras = [generateSilenceMora(query.postPhonemeLength)];
return preSilenceMoras.concat(moras).concat(postSilenceMoras);
}

/** 無音時間を置き換える */
function applyPauseLength(moras: Mora[], query: AudioQuery): Mora[] {
if (query.pauseLength != undefined) {
for (const mora of moras) {
if (mora.vowel == "pau") {
mora.vowelLength = query.pauseLength;
}
}
}
return moras;
}

/** 無音時間スケールを適用する */
function applyPauseLengthScale(moras: Mora[], query: AudioQuery): Mora[] {
if (query.pauseLengthScale != undefined) {
for (const mora of moras) {
if (mora.vowel == "pau") {
mora.vowelLength *= query.pauseLengthScale;
}
}
}
return moras;
}

/** 話速スケールを適用する */
function applySpeedScale(moras: Mora[], query: AudioQuery): Mora[] {
for (const mora of moras) {
mora.vowelLength /= query.speedScale;
if (mora.consonantLength) {
mora.consonantLength /= query.speedScale;
}
}
return moras;
}

/** 音高スケールを適用する */
function applyPitchScale(moras: Mora[], query: AudioQuery): Mora[] {
for (const mora of moras) {
mora.pitch *= 2 ** query.pitchScale;
}
return moras;
}

/** 抑揚スケールを適用する */
function applyIntonationScale(moras: Mora[], query: AudioQuery): Mora[] {
const voiced = moras.filter((mora) => mora.pitch > 0);
if (voiced.length == 0) {
return moras;
}

const meanF0 =
voiced.reduce((sum, mora) => sum + mora.pitch, 0) / voiced.length;
for (const mora of voiced) {
mora.pitch = (mora.pitch - meanF0) * query.intonationScale + meanF0;
}
return moras;
}

/** 疑問文の最後に音高の高いモーラを追加する */
function applyInterrogativeUpspeak(accentPhrases: Array<AccentPhrase>) {
accentPhrases.forEach((accentPhrase) => {
const moras = accentPhrase.moras;
if (
moras.length > 0 &&
accentPhrase.isInterrogative &&
moras[moras.length - 1].pitch > 0
) {
const lastMora = moras[moras.length - 1];
const upspeakMora: Mora = {
text: "ー",
vowel: lastMora.vowel,
vowelLength: 0.15,
pitch: lastMora.pitch + 0.3,
};
accentPhrase.moras.push(upspeakMora);
}
});
}

function secondToFrame(second: number): number {
const FRAME_RATE = 24000 / 256;
return Math.round(second * FRAME_RATE);
}

/** モーラや音素ごとのフレーム数を数える */
function countFramePerUnit(moras: Mora[]): {
framePerPhoneme: number[];
framePerMora: number[];
} {
const framePerPhoneme: number[] = [];
const framePerMora: number[] = [];

for (const mora of moras) {
const vowelFrames = secondToFrame(mora.vowelLength);
const consonantFrames = mora.consonantLength
? secondToFrame(mora.consonantLength)
: 0;
const moraFrames = vowelFrames + consonantFrames;

if (mora.consonant) {
framePerPhoneme.push(consonantFrames);
}
framePerPhoneme.push(vowelFrames);
framePerMora.push(moraFrames);
}

return { framePerPhoneme, framePerMora };
}

/** AudioQueryを適当にFrameAudioQueryに変換する */
export function audioQueryToFrameAudioQueryMock(
audioQuery: AudioQuery,
{ enableInterrogativeUpspeak }: { enableInterrogativeUpspeak: boolean },
): FrameAudioQuery {
const accentPhrases = audioQuery.accentPhrases;

if (enableInterrogativeUpspeak) {
applyInterrogativeUpspeak(accentPhrases);
}

let moras = toFlattenMoras(accentPhrases);
moras = applyPrePostSilence(moras, audioQuery);
moras = applyPauseLength(moras, audioQuery);
moras = applyPauseLengthScale(moras, audioQuery);
moras = applySpeedScale(moras, audioQuery);
moras = applyPitchScale(moras, audioQuery);
moras = applyIntonationScale(moras, audioQuery);

const { framePerPhoneme, framePerMora } = countFramePerUnit(moras);

const f0 = moras.flatMap((mora, i) =>
Array<number>(framePerMora[i]).fill(
mora.pitch == 0 ? 0 : Math.exp(mora.pitch),
),
);
const volume = Array<number>(f0.length).fill(1.0);
const phonemes = toFlattenPhonemes(moras).map((phoneme, i) => ({
phoneme,
frameLength: framePerPhoneme[i],
}));

return {
f0,
volume,
phonemes,
volumeScale: audioQuery.volumeScale,
outputSamplingRate: audioQuery.outputSamplingRate,
outputStereo: audioQuery.outputStereo,
};
}
6 changes: 6 additions & 0 deletions src/mock/engineMock/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* 外部と内部で利用する定数の一覧。
*/

export const dicPath = "node_modules/kuromoji/dict";
export const assetsPath = "tests/assets";
Loading
Loading