feat(showcase): init operator voices

This commit is contained in:
Haoyu Xu
2023-02-08 21:23:12 -05:00
parent b215b15258
commit b64e391fb8
4 changed files with 467 additions and 122 deletions

View File

@@ -2,6 +2,7 @@ folder:
operator: ./operator/ operator: ./operator/
release: ./release/ release: ./release/
background: background background: background
voice: voice
directory: _assets directory: _assets
share: _share share: _share
operators: operators:

View File

@@ -278,7 +278,7 @@ export default class Settings {
</div> </div>
<div> <div>
<label for="operator_logo">Operator Logo</label> <label for="operator_logo">Operator Logo</label>
<input type="checkbox" id="operator_logo" name="operator_logo" checked data-checked="true"/> <input type="checkbox" id="operator_logo" name="operator_logo" checked/>
<div id="operator_logo_realted"> <div id="operator_logo_realted">
<div> <div>
<label for="logo_image">Logo Image (Store Locally)</label> <label for="logo_image">Logo Image (Store Locally)</label>
@@ -311,9 +311,7 @@ export default class Settings {
<div> <div>
<label for="default_background_select">Choose a default background:</label> <label for="default_background_select">Choose a default background:</label>
<select name="default_backgrounds" id="default_background_select"> <select name="default_backgrounds" id="default_background_select">
${JSON.parse(import.meta.env.VITE_BACKGROUND_FILES).map((b) => { ${this.#updateOptions(null, JSON.parse(import.meta.env.VITE_BACKGROUND_FILES))}
return `<option value="${b}">${b}</option>`
})}
</select> </select>
</div> </div>
<div> <div>
@@ -322,6 +320,42 @@ export default class Settings {
<button type="button" disabled id="custom_background_clear" disabled>Clear</button> <button type="button" disabled id="custom_background_clear" disabled>Clear</button>
</div> </div>
</div> </div>
<div>
<label for="voice">Voice</label>
<input type="checkbox" id="voice" name="voice" checked/>
<div id="voice_realted">
<div>
<label for="voice_lang_select">Choose the language of voice:</label>
<select name="voice_lang" id="voice_lang_select">
${this.#updateOptions("voice_lang_select", window.voice.languages)}
</select>
</div>
<div>
<label for="voice_idle_duration">Idle Duration (min)</label>
<input type="number" id="voice_idle_duration_input" min="0" name="voice_idle_duration" value="${window.voice.idleDuration}" />
</div>
<div>
<label for="voice_next_duration">Next Duration (min)</label>
<input type="number" id="voice_next_duration_input" name="voice_next_duration" value="${window.voice.nextDuration}" />
</div>
</div>
</div>
<div>
<label for="subtitle">Subtitle</label>
<input type="checkbox" id="subtitle" name="subtitle" checked/>
<div id="subtitle_realted">
<div>
<label for="subtitle_lang_select">Choose the language of subtitle:</label>
<select name="subtitle_lang" id="subtitle_lang_select">
${this.#updateOptions("subtitle_lang_select", window.voice.subtitleLanguages)}
</select>
</div>
</div>
</div>
<div>
<label for="voice_actor">Voice Actor</label>
<input type="checkbox" id="voice_actor" name="voice_actor" checked/>
</div>
<div> <div>
<label for="position">Position</label> <label for="position">Position</label>
<input type="checkbox" id="position" name="position" /> <input type="checkbox" id="position" name="position" />
@@ -372,123 +406,182 @@ export default class Settings {
} }
}; };
#updateOptions(id, array) {
const e = document.getElementById(id);
const value = array.map(item => `<option value="${item}">${item}</option>`)
if (e) {
e.innerHTML = value.join("");
}
return value
}
#addEventListeners() { #addEventListeners() {
const _this = this; const _this = this;
const listeners = [
document.getElementById("fps_slider").addEventListener("change", e => { {
id: "fps_slider", event: "change", handler: e => {
_this.#sync(e.currentTarget, "fps_input"); _this.#sync(e.currentTarget, "fps_input");
_this.setFPS(e.currentTarget.value); _this.setFPS(e.currentTarget.value);
}) }
document.getElementById("fps_input").addEventListener("change", e => { }, {
id: "fps_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "fps_input");
}
}, {
id: "fps_input", event: "change", handler: e => {
_this.#sync(e.currentTarget, "fps_slider"); _this.#sync(e.currentTarget, "fps_slider");
_this.setFPS(e.currentTarget.value); _this.setFPS(e.currentTarget.value);
}) }
}, {
document.getElementById("operator_logo").addEventListener("click", e => { id: "operator_logo", event: "click", handler: e => {
_this.#showRelated(e.currentTarget, "operator_logo_realted"); _this.#showRelated(e.currentTarget, "operator_logo_realted");
_this.setLogoDisplay(!e.currentTarget.checked) _this.setLogoDisplay(!e.currentTarget.checked)
}) }
}, {
document.getElementById("logo_image").addEventListener("change", e => _this.setLogoImage(e)) id: "logo_image", event: "change", handler: e => _this.setLogoImage(e)
document.getElementById("logo_image_clear").addEventListener("click", e => this.resetLogoImage()) }, {
id: "logo_image_clear", event: "click", handler: e => _this.resetLogoImage()
document.getElementById("logo_ratio_slider").addEventListener("input", e => { }, {
id: "logo_ratio_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "logo_ratio_input"); _this.#sync(e.currentTarget, "logo_ratio_input");
_this.setLogoRatio(e.currentTarget.value); _this.setLogoRatio(e.currentTarget.value);
}) }
document.getElementById("logo_ratio_input").addEventListener("change", e => { }, {
id: "logo_ratio_input", event: "change", handler: e => {
_this.#sync(e.currentTarget, "logo_ratio_slider"); _this.#sync(e.currentTarget, "logo_ratio_slider");
_this.setLogoRatio(e.currentTarget.value); _this.setLogoRatio(e.currentTarget.value);
}) }
}, {
document.getElementById("logo_opacity_slider").addEventListener("input", e => { id: "logo_opacity_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "logo_opacity_input"); _this.#sync(e.currentTarget, "logo_opacity_input");
_this.setLogoOpacity(e.currentTarget.value); _this.setLogoOpacity(e.currentTarget.value);
}) }
document.getElementById("logo_opacity_input").addEventListener("change", e => { }, {
id: "logo_opacity_input", event: "change", handler: e => {
_this.#sync(e.currentTarget, "logo_opacity_slider"); _this.#sync(e.currentTarget, "logo_opacity_slider");
_this.setLogoOpacity(e.currentTarget.value); _this.setLogoOpacity(e.currentTarget.value);
}) }
}, {
document.getElementById("logo_padding_x_slider").addEventListener("input", e => { id: "logo_padding_x_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "logo_padding_x_input"); _this.#sync(e.currentTarget, "logo_padding_x_input");
_this.logoPadding("x", e.currentTarget.value); _this.logoPadding("x", e.currentTarget.value);
}) }
document.getElementById("logo_padding_x_input").addEventListener("change", e => { }, {
id: "logo_padding_x_input", event: "change", handler: e => {
_this.#sync(e.currentTarget, "logo_padding_x_slider"); _this.#sync(e.currentTarget, "logo_padding_x_slider");
_this.logoPadding("x", e.currentTarget.value); _this.logoPadding("x", e.currentTarget.value);
}) }
}, {
document.getElementById("logo_padding_y_slider").addEventListener("input", e => { id: "logo_padding_y_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "logo_padding_y_input"); _this.#sync(e.currentTarget, "logo_padding_y_input");
_this.logoPadding("y", e.currentTarget.value); _this.logoPadding("y", e.currentTarget.value);
}) }
document.getElementById("logo_padding_y_input").addEventListener("change", e => { }, {
id: "logo_padding_y_input", event: "change", handler: e => {
_this.#sync(e.currentTarget, "logo_padding_y_slider"); _this.#sync(e.currentTarget, "logo_padding_y_slider");
_this.logoPadding("y", e.currentTarget.value); _this.logoPadding("y", e.currentTarget.value);
}) }
}, {
document.getElementById('default_background_select').addEventListener("change", e => _this.setDefaultBackground(e.currentTarget.value)) id: "default_background_select", event: "change", handler: e => _this.setDefaultBackground(e.currentTarget.value)
}, {
document.getElementById("custom_background").addEventListener("change", e => _this.setBackground(e)) id: "custom_background", event: "change", handler: e => _this.setBackground(e)
document.getElementById("custom_background_clear").addEventListener("click", e => _this.resetBackground()) }, {
id: "custom_background_clear", event: "click", handler: e => _this.resetBackground()
document.getElementById("position").addEventListener("click", e => { }, {
id: "voice", event: "click", handler: e => {
_this.#showRelated(e.currentTarget, "voice_realted");
window.voice.useVoice = e.currentTarget.checked;
}
}, {
id: "voice_lang_select", event: "change", handler: e => {
window.voice.language = e.currentTarget.value
_this.#updateOptions("subtitle_lang_select", window.voice.subtitleLanguages)
}
}, {
id: "voice_idle_duration_input", event: "change", handler: e => {
window.voice.idleDuration = parseInt(e.currentTarget.value)
}
}, {
id: "voice_next_duration_input", event: "change", handler: e => {
window.voice.nextDuration = parseInt(e.currentTarget.value)
}
}, {
id: "subtitle", event: "click", handler: e => {
_this.#showRelated(e.currentTarget, "subtitle_realted");
window.voice.useSubtitle = e.currentTarget.checked;
}
}, {
id: "subtitle_lang_select", event: "change", handler: e => window.voice.subtitleLanguage = e.currentTarget.value
}, {
id: "voice_actor", event: "click", handler: e => {
window.voice.useVoiceActor = e.currentTarget.checked;
}
}, {
id: "position", event: "click", handler: e => {
_this.#showRelated(e.currentTarget, "position_realted"); _this.#showRelated(e.currentTarget, "position_realted");
if (!e.currentTarget.checked) _this.positionReset(); if (!e.currentTarget.checked) _this.positionReset();
}) }
}, {
document.getElementById("position_padding_left_slider").addEventListener("input", e => { id: "position_padding_left_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "position_padding_left_input"); _this.#sync(e.currentTarget, "position_padding_left_input");
_this.positionPadding("left", e.currentTarget.value); _this.positionPadding("left", e.currentTarget.value);
}) }
document.getElementById("position_padding_left_input").addEventListener("change", e => { }, {
id: "position_padding_left_input", event: "change", handler: e => {
_this.#sync(e.currentTarget, "position_padding_left_slider"); _this.#sync(e.currentTarget, "position_padding_left_slider");
_this.positionPadding("left", e.currentTarget.value); _this.positionPadding("left", e.currentTarget.value);
}) }
}, {
document.getElementById("position_padding_right_slider").addEventListener("input", e => { id: "position_padding_right_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "position_padding_right_input"); _this.#sync(e.currentTarget, "position_padding_right_input");
_this.positionPadding("right", e.currentTarget.value); _this.positionPadding("right", e.currentTarget.value);
}) }
document.getElementById("position_padding_right_input").addEventListener("change", e => { }, {
id: "position_padding_right_input", event: "change", handler: e => {
_this.#sync(e.currentTarget, "position_padding_right_slider"); _this.#sync(e.currentTarget, "position_padding_right_slider");
_this.positionPadding("right", e.currentTarget.value); _this.positionPadding("right", e.currentTarget.value);
}) }
}, {
document.getElementById("position_padding_top_slider").addEventListener("input", e => { id: "position_padding_top_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "position_padding_top_input"); _this.#sync(e.currentTarget, "position_padding_top_input");
_this.positionPadding("top", e.currentTarget.value); _this.positionPadding("top", e.currentTarget.value);
}) }
document.getElementById("position_padding_top_input").addEventListener("change", e => { }, {
id: "position_padding_top_input", event: "change", handler: e => {
_this.#sync(e.currentTarget, "position_padding_top_slider"); _this.#sync(e.currentTarget, "position_padding_top_slider");
_this.positionPadding("top", e.currentTarget.value); _this.positionPadding("top", e.currentTarget.value);
}) }
}, {
document.getElementById("position_padding_bottom_slider").addEventListener("input", e => { id: "position_padding_bottom_slider", event: "input", handler: e => {
_this.#sync(e.currentTarget, "position_padding_bottom_input"); _this.#sync(e.currentTarget, "position_padding_bottom_input");
_this.positionPadding("bottom", e.currentTarget.value); _this.positionPadding("bottom", e.currentTarget.value);
}) }
document.getElementById("position_padding_bottom_input").addEventListener("change", e => { }, {
id: "position_padding_bottom_input", event: "change", handler: e => {
_this.#sync(e.currentTarget, "position_padding_bottom_slider"); _this.#sync(e.currentTarget, "position_padding_bottom_slider");
_this.positionPadding("bottom", e.currentTarget.value); _this.positionPadding("bottom", e.currentTarget.value);
}) }
}, {
document.getElementById("settings_play").addEventListener("click", e => { id: "settings_play", event: "click", handler: e => {
this.spinePlayer.play(); this.spinePlayer.play();
e.currentTarget.disabled = true; e.currentTarget.disabled = true;
document.getElementById("settings_pause").disabled = false; document.getElementById("settings_pause").disabled = false;
}) }
document.getElementById("settings_pause").addEventListener("click", e => { }, {
id: "settings_pause", event: "click", handler: e => {
this.spinePlayer.pause(); this.spinePlayer.pause();
e.currentTarget.disabled = true; e.currentTarget.disabled = true;
document.getElementById("settings_play").disabled = false; document.getElementById("settings_play").disabled = false;
}) }
document.getElementById("settings_reset").addEventListener("click", e => { }, {
_this.reset(); id: "settings_reset", event: "click", handler: e => _this.reset()
}) }, {
document.getElementById("settings_close").addEventListener("click", e => { id: "settings_close", event: "click", handler: e => _this.close()
_this.close(); },
]
listeners.forEach(listener => {
document.getElementById(listener.id).addEventListener(listener.event, e => listener.handler(e))
}) })
} }
} }

