From 0d77ea7f84e25bb1342f4d0a151dd432f0d90753 Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Tue, 27 Feb 2024 00:00:08 -0800 Subject: [PATCH] feat: add dynamic voice setting --- .../CameraControls/utils/cameraStore.ts | 13 +- client/src/lib/Sequences/sequences.ts | 295 +++++++++--------- client/src/lib/stores/settingsStore.ts | 32 +- client/src/lib/utils/getVoicePath.ts | 20 +- 4 files changed, 187 insertions(+), 173 deletions(-) diff --git a/client/src/lib/Dashboard/Visualization/CameraControls/utils/cameraStore.ts b/client/src/lib/Dashboard/Visualization/CameraControls/utils/cameraStore.ts index 6e9f94b..37a02c6 100644 --- a/client/src/lib/Dashboard/Visualization/CameraControls/utils/cameraStore.ts +++ b/client/src/lib/Dashboard/Visualization/CameraControls/utils/cameraStore.ts @@ -1,8 +1,7 @@ -import type CameraControls from 'camera-controls' -import { writable } from 'svelte/store' -import Hornet from '../../models/Hornet.svelte' -import type { Mesh, Object3DEventMap } from 'three' -import type { Group } from 'three/examples/jsm/libs/tween.module.js' +import type CameraControls from "camera-controls"; +import { writable } from "svelte/store"; +import type { Mesh, Object3DEventMap } from "three"; +import type { Group } from "three/examples/jsm/libs/tween.module.js"; -export const cameraControls = writable() -export const mesh = writable() +export const cameraControls = writable(); +export const mesh = writable(); diff --git a/client/src/lib/Sequences/sequences.ts b/client/src/lib/Sequences/sequences.ts index ee6a911..ac45db8 100644 --- a/client/src/lib/Sequences/sequences.ts +++ b/client/src/lib/Sequences/sequences.ts @@ -14,47 +14,44 @@ Sequences should be either event-driven or periodic. In the case of periodic sequences, invoke them in the periodicSequence function */ -import { Notifications } from '../Notifications/notifications' -import { sequenceStore } from '../stores/sequenceStore' -import { settingsStore } from '../stores/settingsStore' -import { get } from 'svelte/store' -import getVoicePath from '../utils/getVoicePath' -import { tick } from 'svelte' +import { Notifications } from "../Notifications/notifications"; +import { sequenceStore } from "../stores/sequenceStore"; +import { settingsStore } from "../stores/settingsStore"; +import { get } from "svelte/store"; +import getVoicePath from "../utils/getVoicePath"; +import { tick } from "svelte"; // await a "tick" (a svelte update frame) at the start of every sequence so that // state is synced and no weird side effects occur export const initializationSequence = async () => { - await tick() - Notifications.info('Jankboard initialized!', { + await tick(); + Notifications.info("Jankboard initialized!", { withAudio: true, - src: getVoicePath('jankboard-initialized', 'en'), - }) + src: getVoicePath("jankboard-initialized"), + }); setTimeout(() => { - if (get(settingsStore).goWoke) return - Notifications.success('LittenOS is online', { + if (get(settingsStore).goWoke) return; + Notifications.success("LittenOS is online", { withAudio: true, - src: getVoicePath('littenos-is-online', 'en'), - }) + src: getVoicePath("littenos-is-online"), + }); setTimeout(() => { - Notifications.warn('Breaching Monte Vista codebase', { + Notifications.warn("Breaching Monte Vista codebase", { withAudio: true, - src: getVoicePath('breaching-monte-vista', 'en'), - }) + src: getVoicePath("breaching-monte-vista"), + }); setTimeout(() => { - Notifications.playAudio( - getVoicePath('hello-virtual-assistant', 'en'), - () => { - sequenceStore.update('initializationComplete', true) - periodicSequence() - } - ) - }, 3000) - }, 3000) - }, 3000) -} + Notifications.playAudio(getVoicePath("hello-virtual-assistant"), () => { + sequenceStore.update("initializationComplete", true); + periodicSequence(); + }); + }, 3000); + }, 3000); + }, 3000); +}; -let counter = 1 +let counter = 1; /** * Special sequence that plays invokes itself periodically, started automatically * at the end of the initializationSequence @@ -64,7 +61,7 @@ let counter = 1 * @return void */ const periodicSequence = async () => { - await tick() + await tick(); /** * Returns either true or false based on the provided probability @@ -74,11 +71,11 @@ const periodicSequence = async () => { */ const chance = (probability: number) => { if (probability < 0 || probability > 1) { - throw new Error('Probability must be between 0 and 1') + throw new Error("Probability must be between 0 and 1"); } - return Math.random() < probability * get(settingsStore).randomWeight - } + return Math.random() < probability * get(settingsStore).randomWeight; + }; /** * Calls a callback function at regular intervals. @@ -87,140 +84,140 @@ const periodicSequence = async () => { * @param callback - the function to call */ const every = (seconds: number, callback: () => void) => { - if (counter % seconds === 0) callback() - } + if (counter % seconds === 0) callback(); + }; // add your periodic sequences here every(15, () => { - if (chance(0.2)) breaching1323Sequence() - else if (chance(0.2)) breaching254Sequence() - }) + if (chance(0.2)) breaching1323Sequence(); + else if (chance(0.2)) breaching254Sequence(); + }); every(25, () => { - if (chance(0.05)) bullyingRohanSequence() - else if (chance(0.1)) bypassCoprocessorRestrictionsSequence() - }) + if (chance(0.05)) bullyingRohanSequence(); + else if (chance(0.1)) bypassCoprocessorRestrictionsSequence(); + }); // Dont touch - counter++ - setTimeout(periodicSequence, 1000) -} + counter++; + setTimeout(periodicSequence, 1000); +}; export const criticalFailureIminentSequence = async () => { - await tick() - Notifications.error('Critical robot failure imminent', { + await tick(); + Notifications.error("Critical robot failure imminent", { withAudio: true, - src: getVoicePath('critical-robot-failure', 'en'), - }) -} + src: getVoicePath("critical-robot-failure"), + }); +}; export const collisionDetectedSequence = async () => { - await tick() - Notifications.error('Collision detected', { + await tick(); + Notifications.error("Collision detected", { withAudio: true, - src: getVoicePath('collision-detected', 'en'), - }) -} + src: getVoicePath("collision-detected"), + }); +}; export const collisionImminentSequence = async () => { - await tick() - Notifications.error('Collision imminent', { + await tick(); + Notifications.error("Collision imminent", { withAudio: true, - src: getVoicePath('collision-imminent', 'en'), - }) -} + src: getVoicePath("collision-imminent"), + }); +}; export const cruiseControlEngagedSequence = async () => { - if (get(settingsStore).disableAnnoyances) return - await tick() - Notifications.success('Cruise control engaged', { + if (get(settingsStore).disableAnnoyances) return; + await tick(); + Notifications.success("Cruise control engaged", { withAudio: true, - src: getVoicePath('cruise-control-engaged', 'en'), - }) -} + src: getVoicePath("cruise-control-engaged"), + }); +}; export const retardSequence = async () => { - if (get(settingsStore).goWoke) return - await tick() - Notifications.warn('Retard', { + if (get(settingsStore).goWoke) return; + await tick(); + Notifications.warn("Retard", { withAudio: true, - src: getVoicePath('retard', 'en'), - }) -} + src: getVoicePath("retard"), + }); +}; const breaching254Sequence = async () => { - if (get(settingsStore).disableAnnoyances) return - await tick() - Notifications.warn('Breaching 254 mainframe', { + if (get(settingsStore).disableAnnoyances) return; + await tick(); + Notifications.warn("Breaching 254 mainframe", { withAudio: true, - src: getVoicePath('breaching-254-mainframe', 'en'), - }) -} + src: getVoicePath("breaching-254-mainframe"), + }); +}; const breaching1323Sequence = async () => { - if (get(settingsStore).disableAnnoyances) return - await tick() - Notifications.warn('Breaching 1323 mainframe', { + if (get(settingsStore).disableAnnoyances) return; + await tick(); + Notifications.warn("Breaching 1323 mainframe", { withAudio: true, - src: getVoicePath('breaching-1323-mainframe', 'en'), - }) -} + src: getVoicePath("breaching-1323-mainframe"), + }); +}; const bullyingRohanSequence = async () => { - if (get(settingsStore).disableAnnoyances) return - await tick() - Notifications.info('Bullying Rohan', { + if (get(settingsStore).disableAnnoyances) return; + await tick(); + Notifications.info("Bullying Rohan", { withAudio: true, - src: getVoicePath('bullying-rohan', 'en'), - }) -} + src: getVoicePath("bullying-rohan"), + }); +}; export const userErrorDetectedSequence = async () => { - await tick() - Notifications.error('User error detected', { + await tick(); + Notifications.error("User error detected", { withAudio: true, - src: getVoicePath('user-error-detected', 'en'), - }) -} + src: getVoicePath("user-error-detected"), + }); +}; // hacky way to prevent duplicate infotainment bootups -let infotainmentStarted = false +let infotainmentStarted = false; export const infotainmentBootupSequence = async () => { if ( get(sequenceStore).infotainmentStartedFirstTime || get(settingsStore).disableAnnoyances || infotainmentStarted ) - return + return; - infotainmentStarted = true - await tick() + infotainmentStarted = true; + await tick(); const sequence = () => { - Notifications.info('Infotainment system buffering', { + Notifications.info("Infotainment system buffering", { withAudio: true, - src: getVoicePath('infotainment-system-buffering', 'en'), - }) + src: getVoicePath("infotainment-system-buffering"), + }); setTimeout(() => { - Notifications.success('Infotainment system online', { + Notifications.success("Infotainment system online", { withAudio: true, - src: getVoicePath('infotainment-system-online', 'en'), + src: getVoicePath("infotainment-system-online"), onComplete: () => { - sequenceStore.update('infotainmentStartedFirstTime', true) + sequenceStore.update("infotainmentStartedFirstTime", true); }, - }) - }, 3000) - } + }); + }, 3000); + }; if (!get(sequenceStore).initializationComplete) { - const unsubscribe = sequenceStore.subscribe(data => { + const unsubscribe = sequenceStore.subscribe((data) => { if (data.initializationComplete) { - sequence() - unsubscribe() + sequence(); + unsubscribe(); } - }) + }); } else { - sequence() + sequence(); } -} +}; /** * Waits for the infotainment system to boot up before executing the given sequence. @@ -231,77 +228,77 @@ export const infotainmentBootupSequence = async () => { */ const waitForInfotainmentBootup = (sequence: () => void) => { if (!get(sequenceStore).infotainmentStartedFirstTime) { - const unsubscribe = sequenceStore.subscribe(data => { + const unsubscribe = sequenceStore.subscribe((data) => { if (data.infotainmentStartedFirstTime) { - sequence() - unsubscribe() + sequence(); + unsubscribe(); } - }) + }); } else { - sequence() + sequence(); } -} +}; export const musicPlayerBootupSequence = async () => { if ( get(sequenceStore).musicStartedFirstTime || get(settingsStore).disableAnnoyances ) - return + return; - await tick() + await tick(); - sequenceStore.update('musicStartedFirstTime', true) + sequenceStore.update("musicStartedFirstTime", true); waitForInfotainmentBootup(() => { - Notifications.info('Downloading copyrighted music...', { + Notifications.info("Downloading copyrighted music...", { withAudio: true, - src: getVoicePath('downloading-copyrighted-music', 'en'), - }) - }) -} + src: getVoicePath("downloading-copyrighted-music"), + }); + }); +}; export const gbaEmulatorBootupSequence = async () => { if ( get(sequenceStore).gbaEmulatorStartedFirstTime || get(settingsStore).disableAnnoyances ) - return + return; - await tick() - sequenceStore.update('gbaEmulatorStartedFirstTime', true) + await tick(); + sequenceStore.update("gbaEmulatorStartedFirstTime", true); waitForInfotainmentBootup(() => { - Notifications.info('Loading pirated Nintendo ROMs', { + Notifications.info("Loading pirated Nintendo ROMs", { withAudio: true, - src: getVoicePath('loading-pirated-nintendo', 'en'), - }) - }) -} + src: getVoicePath("loading-pirated-nintendo"), + }); + }); +}; export const doomBootupSequence = async () => { if ( get(sequenceStore).doomStartedFirstTime || get(settingsStore).disableAnnoyances ) - return + return; - await tick() - sequenceStore.update('doomStartedFirstTime', true) + await tick(); + sequenceStore.update("doomStartedFirstTime", true); waitForInfotainmentBootup(() => { - Notifications.success('Doom Engaged', { + Notifications.success("Doom Engaged", { withAudio: true, - src: getVoicePath('doom-engaged', 'en'), - }) - }) -} + src: getVoicePath("doom-engaged"), + }); + }); +}; const bypassCoprocessorRestrictionsSequence = async () => { - if (get(settingsStore).disableAnnoyances) return - await tick() - Notifications.warn('Bypassing coprocessor restrictions', { + if (get(settingsStore).disableAnnoyances) return; + await tick(); + Notifications.warn("Bypassing coprocessor restrictions", { withAudio: true, - src: getVoicePath('bypassing-coprocessor-restrictions', 'en'), - }) -} + src: getVoicePath("bypassing-coprocessor-restrictions"), + }); +}; diff --git a/client/src/lib/stores/settingsStore.ts b/client/src/lib/stores/settingsStore.ts index 1fd43fd..6b816a5 100644 --- a/client/src/lib/stores/settingsStore.ts +++ b/client/src/lib/stores/settingsStore.ts @@ -1,12 +1,15 @@ /* stores global app wide settings */ -import { writable } from 'svelte/store' +import { writable } from "svelte/store"; + +type SupportedLanguage = "en" | "rus"; export interface SettingsStoreData { - disableAnnoyances: boolean - goWoke: boolean - fastStartup: boolean - randomWeight: number + disableAnnoyances: boolean; + goWoke: boolean; + fastStartup: boolean; + randomWeight: number; + voiceLang: SupportedLanguage; } export const defaults: SettingsStoreData = { @@ -14,25 +17,26 @@ export const defaults: SettingsStoreData = { goWoke: false, // go woke (for showing parents or other officials where DEI has taken over), disables "offensive" sequences fastStartup: false, // skip the loading splash screen (for development purposes. Setting this from within the app has no effect.) randomWeight: 1, // the weight of random events (multiplied by the original probability) -} + voiceLang: "en", +}; const createSequenceStore = () => { - const { subscribe, set, update } = writable(defaults) + const { subscribe, set, update } = writable(defaults); return { subscribe, update: ( data: keyof SettingsStoreData, newValue: SettingsStoreData[typeof data] ) => { - update(store => { + update((store) => { // @ts-expect-error - store[data] = newValue - return store - }) + store[data] = newValue; + return store; + }); }, reset: () => set(defaults), set: (data: SettingsStoreData) => set(data), - } -} + }; +}; -export const settingsStore = createSequenceStore() +export const settingsStore = createSequenceStore(); diff --git a/client/src/lib/utils/getVoicePath.ts b/client/src/lib/utils/getVoicePath.ts index 6a1178f..750a2a1 100644 --- a/client/src/lib/utils/getVoicePath.ts +++ b/client/src/lib/utils/getVoicePath.ts @@ -1,3 +1,6 @@ +import { get } from "svelte/store"; +import { settingsStore } from "../stores/settingsStore"; + /** * Retrieves the voice audio path for the given audio file. * @@ -5,7 +8,18 @@ * @param lang - the language of the audio * @return the path of the audio file */ -type SupportedLanguage = 'en' | 'rus' -export default function getVoicePath(audio: string, lang: SupportedLanguage) { - return `/static/voices/${lang}/${audio}.wav` +type SupportedLanguage = "en" | "rus"; + +let currentLang = "en"; + +settingsStore.subscribe((data) => { + currentLang = data.voiceLang; +}); +export default function getVoicePath(audio: string, lang?: SupportedLanguage) { + console.log(get(settingsStore).voiceLang); + if (!lang) { + return `/static/voices/${get(settingsStore).voiceLang}/${audio}.wav`; + } + + return `/static/voices/${lang}/${audio}.wav`; }