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

ENH: モーフィングUIの実装 #1030

Merged
merged 15 commits into from
Jan 16, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
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
19 changes: 19 additions & 0 deletions src/background.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,23 @@ const store = new Store<ElectronStoreType>({
volumeScale: { type: "number" },
prePhonemeLength: { type: "number" },
postPhonemeLength: { type: "number" },
morphingInfo: {
type: "object",
properties: {
rate: { type: "number" },
targetEngineId: {
type: "string",
pattern:
"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
},
targetSpeakerId: {
type: "string",
pattern:
"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
},
targetStyleId: { type: "number" },
},
},
},
},
},
Expand Down Expand Up @@ -458,10 +475,12 @@ const store = new Store<ElectronStoreType>({
type: "boolean",
default: false,
},
enableMorphing: { type: "boolean", default: false },
},
default: {
enablePreset: false,
enableInterrogativeUpspeak: false,
enableMorphing: false,
},
},
acceptRetrieveTelemetry: {
Expand Down
237 changes: 231 additions & 6 deletions src/components/AudioInfo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,92 @@
@pan="postPhonemeLengthSlider.qSliderProps.onPan"
/>
</div>
<div
v-if="showMorphing"
class="q-px-md"
:class="{
disabled: uiLocked,
}"
>
<q-separator class="q-mb-md" />
<span class="text-body1 q-mb-xs">モーフィング</span>
<div class="row no-wrap items-center">
<character-button
class="q-my-xs"
:character-infos="selectableCharacters"
:show-engine-info="selectableEngines.length >= 2"
:emptiable="true"
:ui-locked="uiLocked"
v-model:selected-voice="morphingTargetVoice"
/>
<div class="q-pl-xs overflow-hidden">
<div class="text-body2 text-no-wrap ellipsis overflow-hidden">
{{
morphingTargetCharacterInfo
? morphingTargetCharacterInfo.metas.speakerName
: "未設定"
}}
</div>
<div
v-if="
morphingTargetCharacterInfo &&
morphingTargetCharacterInfo.metas.styles.length >= 2
"
class="text-body2 text-no-wrap ellipsis overflow-hidden"
>
({{
morphingTargetStyleInfo
? morphingTargetStyleInfo.styleName
: undefined
}})
</div>
Comment on lines +414 to +433
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

スタイル選択ボタンの表記と合わせて横並びにすると馴染み深いかもと思いました!
image

Suggested change
<div class="text-body2 text-no-wrap ellipsis overflow-hidden">
{{
morphingTargetCharacterInfo
? morphingTargetCharacterInfo.metas.speakerName
: "未設定"
}}
</div>
<div
v-if="
morphingTargetCharacterInfo &&
morphingTargetCharacterInfo.metas.styles.length >= 2
"
class="text-body2 text-no-wrap ellipsis overflow-hidden"
>
({{
morphingTargetStyleInfo
? morphingTargetStyleInfo.styleName
: undefined
}})
</div>
<div class="text-body2 text-no-wrap ellipsis overflow-hidden">
{{
morphingTargetCharacterInfo
? morphingTargetCharacterInfo.metas.speakerName +
(morphingTargetStyleInfo &&
morphingTargetCharacterInfo.metas.styles.length >= 2
? ` (${morphingTargetStyleInfo.styleName})`
: "")
: "未設定"
}}
</div>

まあAudioInfoを最小幅にすると消えちゃうのですが・・・。
(デザインは全体的に見て最終調整するので、そのままにしていただいても大丈夫です!)

</div>
</div>
<div
v-if="!supportMorphing"
class="text-warning"
style="font-size: 0.7rem"
>
非対応エンジンです
</div>
<div
v-else-if="warnMorphing"
class="text-warning"
style="font-size: 0.7rem"
>
エンジンが異なります
</div>
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved
<div :class="{ disabled: morphingTargetStyleInfo == undefined }">
<span class="text-body1 q-mb-xs"
>割合
{{
morphingRateSlider.state.currentValue.value != undefined
? morphingRateSlider.state.currentValue.value.toFixed(2)
: undefined
}}</span
>
<q-slider
dense
snap
color="primary-light"
trackSize="2px"
:min="morphingRateSlider.qSliderProps.min.value"
:max="morphingRateSlider.qSliderProps.max.value"
:step="morphingRateSlider.qSliderProps.step.value"
:disable="
morphingRateSlider.qSliderProps.disable.value ||
morphingTargetStyleInfo == undefined
"
:model-value="morphingRateSlider.qSliderProps.modelValue.value"
@update:model-value="
morphingRateSlider.qSliderProps['onUpdate:modelValue']
"
@change="morphingRateSlider.qSliderProps.onChange"
@wheel="morphingRateSlider.qSliderProps.onWheel"
@pan="morphingRateSlider.qSliderProps.onPan"
/>
</div>
</div>
</div>
</template>