View File

@@ -1,6 +1,254 @@
import charword_table from '!/charword_table.json' import charword_table from '!/charword_table.json'
import '@/components/voice.css' import '@/components/voice.css'
export default function Voice() { export default class Voice {
#el
#defaultVoiceLang = "JP"
#defaultRegion = charword_table.config.default_region
#defaultIdleDuration = 10 * 60 * 1000
#defaultNextDuration = 3 * 60 * 1000
#voiceLang = this.#defaultVoiceLang
#subtitleLang = this.#defaultRegion
#useSubtitle = false
#useVoice = false
#useVoiceActor = false
#currentVoiceId = null
#lastVoiceId = null
#idleListener = -1
#idleDuration = this.#defaultIdleDuration
#nextListener = -1
#nextDuration = this.#defaultNextDuration
#lastClickToNext = false
constructor(el) {
this.#el = el
}
init() {
this.#playTitleVoice()
}
success() {
this.#playEntryVoice()
this.#initNextVoiceTimer()
document.addEventListener('click', e => {
this.#lastClickToNext = true
this.#nextVoice()
})
document.addEventListener('mousemove', e => {
if (this.#idleListener === -1) {
this.#initIdleVoiceTimer()
}
})
}
/**
* @param {boolean} show
*/
set useSubtitle(show) {
this.#useSubtitle = show
}
/**
* @param {boolean} show
*/
set useVoice(show) {
this.#useVoice = show
}
/**
* @param {boolean} show
*/
set useVoiceActor(show) {
this.#useVoiceActor = show
}
/**
* @param {string} lang
*/
set subtitleLanguage(lang) {
if (this.#getSubtitleLanguages().includes(lang)) {
this.#subtitleLang = lang
} else {
this.#subtitleLang = this.#defaultRegion
}
}
get subtitleLanguage() {
return this.#subtitleLang
}
get subtitleLanguages() {
return this.#getSubtitleLanguages()
}
/**
* @param {string} lang
*/
set language(lang) {
if (this.#getVoiceLanguages().includes(lang)) {
this.#voiceLang = lang
} else {
this.#voiceLang = this.#defaultVoiceLang
}
const availableSubtitleLang = this.#getSubtitleLanguages()
if (!availableSubtitleLang.includes(this.#subtitleLang)) {
this.#subtitleLang = availableSubtitleLang[0]
}
}
get language() {
return this.#voiceLang
}
get languages() {
return this.#getVoiceLanguages()
}
/**
* @param {int} duration
*/
set idleDuration(duration) {
clearInterval(this.#idleListener)
if (duration !== 0) {
this.#idleDuration = duration * 60 * 1000
this.#initIdleVoiceTimer()
}
}
get idleDuration() {
return this.#idleDuration / 60 / 1000
}
/**
* @param {int} duration
*/
set nextDuration(duration) {
clearInterval(this.#nextListener)
if (duration !== 0) {
this.#nextDuration = duration * 1000
this.#initNextVoiceTimer()
}
}
get nextDuration() {
return this.#nextDuration / 60 / 1000
}
#playTitleVoice() {
this.#playSpecialVoice("标题")
}
#initIdleVoiceTimer() {
this.#idleListener = setInterval(() => {
this.#playSpecialVoice("闲置")
clearInterval(this.#idleListener)
this.#idleListener = -1
}, this.#idleDuration)
}
#initNextVoiceTimer() {
this.#nextListener = setInterval(() => {
if (!this.#lastClickToNext) {
this.#nextVoice()
}
}, this.#nextDuration)
}
#nextVoice() {
this.#playVoice("CN_001")
}
#playEntryVoice() {
this.#playSpecialVoice("问候")
}
#setCurrentSubtitle(id) {
console.log(id, this.#getSubtitleById(id))
}
#playVoice(id) {
this.#currentVoiceId = id
const audio = new Audio()
// audio.src = `https://cdn.jsdelivr.net/gh/Arondight/Adachi-BOT@master/src/assets/voice/${id}.mp3`
audio.play()
audio.addEventListener('ended', () => {
this.#lastVoiceId = this.#currentVoiceId
this.#currentVoiceId = null
this.#setCurrentSubtitle(null)
this.#lastClickToNext = false
})
this.#setCurrentSubtitle(id)
}
#playSpecialVoice(matcher) {
const voiceId = this.#getSpecialVoiceId(matcher)
this.#playVoice(voiceId)
}
#getSpecialVoiceId(matcher) {
const voices = this.#getVoices()
const voiceId = Object.keys(voices).find(e => voices[e].title === matcher)
return voiceId
}
#getVoices() {
return charword_table.operator.voice[this.#defaultRegion][this.#getWordKeyByVoiceLang()[this.#defaultVoiceLang]]
}
#getSubtitleById(id) {
return charword_table.operator.voice[this.#subtitleLang][this.#getWordKeyByVoiceLang()[this.#voiceLang]][id]
}
#getVoiceLanguages() {
return Object.keys(this.#getCVInfo(this.#defaultRegion))
}
/**
* @returns the cvInfo in the region's language
*/
#getCVInfo(region) {
const infoArray = Object.values(charword_table.operator.info[region])
// combine the infoArray
let output = {}
for (const info of infoArray) {
output = {
...output,
...info
}
}
return output
}
/**
* @returns the cvInfo corresponsing to the voice language
*/
#getCVInfoByVoiceLang() {
const languages = {}
for (const lang of Object.keys(charword_table.operator.info)) {
const cvInfo = this.#getCVInfo(lang)
for (const [voiceLanguage, cvArray] of Object.entries(cvInfo)) {
if (languages[voiceLanguage] === undefined) {
languages[voiceLanguage] = {}
}
languages[voiceLanguage][lang] = cvArray
}
}
return languages
}
#getWordKeyByVoiceLang() {
const output = {}
for (const [wordKey, wordKeyDict] of Object.entries(charword_table.operator.info[this.#defaultRegion])) {
for (const lang of Object.keys(wordKeyDict)) {
output[lang] = wordKey
}
}
return output
}
#getSubtitleLanguages() {
return Object.keys(this.#getCVInfoByVoiceLang()[this.#voiceLang])
}
} }

