diff --git a/config.yaml b/config.yaml index 62c0634..0538059 100644 --- a/config.yaml +++ b/config.yaml @@ -2,6 +2,7 @@ folder: operator: ./operator/ release: ./release/ background: background + voice: voice directory: _assets share: _share operators: diff --git a/src/components/settings.js b/src/components/settings.js index 277257c..f01499b 100644 --- a/src/components/settings.js +++ b/src/components/settings.js @@ -278,7 +278,7 @@ export default class Settings {
- +
@@ -311,9 +311,7 @@ export default class Settings {
@@ -322,6 +320,42 @@ export default class Settings {
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
@@ -372,123 +406,182 @@ export default class Settings { } }; + #updateOptions(id, array) { + const e = document.getElementById(id); + const value = array.map(item => ``) + if (e) { + e.innerHTML = value.join(""); + } + return value + } + #addEventListeners() { const _this = this; + const listeners = [ + { + id: "fps_slider", event: "change", handler: e => { + _this.#sync(e.currentTarget, "fps_input"); + _this.setFPS(e.currentTarget.value); + } + }, { + 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.setFPS(e.currentTarget.value); + } + }, { + id: "operator_logo", event: "click", handler: e => { + _this.#showRelated(e.currentTarget, "operator_logo_realted"); + _this.setLogoDisplay(!e.currentTarget.checked) + } + }, { + id: "logo_image", event: "change", handler: e => _this.setLogoImage(e) + }, { + id: "logo_image_clear", event: "click", handler: e => _this.resetLogoImage() + }, { + id: "logo_ratio_slider", event: "input", handler: e => { + _this.#sync(e.currentTarget, "logo_ratio_input"); + _this.setLogoRatio(e.currentTarget.value); + } + }, { + id: "logo_ratio_input", event: "change", handler: e => { + _this.#sync(e.currentTarget, "logo_ratio_slider"); + _this.setLogoRatio(e.currentTarget.value); + } + }, { + id: "logo_opacity_slider", event: "input", handler: e => { + _this.#sync(e.currentTarget, "logo_opacity_input"); + _this.setLogoOpacity(e.currentTarget.value); + } + }, { + id: "logo_opacity_input", event: "change", handler: e => { + _this.#sync(e.currentTarget, "logo_opacity_slider"); + _this.setLogoOpacity(e.currentTarget.value); + } + }, { + id: "logo_padding_x_slider", event: "input", handler: e => { + _this.#sync(e.currentTarget, "logo_padding_x_input"); + _this.logoPadding("x", e.currentTarget.value); + } + }, { + id: "logo_padding_x_input", event: "change", handler: e => { + _this.#sync(e.currentTarget, "logo_padding_x_slider"); + _this.logoPadding("x", e.currentTarget.value); + } + }, { + id: "logo_padding_y_slider", event: "input", handler: e => { + _this.#sync(e.currentTarget, "logo_padding_y_input"); + _this.logoPadding("y", e.currentTarget.value); + } + }, { + id: "logo_padding_y_input", event: "change", handler: e => { + _this.#sync(e.currentTarget, "logo_padding_y_slider"); + _this.logoPadding("y", e.currentTarget.value); + } + }, { + id: "default_background_select", event: "change", handler: e => _this.setDefaultBackground(e.currentTarget.value) + }, { + id: "custom_background", event: "change", handler: e => _this.setBackground(e) + }, { + id: "custom_background_clear", event: "click", handler: e => _this.resetBackground() + }, { + 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"); + if (!e.currentTarget.checked) _this.positionReset(); + } + }, { + id: "position_padding_left_slider", event: "input", handler: e => { + _this.#sync(e.currentTarget, "position_padding_left_input"); + _this.positionPadding("left", e.currentTarget.value); + } + }, { + id: "position_padding_left_input", event: "change", handler: e => { + _this.#sync(e.currentTarget, "position_padding_left_slider"); + _this.positionPadding("left", e.currentTarget.value); + } + }, { + id: "position_padding_right_slider", event: "input", handler: e => { + _this.#sync(e.currentTarget, "position_padding_right_input"); + _this.positionPadding("right", e.currentTarget.value); + } + }, { + id: "position_padding_right_input", event: "change", handler: e => { + _this.#sync(e.currentTarget, "position_padding_right_slider"); + _this.positionPadding("right", e.currentTarget.value); + } + }, { + id: "position_padding_top_slider", event: "input", handler: e => { + _this.#sync(e.currentTarget, "position_padding_top_input"); + _this.positionPadding("top", e.currentTarget.value); + } + }, { + id: "position_padding_top_input", event: "change", handler: e => { + _this.#sync(e.currentTarget, "position_padding_top_slider"); + _this.positionPadding("top", e.currentTarget.value); + } + }, { + id: "position_padding_bottom_slider", event: "input", handler: e => { + _this.#sync(e.currentTarget, "position_padding_bottom_input"); + _this.positionPadding("bottom", e.currentTarget.value); + } + }, { + id: "position_padding_bottom_input", event: "change", handler: e => { + _this.#sync(e.currentTarget, "position_padding_bottom_slider"); + _this.positionPadding("bottom", e.currentTarget.value); + } + }, { + id: "settings_play", event: "click", handler: e => { + this.spinePlayer.play(); + e.currentTarget.disabled = true; + document.getElementById("settings_pause").disabled = false; + } + }, { + id: "settings_pause", event: "click", handler: e => { + this.spinePlayer.pause(); + e.currentTarget.disabled = true; + document.getElementById("settings_play").disabled = false; + } + }, { + id: "settings_reset", event: "click", handler: e => _this.reset() + }, { + id: "settings_close", event: "click", handler: e => _this.close() + }, + ] - document.getElementById("fps_slider").addEventListener("change", e => { - _this.#sync(e.currentTarget, "fps_input"); - _this.setFPS(e.currentTarget.value); - }) - document.getElementById("fps_input").addEventListener("change", e => { - _this.#sync(e.currentTarget, "fps_slider"); - _this.setFPS(e.currentTarget.value); - }) - - document.getElementById("operator_logo").addEventListener("click", e => { - _this.#showRelated(e.currentTarget, "operator_logo_realted"); - _this.setLogoDisplay(!e.currentTarget.checked) - }) - - document.getElementById("logo_image").addEventListener("change", e => _this.setLogoImage(e)) - document.getElementById("logo_image_clear").addEventListener("click", e => this.resetLogoImage()) - - document.getElementById("logo_ratio_slider").addEventListener("input", e => { - _this.#sync(e.currentTarget, "logo_ratio_input"); - _this.setLogoRatio(e.currentTarget.value); - }) - document.getElementById("logo_ratio_input").addEventListener("change", e => { - _this.#sync(e.currentTarget, "logo_ratio_slider"); - _this.setLogoRatio(e.currentTarget.value); - }) - - document.getElementById("logo_opacity_slider").addEventListener("input", e => { - _this.#sync(e.currentTarget, "logo_opacity_input"); - _this.setLogoOpacity(e.currentTarget.value); - }) - document.getElementById("logo_opacity_input").addEventListener("change", e => { - _this.#sync(e.currentTarget, "logo_opacity_slider"); - _this.setLogoOpacity(e.currentTarget.value); - }) - - document.getElementById("logo_padding_x_slider").addEventListener("input", e => { - _this.#sync(e.currentTarget, "logo_padding_x_input"); - _this.logoPadding("x", e.currentTarget.value); - }) - document.getElementById("logo_padding_x_input").addEventListener("change", e => { - _this.#sync(e.currentTarget, "logo_padding_x_slider"); - _this.logoPadding("x", e.currentTarget.value); - }) - - document.getElementById("logo_padding_y_slider").addEventListener("input", e => { - _this.#sync(e.currentTarget, "logo_padding_y_input"); - _this.logoPadding("y", e.currentTarget.value); - }) - document.getElementById("logo_padding_y_input").addEventListener("change", e => { - _this.#sync(e.currentTarget, "logo_padding_y_slider"); - _this.logoPadding("y", e.currentTarget.value); - }) - - document.getElementById('default_background_select').addEventListener("change", e => _this.setDefaultBackground(e.currentTarget.value)) - - document.getElementById("custom_background").addEventListener("change", e => _this.setBackground(e)) - document.getElementById("custom_background_clear").addEventListener("click", e => _this.resetBackground()) - - document.getElementById("position").addEventListener("click", e => { - _this.#showRelated(e.currentTarget, "position_realted"); - if (!e.currentTarget.checked) _this.positionReset(); - }) - - document.getElementById("position_padding_left_slider").addEventListener("input", e => { - _this.#sync(e.currentTarget, "position_padding_left_input"); - _this.positionPadding("left", e.currentTarget.value); - }) - document.getElementById("position_padding_left_input").addEventListener("change", e => { - _this.#sync(e.currentTarget, "position_padding_left_slider"); - _this.positionPadding("left", e.currentTarget.value); - }) - - document.getElementById("position_padding_right_slider").addEventListener("input", e => { - _this.#sync(e.currentTarget, "position_padding_right_input"); - _this.positionPadding("right", e.currentTarget.value); - }) - document.getElementById("position_padding_right_input").addEventListener("change", e => { - _this.#sync(e.currentTarget, "position_padding_right_slider"); - _this.positionPadding("right", e.currentTarget.value); - }) - - document.getElementById("position_padding_top_slider").addEventListener("input", e => { - _this.#sync(e.currentTarget, "position_padding_top_input"); - _this.positionPadding("top", e.currentTarget.value); - }) - document.getElementById("position_padding_top_input").addEventListener("change", e => { - _this.#sync(e.currentTarget, "position_padding_top_slider"); - _this.positionPadding("top", e.currentTarget.value); - }) - - document.getElementById("position_padding_bottom_slider").addEventListener("input", e => { - _this.#sync(e.currentTarget, "position_padding_bottom_input"); - _this.positionPadding("bottom", e.currentTarget.value); - }) - document.getElementById("position_padding_bottom_input").addEventListener("change", e => { - _this.#sync(e.currentTarget, "position_padding_bottom_slider"); - _this.positionPadding("bottom", e.currentTarget.value); - }) - - document.getElementById("settings_play").addEventListener("click", e => { - this.spinePlayer.play(); - e.currentTarget.disabled = true; - document.getElementById("settings_pause").disabled = false; - }) - document.getElementById("settings_pause").addEventListener("click", e => { - this.spinePlayer.pause(); - e.currentTarget.disabled = true; - document.getElementById("settings_play").disabled = false; - }) - document.getElementById("settings_reset").addEventListener("click", e => { - _this.reset(); - }) - document.getElementById("settings_close").addEventListener("click", e => { - _this.close(); + listeners.forEach(listener => { + document.getElementById(listener.id).addEventListener(listener.event, e => listener.handler(e)) }) } } \ No newline at end of file diff --git a/src/components/voice.js b/src/components/voice.js index 91460fd..1a0a9a1 100644 --- a/src/components/voice.js +++ b/src/components/voice.js @@ -1,6 +1,254 @@ import charword_table from '!/charword_table.json' 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]) + } + } \ No newline at end of file diff --git a/src/index.js b/src/index.js index fff63cc..00a3e4f 100644 --- a/src/index.js +++ b/src/index.js @@ -2,6 +2,7 @@ import '@/index.css' import '@/libs/wallpaper_engine' import check_web_gl from '@/libs/check_web_gl' import Settings from '@/components/settings' +import Voice from '@/components/voice' document.querySelector('#app').innerHTML = ` @@ -11,10 +12,12 @@ document.querySelector('#app').innerHTML = ` style="background-image: url(./assets/${import.meta.env.VITE_FALLBACK_FILENAME}.png)" hidden >
- + +
` - +window.voice = new Voice(document.querySelector('#voice')) +window.voice.init() window.settings = new Settings(document.querySelector('#settings'), document.querySelector('#logo')) document.title = import.meta.env.VITE_TITLE console.log("All resources are extracted from Arknights. Github: https://github.com/Halyul/aklive2d")