Expand All @@ -358,15 +444,17 @@ import { computed, defineComponent, ref } from "vue";
import { QSelectProps } from "quasar";
import { useStore } from "@/store";

import { Preset } from "@/type/preload";
import { MorphingInfo, Preset, Voice } from "@/type/preload";
import { previewSliderHelper } from "@/helpers/previewSliderHelper";
import CharacterButton from "./CharacterButton.vue";
import PresetManageDialog from "./PresetManageDialog.vue";
import { EngineManifest } from "@/openapi";

export default defineComponent({
name: "AudioInfo",

components: {
CharacterButton,
PresetManageDialog,
},

Expand Down Expand Up @@ -443,6 +531,22 @@ export default defineComponent({
});
};

const setMorphingRate = (rate: number) => {
const info = audioItem.value.morphingInfo;
if (info == undefined) {
throw new Error("audioItem.value.morphingInfo == undefined");
}
store.dispatch("COMMAND_SET_MORPHING_INFO", {
audioKey: props.activeAudioKey,
morphingInfo: {
rate,
targetEngineId: info.targetEngineId,
targetSpeakerId: info.targetSpeakerId,
targetStyleId: info.targetStyleId,
},
});
};

const speedScaleSlider = previewSliderHelper({
modelValue: () => query.value?.speedScale ?? null,
disable: () =>
Expand Down Expand Up @@ -508,6 +612,88 @@ export default defineComponent({
scrollMinStep: () => 0.01,
});

const showMorphing = computed(
sabonerune marked this conversation as resolved.
Show resolved Hide resolved
() => store.state.experimentalSetting.enableMorphing
);

const supportMorphing = computed(
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved
() => supportedFeatures.value?.synthesisMorphing
);

const warnMorphing = computed(() => {
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved
if (audioItem.value.morphingInfo == undefined) return false;
return !store.getters.VALID_MOPHING_INFO(audioItem.value);
});

const selectableEngines = store.getters.SELECTABLE_MOPHING_TARGET_ENGINES;
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved

const selectableCharacters = computed(() => {
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved
const allCharacters = store.getters.GET_ORDERED_ALL_CHARACTER_INFOS;
return allCharacters
.map((character) => {
const targetStyles = character.metas.styles.filter((style) =>
selectableEngines.includes(style.engineId)
);
character.metas.styles = targetStyles;
return character;
})
.filter((characters) => characters.metas.styles.length >= 1);
});

const morphingTargetVoice = computed({
get() {
const morphingInfo = audioItem.value.morphingInfo;
if (morphingInfo == undefined) return undefined;
return {
engineId: morphingInfo.targetEngineId,
speakerId: morphingInfo.targetSpeakerId,
styleId: morphingInfo.targetStyleId,
};
},
set(voice: Voice | undefined) {
const morphingInfo =
voice != undefined
? {
rate: audioItem.value.morphingInfo?.rate ?? 0.5,
targetEngineId: voice.engineId,
targetSpeakerId: voice.speakerId,
targetStyleId: voice.styleId,
}
: undefined;
store.dispatch("COMMAND_SET_MORPHING_INFO", {
audioKey: props.activeAudioKey,
morphingInfo,
});
},
});

const morphingTargetCharacterInfo = computed(() =>
selectableCharacters.value.find(
(character) =>
character.metas.speakerUuid === morphingTargetVoice.value?.speakerId
)
);

const morphingTargetStyleInfo = computed(() => {
const targetVoice = morphingTargetVoice.value;
return morphingTargetCharacterInfo.value?.metas.styles.find(
(style) =>
style.engineId === targetVoice?.engineId &&
style.styleId === targetVoice.styleId
);
});

const morphingRateSlider = previewSliderHelper({
modelValue: () => audioItem.value.morphingInfo?.rate ?? null,
disable: () => uiLocked.value,
onChange: setMorphingRate,
max: () => 1,
min: () => 0,
step: () => 0.01,
scrollStep: () => 0.1,
scrollMinStep: () => 0.01,
});

// プリセット
const enablePreset = computed(
() => store.state.experimentalSetting.enablePreset
Expand All @@ -530,13 +716,30 @@ export default defineComponent({
if (audioPresetKey.value == undefined)
throw new Error("audioPresetKey is undefined"); // 次のコードが何故かコンパイルエラーになるチェック
const preset = presetItems.value[audioPresetKey.value];
const { name: _, ...presetParts } = preset;
const { name: _, morphingInfo, ...presetParts } = preset;

// 入力パラメータと比較
const keys = Object.keys(presetParts) as (keyof Omit<Preset, "name">)[];
return keys.some(
(key) => presetParts[key] !== presetPartsFromParameter.value[key]
);
const keys = Object.keys(presetParts) as (keyof Omit<
Preset,
"name" | "morphingInfo"
>)[];
if (
keys.some(
(key) => presetParts[key] !== presetPartsFromParameter.value[key]
)
)
return true;
const morphingInfoFromParameter =
presetPartsFromParameter.value.morphingInfo;
if (morphingInfo && morphingInfoFromParameter) {
const morphingInfoKeys = Object.keys(
morphingInfo
) as (keyof MorphingInfo)[];
return morphingInfoKeys.some(
(key) => morphingInfo[key] !== morphingInfoFromParameter[key]
);
}
return morphingInfo != morphingInfoFromParameter;
});

type PresetSelectModelType = {
Expand Down Expand Up @@ -695,6 +898,18 @@ export default defineComponent({
volumeScale: volumeScaleSlider.state.currentValue.value,
prePhonemeLength: prePhonemeLengthSlider.state.currentValue.value,
postPhonemeLength: postPhonemeLengthSlider.state.currentValue.value,
morphingInfo:
morphingTargetStyleInfo.value &&
morphingTargetCharacterInfo.value &&
morphingRateSlider.state.currentValue.value
sabonerune marked this conversation as resolved.
Show resolved Hide resolved
? {
rate: morphingRateSlider.state.currentValue.value,
targetEngineId: morphingTargetStyleInfo.value.engineId,
targetSpeakerId:
morphingTargetCharacterInfo.value.metas.speakerUuid,
targetStyleId: morphingTargetStyleInfo.value.styleId,
}
: undefined,
};
});

Expand Down Expand Up @@ -752,6 +967,7 @@ export default defineComponent({
setAudioVolumeScale,
setAudioPrePhonemeLength,
setAudioPostPhonemeLength,
setMorphingRate,
applyPreset,
enablePreset,
isRegisteredPreset,
Expand All @@ -777,6 +993,15 @@ export default defineComponent({
volumeScaleSlider,
prePhonemeLengthSlider,
postPhonemeLengthSlider,
selectableEngines,
showMorphing,
supportMorphing,
warnMorphing,
selectableCharacters,
morphingTargetVoice,
morphingTargetCharacterInfo,
morphingTargetStyleInfo,
morphingRateSlider,
};
},
});
Expand Down
14 changes: 8 additions & 6 deletions src/components/CharacterButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
transition-show="none"
transition-hide="none"
>
<q-list>
<q-list style="min-width: max-content">
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

右方向に十分スペースがなかったときスタイル名が改行されてしまう問題への対処

<q-item
v-if="selectedStyleInfo == undefined"
v-if="selectedStyleInfo == undefined && !emptiable"
class="row no-wrap items-center"
>
<span class="text-warning vertical-middle"
Expand Down Expand Up @@ -123,7 +123,7 @@
class="character-menu"
v-model="subMenuOpenFlags[characterIndex]"
>
<q-list>
<q-list style="min-width: max-content">
<q-item
v-for="(style, styleIndex) in characterInfo.metas.styles"
:key="styleIndex"
Expand Down Expand Up @@ -221,6 +221,7 @@ export default defineComponent({

const selectedCharacter = computed(() => {
const selectedVoice = props.selectedVoice;
if (selectedVoice == undefined) return undefined;
const character = props.characterInfos.find(
(characterInfo) =>
characterInfo.metas.speakerUuid === selectedVoice?.speakerId &&
Expand Down Expand Up @@ -261,9 +262,10 @@ export default defineComponent({
(x) => x.speakerUuid === speakerUuid
)?.defaultStyleId;

const defaultStyle = characterInfo?.metas.styles.find(
(style) => style.styleId === defaultStyleId
);
const defaultStyle =
characterInfo?.metas.styles.find(
(style) => style.styleId === defaultStyleId
) ?? characterInfo?.metas.styles[0]; // FIXME: デフォルトのスタイルIDが見つからない場合stylesの先頭を選択する
Hiroshiba marked this conversation as resolved.
Show resolved Hide resolved

if (defaultStyle == undefined)
throw new Error("defaultStyle == undefined");
Expand Down
Loading