View File

@@ -2,6 +2,7 @@ import '@/index.css'
import '@/libs/wallpaper_engine' import '@/libs/wallpaper_engine'
import check_web_gl from '@/libs/check_web_gl' import check_web_gl from '@/libs/check_web_gl'
import Settings from '@/components/settings' import Settings from '@/components/settings'
import Voice from '@/components/voice'
document.querySelector('#app').innerHTML = ` document.querySelector('#app').innerHTML = `
<img src="./assets/${import.meta.env.VITE_LOGO_FILENAME}.png" class="logo invert-filter" id="logo" alt="operator logo" /> <img src="./assets/${import.meta.env.VITE_LOGO_FILENAME}.png" class="logo invert-filter" id="logo" alt="operator logo" />
@@ -12,9 +13,11 @@ document.querySelector('#app').innerHTML = `
hidden hidden
></div> ></div>
<div id="player" hidden></div> <div id="player" hidden></div>
<div id="voice" hidden></div>
</div> </div>
` `
window.voice = new Voice(document.querySelector('#voice'))
window.voice.init()
window.settings = new Settings(document.querySelector('#settings'), document.querySelector('#logo')) window.settings = new Settings(document.querySelector('#settings'), document.querySelector('#logo'))
document.title = import.meta.env.VITE_TITLE document.title = import.meta.env.VITE_TITLE
console.log("All resources are extracted from Arknights. Github: https://github.com/Halyul/aklive2d") console.log("All resources are extracted from Arknights. Github: https://github.com/Halyul/aklive2d")