Skip to content

Commit

Permalink
ENH: モーフィングUIとプリセットを追加
Browse files Browse the repository at this point in the history
  • Loading branch information
sabonerune committed Dec 20, 2022
1 parent 202f31a commit 9a15da6
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 26 deletions.
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>
</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>
<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(
() => store.state.experimentalSetting.enableMorphing
);
const supportMorphing = computed(
() => supportedFeatures.value?.synthesisMorphing
);
const warnMorphing = computed(() => {
if (audioItem.value.morphingInfo == undefined) return false;
return !store.getters.VALID_MOPHING_INFO(audioItem.value);
});
const selectableEngines = store.getters.SELECTABLE_MOPHING_TARGET_ENGINES;
const selectableCharacters = computed(() => {
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
? {
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">
<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の先頭を選択する
if (defaultStyle == undefined)
throw new Error("defaultStyle == undefined");
Expand Down
Loading

0 comments on commit 9a15da6

Please sign in to comment.