Skip to content

Commit

Permalink
Merge branch 'main' into convertToUrlString
Browse files Browse the repository at this point in the history
  • Loading branch information
Hiroshiba authored Nov 16, 2024
2 parents 875abfd + 8bf37e2 commit ca21742
Show file tree
Hide file tree
Showing 21 changed files with 7,326 additions and 115 deletions.
3 changes: 3 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
src/openapi/**/* linguist-generated=true
openapi.json linguist-generated=true

**/__snapshots__/**/*.snap linguist-generated=true

*.woff2 linguist-vendored=true

* text=auto eol=lf
11 changes: 11 additions & 0 deletions src/backend/common/ConfigManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,17 @@ const migrations: [string, (store: Record<string, unknown>) => unknown][] = [
return config;
},
],
[
">=0.22",
(config) => {
// プリセットに文内無音倍率を追加
const presets = config.presets as ConfigType["presets"];
for (const preset of Object.values(presets.items)) {
if (preset == undefined) throw new Error("preset == undefined");
preset.pauseLengthScale = 1;
}
},
],
];

export type Metadata = {
Expand Down
166 changes: 94 additions & 72 deletions src/backend/electron/manager/engineInfoManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,32 +16,7 @@ import {
} from "@/type/preload";
import { AltPortInfos } from "@/store/type";
import { loadEnvEngineInfos } from "@/domain/defaultEngine/envEngineInfo";

/**
* デフォルトエンジンの情報を取得する
*/
function fetchDefaultEngineInfos(defaultEngineDir: string): EngineInfo[] {
// TODO: envから直接ではなく、envに書いたengine_manifest.jsonから情報を得るようにする
const engines = loadEnvEngineInfos();

return engines.map((engineInfo) => {
const { protocol, hostname, port, pathname } = new URL(engineInfo.host);
return {
...engineInfo,
protocol,
hostname,
defaultPort: port,
pathname: pathname === "/" ? "" : pathname,
isDefault: true,
type: "path",
executionFilePath: path.resolve(engineInfo.executionFilePath),
path:
engineInfo.path == undefined
? undefined
: path.resolve(defaultEngineDir, engineInfo.path),
} satisfies EngineInfo;
});
}
import { failure, Result, success } from "@/type/result";

/** エンジンの情報を管理するクラス */
export class EngineInfoManager {
Expand All @@ -57,43 +32,77 @@ export class EngineInfoManager {
}

/**
* 追加エンジンの一覧を取得する。
* FIXME: store.get("registeredEngineDirs")への副作用をEngineManager外に移動する
* エンジンディレクトリのエンジンマニフェストからエンジンの情報を読み込む。
*/
private fetchAdditionalEngineInfos(): EngineInfo[] {
const engines: EngineInfo[] = [];
const addEngine = (engineDir: string, type: "vvpp" | "path") => {
const manifestPath = path.join(engineDir, "engine_manifest.json");
if (!fs.existsSync(manifestPath)) {
return "manifestNotFound";
}
let manifest: MinimumEngineManifestType;
try {
manifest = minimumEngineManifestSchema.parse(
JSON.parse(fs.readFileSync(manifestPath, { encoding: "utf8" })),
);
} catch (e) {
return "manifestParseError";
}
private loadEngineInfo(
engineDir: string,
type: "vvpp" | "path",
): Result<EngineInfo, "manifestNotFound" | "manifestParseError"> {
const manifestPath = path.join(engineDir, "engine_manifest.json");
if (!fs.existsSync(manifestPath)) {
return failure("manifestNotFound", new Error("manifest not found"));
}
let manifest: MinimumEngineManifestType;
try {
manifest = minimumEngineManifestSchema.parse(
JSON.parse(fs.readFileSync(manifestPath, { encoding: "utf8" })),
);
} catch (e) {
return failure(
"manifestParseError",
e instanceof Error ? e : new Error("manifest parse error"),
);
}

const [command, ...args] = shlex.split(manifest.command);

return success({
uuid: manifest.uuid,
protocol: "http:",
hostname: "127.0.0.1",
defaultPort: manifest.port.toString(),
pathname: "",
name: manifest.name,
path: engineDir,
executionEnabled: true,
executionFilePath: path.join(engineDir, command),
executionArgs: args,
type,
isDefault: false,
} satisfies EngineInfo);
}

/**
* .envにあるエンジンの情報を取得する。
*/
private fetchEnvEngineInfos(): EngineInfo[] {
// TODO: envから直接ではなく、envに書いたengine_manifest.jsonから情報を得るようにする
const engines = loadEnvEngineInfos();

return engines.map((engineInfo) => {
const { protocol, hostname, port, pathname } = new URL(engineInfo.host);
return {
...engineInfo,
protocol,
hostname,
defaultPort: port,
pathname: pathname === "/" ? "" : pathname,
isDefault: true,
type: "path",
executionFilePath: path.resolve(engineInfo.executionFilePath),
path:
engineInfo.path == undefined
? undefined
: path.resolve(this.defaultEngineDir, engineInfo.path),
} satisfies EngineInfo;
});
}

const [command, ...args] = shlex.split(manifest.command);

engines.push({
uuid: manifest.uuid,
protocol: "http:",
hostname: "127.0.0.1",
defaultPort: manifest.port.toString(),
pathname: "",
name: manifest.name,
path: engineDir,
executionEnabled: true,
executionFilePath: path.join(engineDir, command),
executionArgs: args,
type,
isDefault: false,
} satisfies EngineInfo);
return "ok";
};
/**
* VVPPエンジンの情報を取得する。
*/
private fetchVvppEngineInfos(): EngineInfo[] {
const engineInfos: EngineInfo[] = [];
for (const dirName of fs.readdirSync(this.vvppEngineDir)) {
const engineDir = path.join(this.vvppEngineDir, dirName);
if (!fs.statSync(engineDir).isDirectory()) {
Expand All @@ -103,17 +112,27 @@ export class EngineInfoManager {
if (dirName === ".tmp") {
continue;
}
const result = addEngine(engineDir, "vvpp");
if (result !== "ok") {
log.log(`Failed to load engine: ${result}, ${engineDir}`);
const result = this.loadEngineInfo(engineDir, "vvpp");
if (!result.ok) {
log.log(`Failed to load engine: ${result.code}, ${engineDir}`);
continue;
}
engineInfos.push(result.value);
}
return engineInfos;
}

/**
* 設定で登録したエンジンの情報を取得する。
*/
private fetchRegisteredEngineInfos(): EngineInfo[] {
const configManager = getConfigManager();
// FIXME: この関数の引数でregisteredEngineDirsを受け取り、動かないエンジンをreturnして、EngineManager外でconfig.setする

const engineInfos: EngineInfo[] = [];
for (const engineDir of configManager.get("registeredEngineDirs")) {
const result = addEngine(engineDir, "path");
if (result !== "ok") {
log.log(`Failed to load engine: ${result}, ${engineDir}`);
const result = this.loadEngineInfo(engineDir, "path");
if (!result.ok) {
log.log(`Failed to load engine: ${result.code}, ${engineDir}`);
// 動かないエンジンは追加できないので削除
// FIXME: エンジン管理UIで削除可能にする
dialog.showErrorBox(
Expand All @@ -126,18 +145,21 @@ export class EngineInfoManager {
.get("registeredEngineDirs")
.filter((p) => p !== engineDir),
);
continue;
}
engineInfos.push(result.value);
}
return engines;
return engineInfos;
}

/**
* 全てのエンジンの一覧を取得する。デフォルトエンジン+追加エンジン
* 全てのエンジンの情報を取得する
*/
fetchEngineInfos(): EngineInfo[] {
const engineInfos = [
...fetchDefaultEngineInfos(this.defaultEngineDir),
...this.fetchAdditionalEngineInfos(),
...this.fetchEnvEngineInfos(),
...this.fetchVvppEngineInfos(),
...this.fetchRegisteredEngineInfos(),
];
return engineInfos;
}
Expand Down
18 changes: 18 additions & 0 deletions src/components/Talk/AudioInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,24 @@ const parameterConfigs = computed<ParameterConfig[]>(() => [
}),
key: "volumeScale",
},
{
label: "文内無音倍率",
sliderProps: {
modelValue: () => query.value?.pauseLengthScale ?? null,
disable: () => uiLocked.value,
max: SLIDER_PARAMETERS.PAUSE_LENGTH_SCALE.max,
min: SLIDER_PARAMETERS.PAUSE_LENGTH_SCALE.min,
step: SLIDER_PARAMETERS.PAUSE_LENGTH_SCALE.step,
scrollStep: SLIDER_PARAMETERS.PAUSE_LENGTH_SCALE.scrollStep,
scrollMinStep: SLIDER_PARAMETERS.PAUSE_LENGTH_SCALE.scrollMinStep,
},
onChange: (pauseLengthScale: number) =>
store.actions.COMMAND_MULTI_SET_AUDIO_PAUSE_LENGTH_SCALE({
audioKeys: selectedAudioKeys.value,
pauseLengthScale,
}),
key: "pauseLengthScale",
},
{
label: "開始無音",
sliderProps: {
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",
);
11 changes: 10 additions & 1 deletion src/domain/project/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
DEFAULT_TPQN,
DEFAULT_TRACK_NAME,
} from "@/sing/domain";
import { uuid4 } from "@/helpers/random";

const DEFAULT_SAMPLING_RATE = 24000;

Expand Down Expand Up @@ -125,6 +126,7 @@ export const migrateProjectFileObject = async (
for (const audioItemsKey in projectData.audioItems) {
if (projectData.audioItems[audioItemsKey].query != null) {
projectData.audioItems[audioItemsKey].query.volumeScale = 1;
projectData.audioItems[audioItemsKey].query.pauseLengthScale = 1;
projectData.audioItems[audioItemsKey].query.prePhonemeLength = 0.1;
projectData.audioItems[audioItemsKey].query.postPhonemeLength = 0.1;
projectData.audioItems[audioItemsKey].query.outputSamplingRate =
Expand Down Expand Up @@ -296,12 +298,19 @@ export const migrateProjectFileObject = async (
track.mute = false;
track.gain = 1;
track.pan = 0;
newTracks[TrackId(crypto.randomUUID())] = track;
newTracks[TrackId(uuid4())] = track;
}
projectData.song.tracks = newTracks;
projectData.song.trackOrder = Object.keys(newTracks);
}

if (semver.satisfies(projectAppVersion, "<0.22.0", semverSatisfiesOptions)) {
// 文内無音倍率の追加
for (const audioItemsKey in projectData.talk.audioItems) {
projectData.talk.audioItems[audioItemsKey].query.pauseLengthScale = 1;
}
}

// Validation check
// トークはvalidateTalkProjectで検証する
// ソングはSET_SCOREの中の`isValidScore`関数で検証される
Expand Down
1 change: 1 addition & 0 deletions src/domain/project/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ const audioQuerySchema = z.object({
pitchScale: z.number(),
intonationScale: z.number(),
volumeScale: z.number(),
pauseLengthScale: z.number(),
prePhonemeLength: z.number(),
postPhonemeLength: z.number(),
outputSamplingRate: z.union([z.number(), z.literal("engineDefault")]),
Expand Down
17 changes: 1 addition & 16 deletions src/sing/domain.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { calculateHash } from "./utility";
import { convertLongVowel } from "@/domain/japanese";
import { convertLongVowel, moraPattern } from "@/domain/japanese";
import {
Note,
Phrase,
Expand Down Expand Up @@ -534,21 +534,6 @@ export function applyPitchEdit(
}
}

// 参考: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",
);

/**
* 文字列をモーラと非モーラに分割する。長音は展開される。連続する非モーラはまとめる。
* 例:"カナー漢字" -> ["カ", "ナ", "ア", "漢字"]
Expand Down
Loading

0 comments on commit ca21742

Please sign in to comment.