diff --git a/src/backend/common/ConfigManager.ts b/src/backend/common/ConfigManager.ts index 20a00ee282..22f5db1d28 100644 --- a/src/backend/common/ConfigManager.ts +++ b/src/backend/common/ConfigManager.ts @@ -265,6 +265,17 @@ const migrations: [string, (store: Record) => 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 = { diff --git a/src/components/Talk/AudioInfo.vue b/src/components/Talk/AudioInfo.vue index 4bc03b77a6..9979426acb 100644 --- a/src/components/Talk/AudioInfo.vue +++ b/src/components/Talk/AudioInfo.vue @@ -434,6 +434,24 @@ const parameterConfigs = computed(() => [ }), 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: { diff --git a/src/domain/project/index.ts b/src/domain/project/index.ts index 3f541af269..6be25eb57b 100644 --- a/src/domain/project/index.ts +++ b/src/domain/project/index.ts @@ -125,6 +125,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 = @@ -302,6 +303,13 @@ export const migrateProjectFileObject = async ( 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`関数で検証される diff --git a/src/domain/project/schema.ts b/src/domain/project/schema.ts index c23a6812f1..eba32d5989 100644 --- a/src/domain/project/schema.ts +++ b/src/domain/project/schema.ts @@ -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")]), diff --git a/src/store/audio.ts b/src/store/audio.ts index 0444f5b95a..2d802665ee 100644 --- a/src/store/audio.ts +++ b/src/store/audio.ts @@ -11,6 +11,7 @@ import { AudioCommandStoreTypes, transformCommandStore, FetchAudioResult, + EditorAudioQuery, } from "./type"; import { buildAudioFileNameFromRawData, @@ -34,6 +35,7 @@ import { isMorphable, } from "./audioGenerate"; import { ContinuousPlayer } from "./audioContinuousPlayer"; +import { convertAudioQueryFromEngineToEditor } from "./proxy"; import { convertHiraToKana, convertLongVowel, @@ -739,6 +741,8 @@ export const audioStore = createPartialStore({ baseAudioItem.query.prePhonemeLength; newAudioItem.query.postPhonemeLength = baseAudioItem.query.postPhonemeLength; + newAudioItem.query.pauseLengthScale = + baseAudioItem.query.pauseLengthScale; newAudioItem.query.outputSamplingRate = baseAudioItem.query.outputSamplingRate; newAudioItem.query.outputStereo = baseAudioItem.query.outputStereo; @@ -891,6 +895,23 @@ export const audioStore = createPartialStore({ }, }, + SET_AUDIO_PAUSE_LENGTH_SCALE: { + mutation( + state, + { + audioKey, + pauseLengthScale, + }: { + audioKey: AudioKey; + pauseLengthScale: number; + }, + ) { + const query = state.audioItems[audioKey].query; + if (query == undefined) throw new Error("query == undefined"); + query.pauseLengthScale = pauseLengthScale; + }, + }, + SET_AUDIO_PRE_PHONEME_LENGTH: { mutation( state, @@ -949,13 +970,16 @@ export const audioStore = createPartialStore({ SET_AUDIO_QUERY: { mutation( state, - { audioKey, audioQuery }: { audioKey: AudioKey; audioQuery: AudioQuery }, + { + audioKey, + audioQuery, + }: { audioKey: AudioKey; audioQuery: EditorAudioQuery }, ) { state.audioItems[audioKey].query = audioQuery; }, action( { mutations }, - payload: { audioKey: AudioKey; audioQuery: AudioQuery }, + payload: { audioKey: AudioKey; audioQuery: EditorAudioQuery }, ) { mutations.SET_AUDIO_QUERY(payload); }, @@ -974,11 +998,13 @@ export const audioStore = createPartialStore({ .INSTANTIATE_ENGINE_CONNECTOR({ engineId, }) - .then((instance) => - instance.invoke("audioQueryAudioQueryPost")({ - text, - speaker: styleId, - }), + .then(async (instance) => + convertAudioQueryFromEngineToEditor( + await instance.invoke("audioQueryAudioQueryPost")({ + text, + speaker: styleId, + }), + ), ) .catch((error) => { window.backend.logError( @@ -1271,7 +1297,9 @@ export const audioStore = createPartialStore({ length += m.consonantLength != undefined ? m.consonantLength : 0; length += m.vowelLength; }); - length += phrase.pauseMora ? phrase.pauseMora.vowelLength : 0; + length += phrase.pauseMora + ? phrase.pauseMora.vowelLength * query.pauseLengthScale + : 0; // post phoneme lengthは最後のアクセント句の一部として扱う if (i === accentPhrases.length - 1) { length += query.postPhonemeLength; @@ -1919,7 +1947,7 @@ export const audioCommandStore = transformCommandStore( payload: { audioKey: AudioKey; text: string } & ( | { update: "Text" } | { update: "AccentPhrases"; accentPhrases: AccentPhrase[] } - | { update: "AudioQuery"; query: AudioQuery } + | { update: "AudioQuery"; query: EditorAudioQuery } ), ) { audioStore.mutations.SET_AUDIO_TEXT(draft, { @@ -2025,7 +2053,7 @@ export const audioCommandStore = transformCommandStore( } | { update: "AudioQuery"; - query: AudioQuery; + query: EditorAudioQuery; } | { update: "OnlyVoice"; @@ -2089,7 +2117,7 @@ export const audioCommandStore = transformCommandStore( } | { update: "AudioQuery"; - query: AudioQuery; + query: EditorAudioQuery; } | { update: "OnlyVoice"; @@ -2100,7 +2128,7 @@ export const audioCommandStore = transformCommandStore( try { const audioItem = state.audioItems[audioKey]; if (audioItem.query == undefined) { - const query: AudioQuery = await actions.FETCH_AUDIO_QUERY({ + const query = await actions.FETCH_AUDIO_QUERY({ text: audioItem.text, engineId: voice.engineId, styleId: voice.styleId, @@ -2711,6 +2739,29 @@ export const audioCommandStore = transformCommandStore( }, }, + COMMAND_MULTI_SET_AUDIO_PAUSE_LENGTH_SCALE: { + mutation( + draft, + payload: { + audioKeys: AudioKey[]; + pauseLengthScale: number; + }, + ) { + for (const audioKey of payload.audioKeys) { + audioStore.mutations.SET_AUDIO_PAUSE_LENGTH_SCALE(draft, { + audioKey, + pauseLengthScale: payload.pauseLengthScale, + }); + } + }, + action( + { mutations }, + payload: { audioKeys: AudioKey[]; pauseLengthScale: number }, + ) { + mutations.COMMAND_MULTI_SET_AUDIO_PAUSE_LENGTH_SCALE(payload); + }, + }, + COMMAND_MULTI_SET_AUDIO_PRE_PHONEME_LENGTH: { mutation( draft, diff --git a/src/store/audioGenerate.ts b/src/store/audioGenerate.ts index 7d9ca52b74..d50790674e 100644 --- a/src/store/audioGenerate.ts +++ b/src/store/audioGenerate.ts @@ -100,7 +100,11 @@ export async function generateLabFromAudioQuery( }); if (accentPhrase.pauseMora != undefined) { labString += timestamp.toFixed() + " "; - timestamp += (accentPhrase.pauseMora.vowelLength * 10000000) / speedScale; + timestamp += + (accentPhrase.pauseMora.vowelLength * + audioQuery.pauseLengthScale * + 10000000) / + speedScale; labString += timestamp.toFixed() + " "; labString += accentPhrase.pauseMora.vowel + "\n"; } diff --git a/src/store/preset.ts b/src/store/preset.ts index c554fec1f3..6672d5dd81 100644 --- a/src/store/preset.ts +++ b/src/store/preset.ts @@ -222,6 +222,7 @@ export const presetStore = createPartialStore({ pitchScale: 0.0, intonationScale: 1.0, volumeScale: 1.0, + pauseLengthScale: 1, prePhonemeLength: 0.1, postPhonemeLength: 0.1, }; diff --git a/src/store/proxy.ts b/src/store/proxy.ts index 6cc82334d2..82342bd632 100644 --- a/src/store/proxy.ts +++ b/src/store/proxy.ts @@ -43,6 +43,7 @@ const proxyStoreCreator = (_engineFactory: IEngineConnectorFactory) => { return proxyStore; }; +/** AudioQueryをエンジン用に変換する */ export const convertAudioQueryFromEditorToEngine = ( editorAudioQuery: EditorAudioQuery, defaultOutputSamplingRate: number, @@ -56,4 +57,14 @@ export const convertAudioQueryFromEditorToEngine = ( }; }; +/** AudioQueryをエディタ用に変換する */ +export const convertAudioQueryFromEngineToEditor = ( + engineAudioQuery: AudioQuery, +): EditorAudioQuery => { + return { + ...engineAudioQuery, + pauseLengthScale: engineAudioQuery.pauseLengthScale ?? 1, + }; +}; + export const proxyStore = proxyStoreCreator(OpenAPIEngineConnectorFactory); diff --git a/src/store/type.ts b/src/store/type.ts index 53468402c6..eebfd77fcc 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -76,8 +76,12 @@ import { /** * エディタ用のAudioQuery */ -export type EditorAudioQuery = Omit & { +export type EditorAudioQuery = Omit< + AudioQuery, + "outputSamplingRate" | "pauseLengthScale" +> & { outputSamplingRate: number | "engineDefault"; + pauseLengthScale: number; // エンジンと違って必須 }; export type AudioItem = { @@ -290,6 +294,10 @@ export type AudioStoreTypes = { mutation: { audioKey: AudioKey; volumeScale: number }; }; + SET_AUDIO_PAUSE_LENGTH_SCALE: { + mutation: { audioKey: AudioKey; pauseLengthScale: number }; + }; + SET_AUDIO_PRE_PHONEME_LENGTH: { mutation: { audioKey: AudioKey; prePhonemeLength: number }; }; @@ -329,8 +337,8 @@ export type AudioStoreTypes = { }; SET_AUDIO_QUERY: { - mutation: { audioKey: AudioKey; audioQuery: AudioQuery }; - action(payload: { audioKey: AudioKey; audioQuery: AudioQuery }): void; + mutation: { audioKey: AudioKey; audioQuery: EditorAudioQuery }; + action(payload: { audioKey: AudioKey; audioQuery: EditorAudioQuery }): void; }; FETCH_AUDIO_QUERY: { @@ -338,7 +346,7 @@ export type AudioStoreTypes = { text: string; engineId: EngineId; styleId: StyleId; - }): Promise; + }): Promise; }; SET_AUDIO_VOICE: { @@ -506,7 +514,7 @@ export type AudioCommandStoreTypes = { mutation: { audioKey: AudioKey; text: string } & ( | { update: "Text" } | { update: "AccentPhrases"; accentPhrases: AccentPhrase[] } - | { update: "AudioQuery"; query: AudioQuery } + | { update: "AudioQuery"; query: EditorAudioQuery } ); action(payload: { audioKey: AudioKey; text: string }): void; }; @@ -522,7 +530,7 @@ export type AudioCommandStoreTypes = { } | { update: "AudioQuery"; - query: AudioQuery; + query: EditorAudioQuery; } | { update: "OnlyVoice"; @@ -627,6 +635,11 @@ export type AudioCommandStoreTypes = { action(payload: { audioKeys: AudioKey[]; volumeScale: number }): void; }; + COMMAND_MULTI_SET_AUDIO_PAUSE_LENGTH_SCALE: { + mutation: { audioKeys: AudioKey[]; pauseLengthScale: number }; + action(payload: { audioKeys: AudioKey[]; pauseLengthScale: number }): void; + }; + COMMAND_MULTI_SET_AUDIO_PRE_PHONEME_LENGTH: { mutation: { audioKeys: AudioKey[]; prePhonemeLength: number }; action(payload: { audioKeys: AudioKey[]; prePhonemeLength: number }): void; diff --git a/src/store/utility.ts b/src/store/utility.ts index c27bbe290f..1bbe75be38 100644 --- a/src/store/utility.ts +++ b/src/store/utility.ts @@ -105,6 +105,16 @@ export const SLIDER_PARAMETERS = { scrollStep: () => 0.1, scrollMinStep: () => 0.01, }, + /** + * 文内無音(倍率)パラメータの定義 + */ + PAUSE_LENGTH_SCALE: { + max: () => 2, + min: () => 0, + step: () => 0.01, + scrollStep: () => 0.1, + scrollMinStep: () => 0.01, + }, /** * モーフィングレートパラメータの定義 */ diff --git a/src/type/preload.ts b/src/type/preload.ts index 3fceecc791..7e6d141999 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -414,6 +414,7 @@ export type Preset = { pitchScale: number; intonationScale: number; volumeScale: number; + pauseLengthScale: number; prePhonemeLength: number; postPhonemeLength: number; morphingInfo?: MorphingInfo; @@ -643,6 +644,7 @@ export const configSchema = z pitchScale: z.number(), intonationScale: z.number(), volumeScale: z.number(), + pauseLengthScale: z.number(), prePhonemeLength: z.number(), postPhonemeLength: z.number(), morphingInfo: z diff --git "a/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\210\343\203\274\343\202\257\347\224\273\351\235\242-browser-win32.png" "b/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\210\343\203\274\343\202\257\347\224\273\351\235\242-browser-win32.png" index 538d4153c3..0ac99a0157 100644 Binary files "a/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\210\343\203\274\343\202\257\347\224\273\351\235\242-browser-win32.png" and "b/tests/e2e/browser/\343\202\271\343\202\257\343\203\252\343\203\274\343\203\263\343\202\267\343\203\247\343\203\203\343\203\210.spec.ts-snapshots/\343\203\210\343\203\274\343\202\257\347\224\273\351\235\242-browser-win32.png" differ