Skip to content

Commit

Permalink
Add: 1~10番目のキャラクターを選択するホットキーを追加 (VOICEVOX#2034)
Browse files Browse the repository at this point in the history
* characterSelectShortcutの関数渡しを一段回減らす

* add: キーバインドと型設定

* fix: 型の修正

ビルド失敗の修正

* 変更commit

---------

Co-authored-by: Hiroshiba <[email protected]>
Co-authored-by: Hiroshiba Kazuyuki <[email protected]>
  • Loading branch information
3 people authored May 4, 2024
1 parent c1d5c7a commit 1c645f4
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 20 deletions.
30 changes: 10 additions & 20 deletions src/components/CharacterButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@
no-transition
:ratio="1"
:src="
getDefaultStyle(characterInfo.metas.speakerUuid).iconPath
getDefaultStyleWrapper(characterInfo.metas.speakerUuid)
.iconPath
"
/>
<QAvatar
Expand All @@ -92,7 +93,7 @@
<img
:src="
engineIcons[
getDefaultStyle(characterInfo.metas.speakerUuid)
getDefaultStyleWrapper(characterInfo.metas.speakerUuid)
.engineId
]
"
Expand Down Expand Up @@ -199,6 +200,7 @@ import { base64ImageToUri } from "@/helpers/imageHelper";
import { useStore } from "@/store";
import { CharacterInfo, SpeakerId, Voice } from "@/type/preload";
import { formatCharacterStyleName } from "@/store/utility";
import { getDefaultStyle } from "@/domain/talk";
const props = withDefaults(
defineProps<{
Expand Down Expand Up @@ -282,27 +284,15 @@ const engineIcons = computed(() =>
),
);
const getDefaultStyle = (speakerUuid: SpeakerId) => {
// FIXME: 同一キャラが複数エンジンにまたがっているとき、順番が先のエンジンが必ず選択される
const characterInfo = props.characterInfos.find(
(info) => info.metas.speakerUuid === speakerUuid,
const getDefaultStyleWrapper = (speakerUuid: SpeakerId) =>
getDefaultStyle(
speakerUuid,
props.characterInfos,
store.state.defaultStyleIds,
);
const defaultStyleId = store.state.defaultStyleIds.find(
(x) => x.speakerUuid === speakerUuid,
)?.defaultStyleId;
const defaultStyle =
characterInfo?.metas.styles.find(
(style) => style.styleId === defaultStyleId,
) ?? characterInfo?.metas.styles[0]; // デフォルトのスタイルIDが見つからない場合stylesの先頭を選択する
if (defaultStyle == undefined) throw new Error("defaultStyle == undefined");
return defaultStyle;
};
const onSelectSpeaker = (speakerUuid: SpeakerId) => {
const style = getDefaultStyle(speakerUuid);
const style = getDefaultStyleWrapper(speakerUuid);
emit("update:selectedVoice", {
engineId: style.engineId,
speakerId: speakerUuid,
Expand Down
29 changes: 29 additions & 0 deletions src/components/Talk/AudioCell.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ import {
useShiftKey,
useCommandOrControlKey,
} from "@/composables/useModifierKey";
import { getDefaultStyle } from "@/domain/talk";
const props = defineProps<{
audioKey: AudioKey;
Expand Down Expand Up @@ -155,6 +156,10 @@ defineExpose({
removeCell: () => {
removeCell();
},
/** index番目のキャラクターを選ぶ */
selectCharacterAt: (index: number) => {
selectCharacterAt(index);
},
});
const store = useStore();
Expand Down Expand Up @@ -490,6 +495,30 @@ const removeCell = async () => {
}
};
// N番目のキャラクターを選ぶ
const selectCharacterAt = (index: number) => {
if (userOrderedCharacterInfos.value.length < index + 1) {
return;
}
const speakerUuid = userOrderedCharacterInfos.value[index].metas.speakerUuid;
const style = getDefaultStyle(
speakerUuid,
userOrderedCharacterInfos.value,
store.state.defaultStyleIds,
);
const voice = {
engineId: style.engineId,
speakerId: speakerUuid,
styleId: style.styleId,
};
store.dispatch("COMMAND_MULTI_CHANGE_VOICE", {
audioKeys: isMultiSelectEnabled.value
? store.getters.SELECTED_AUDIO_KEYS
: [props.audioKey],
voice,
});
};
// 削除ボタンの有効/無効判定
const enableDeleteButton = computed(() => {
return (
Expand Down
19 changes: 19 additions & 0 deletions src/components/Talk/TalkEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ import {
PresetKey,
SplitterPositionType,
Voice,
HotkeyActionNameType,
actionPostfixSelectNthCharacter,
} from "@/type/preload";
import { useHotkeyManager } from "@/plugins/hotkeyPlugin";
import onetimeWatch from "@/helpers/onetimeWatch";
Expand Down Expand Up @@ -258,12 +260,29 @@ registerHotkeyWithCleanup({
}
},
});
for (let i = 0; i < 10; i++) {
registerHotkeyWithCleanup({
editor: "talk",
enableInTextbox: true,
name: `${i + 1}${actionPostfixSelectNthCharacter}` as HotkeyActionNameType,
callback: () => {
if (!uiLocked.value) {
onCharacterSelectHotkey(i);
}
},
});
}
const removeAudioItem = async () => {
if (activeAudioKey.value == undefined) throw new Error();
audioCellRefs[activeAudioKey.value].removeCell();
};
const onCharacterSelectHotkey = async (selectedCharacterIndex: number) => {
if (activeAudioKey.value == undefined) throw new Error();
audioCellRefs[activeAudioKey.value].selectCharacterAt(selectedCharacterIndex);
};
// view
const DEFAULT_PORTRAIT_PANE_WIDTH = 22; // %
const MIN_PORTRAIT_PANE_WIDTH = 0;
Expand Down
25 changes: 25 additions & 0 deletions src/domain/talk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { CharacterInfo, DefaultStyleId, SpeakerId } from "@/type/preload";

/** 話者に対応するデフォルトスタイルを取得する */
export const getDefaultStyle = (
speakerUuid: SpeakerId,
characterInfos: CharacterInfo[],
defaultStyleIds: DefaultStyleId[],
) => {
// FIXME: 同一キャラが複数エンジンにまたがっているとき、順番が先のエンジンが必ず選択される
const characterInfo = characterInfos.find(
(info) => info.metas.speakerUuid === speakerUuid,
);
const defaultStyleId = defaultStyleIds.find(
(x) => x.speakerUuid === speakerUuid,
)?.defaultStyleId;

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

if (defaultStyle == undefined) throw new Error("defaultStyle == undefined");

return defaultStyle;
};
21 changes: 21 additions & 0 deletions src/type/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export type VoiceId = z.infer<typeof voiceIdSchema>;
export const VoiceId = (voice: Voice): VoiceId =>
voiceIdSchema.parse(`${voice.engineId}:${voice.speakerId}:${voice.styleId}`);

// 共通のアクション名
export const actionPostfixSelectNthCharacter = "番目のキャラクターを選択";

// ホットキーを追加したときは設定のマイグレーションが必要
export const defaultHotkeySettings: HotkeySettingType[] = [
{
Expand Down Expand Up @@ -176,6 +179,14 @@ export const defaultHotkeySettings: HotkeySettingType[] = [
action: "全セルを選択",
combination: HotkeyCombination(!isMac ? "Ctrl Shift A" : "Meta Shift A"),
},
...Array.from({ length: 10 }, (_, index) => {
const roleKey = index == 9 ? 0 : index + 1;
return {
action:
`${index + 1}${actionPostfixSelectNthCharacter}` as HotkeyActionNameType,
combination: HotkeyCombination((!isMac ? "Ctrl " : "Meta ") + roleKey),
};
}),
];

export const defaultToolbarButtonSetting: ToolbarSettingType = [
Expand Down Expand Up @@ -463,6 +474,16 @@ export const hotkeyActionNameSchema = z.enum([
"すべて選択",
"選択解除",
"全セルを選択",
`1${actionPostfixSelectNthCharacter}`,
`2${actionPostfixSelectNthCharacter}`,
`3${actionPostfixSelectNthCharacter}`,
`4${actionPostfixSelectNthCharacter}`,
`5${actionPostfixSelectNthCharacter}`,
`6${actionPostfixSelectNthCharacter}`,
`7${actionPostfixSelectNthCharacter}`,
`8${actionPostfixSelectNthCharacter}`,
`9${actionPostfixSelectNthCharacter}`,
`10${actionPostfixSelectNthCharacter}`,
]);

export type HotkeyActionNameType = z.infer<typeof hotkeyActionNameSchema>;
Expand Down

0 comments on commit 1c645f4

Please sign in to comment.