diff --git a/frontend/apps/editor2d/tsconfig.json b/frontend/apps/editor2d/tsconfig.json
index 59339b5499..8650ed60c3 100644
--- a/frontend/apps/editor2d/tsconfig.json
+++ b/frontend/apps/editor2d/tsconfig.json
@@ -25,6 +25,9 @@
{
"path": "../../libs/tauri-utils"
},
+ {
+ "path": "../../libs/soundboard"
+ },
{
"path": "./tsconfig.app.json"
},
diff --git a/frontend/apps/editor3d/app/public/resources/sound/oot_dialogue_done.wav b/frontend/apps/editor3d/app/public/resources/sound/oot_dialogue_done.wav
new file mode 100644
index 0000000000..6a34234919
Binary files /dev/null and b/frontend/apps/editor3d/app/public/resources/sound/oot_dialogue_done.wav differ
diff --git a/frontend/apps/editor3d/app/public/resources/sound/oot_dialogue_next.wav b/frontend/apps/editor3d/app/public/resources/sound/oot_dialogue_next.wav
new file mode 100644
index 0000000000..33fdd81998
Binary files /dev/null and b/frontend/apps/editor3d/app/public/resources/sound/oot_dialogue_next.wav differ
diff --git a/frontend/apps/editor3d/app/public/resources/sound/smrpg_correct.wav b/frontend/apps/editor3d/app/public/resources/sound/smrpg_correct.wav
new file mode 100644
index 0000000000..ccb8c51776
Binary files /dev/null and b/frontend/apps/editor3d/app/public/resources/sound/smrpg_correct.wav differ
diff --git a/frontend/apps/editor3d/app/public/resources/sound/smrpg_drybones_crumble.wav b/frontend/apps/editor3d/app/public/resources/sound/smrpg_drybones_crumble.wav
new file mode 100644
index 0000000000..7513d1f1ac
Binary files /dev/null and b/frontend/apps/editor3d/app/public/resources/sound/smrpg_drybones_crumble.wav differ
diff --git a/frontend/apps/editor3d/app/public/resources/sound/smrpg_flower.wav b/frontend/apps/editor3d/app/public/resources/sound/smrpg_flower.wav
new file mode 100644
index 0000000000..f7b739399d
Binary files /dev/null and b/frontend/apps/editor3d/app/public/resources/sound/smrpg_flower.wav differ
diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx
index 4bc27e6825..9a4b1f3c28 100644
--- a/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx
+++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/PageEnigma.tsx
@@ -22,6 +22,7 @@ import {
precisionSelectorValues,
showPrecisionSelector,
} from "./signals/precisionSelectorMenu";
+import { SoundRegistry, SoundEffect } from "@storyteller/soundboard";
export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => {
useSignals();
@@ -87,6 +88,16 @@ export const PageEnigma = ({ sceneToken }: { sceneToken?: string }) => {
});
});
+ useEffect(() => {
+ const r = SoundRegistry.getInstance();
+ r.setSoundOnce("success", new SoundEffect("resources/sound/smrpg_flower.wav"));
+ r.setSoundOnce("bingo", new SoundEffect("resources/sound/smrpg_correct.wav"));
+ r.setSoundOnce("next", new SoundEffect("resources/sound/oot_dialogue_next.wav", 2.0));
+ r.setSoundOnce("done", new SoundEffect("resources/sound/oot_dialogue_done.wav", 2.0));
+ r.setSoundOnce("fail", new SoundEffect("resources/sound/smrpg_drybones_crumble.wav"));
+ (window as any).sounds = r;
+ });
+
if (validGpu === "unknown") {
return ;
}
diff --git a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/PromptBox/PromptBox.tsx b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/PromptBox/PromptBox.tsx
index 16c5fb8186..4a8b061e67 100644
--- a/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/PromptBox/PromptBox.tsx
+++ b/frontend/apps/editor3d/app/src/pages/PageEnigma/comps/PromptBox/PromptBox.tsx
@@ -45,6 +45,7 @@ import { QueueNames } from "~/pages/PageEnigma/Queue/QueueNames";
import Queue from "~/pages/PageEnigma/Queue/Queue";
import { toEngineActions } from "~/pages/PageEnigma/Queue/toEngineActions";
import { PromptsApi } from "@storyteller/api";
+import { SoundRegistry } from "@storyteller/soundboard";
import {
cameras,
@@ -439,6 +440,9 @@ export const PromptBox = () => {
const isDesktop = IsDesktopApp();
console.log("Is this a desktop app?", isDesktop);
+ const r = SoundRegistry.getInstance();
+ r.playSound("test");
+
if (isDesktop) {
await handleTauriEnqueue();
} else {
diff --git a/frontend/apps/editor3d/tsconfig.json b/frontend/apps/editor3d/tsconfig.json
index 34f623a1b8..f4535364ef 100644
--- a/frontend/apps/editor3d/tsconfig.json
+++ b/frontend/apps/editor3d/tsconfig.json
@@ -68,6 +68,9 @@
},
{
"path": "../../libs/api"
+ },
+ {
+ "path": "../../libs/soundboard"
}
]
}
diff --git a/frontend/libs/soundboard/.babelrc b/frontend/libs/soundboard/.babelrc
new file mode 100644
index 0000000000..1ea870ead4
--- /dev/null
+++ b/frontend/libs/soundboard/.babelrc
@@ -0,0 +1,12 @@
+{
+ "presets": [
+ [
+ "@nx/react/babel",
+ {
+ "runtime": "automatic",
+ "useBuiltIns": "usage"
+ }
+ ]
+ ],
+ "plugins": []
+}
diff --git a/frontend/libs/soundboard/.eslintrc.json b/frontend/libs/soundboard/.eslintrc.json
new file mode 100644
index 0000000000..def19dc990
--- /dev/null
+++ b/frontend/libs/soundboard/.eslintrc.json
@@ -0,0 +1,36 @@
+{
+ "extends": [
+ "plugin:@nx/react",
+ "../../.eslintrc.json"
+ ],
+ "ignorePatterns": [
+ "!**/*",
+ "**/vite.config.*.timestamp*",
+ "**/vitest.config.*.timestamp*"
+ ],
+ "overrides": [
+ {
+ "files": [
+ "*.ts",
+ "*.tsx",
+ "*.js",
+ "*.jsx"
+ ],
+ "rules": {}
+ },
+ {
+ "files": [
+ "*.ts",
+ "*.tsx"
+ ],
+ "rules": {}
+ },
+ {
+ "files": [
+ "*.js",
+ "*.jsx"
+ ],
+ "rules": {}
+ }
+ ]
+}
diff --git a/frontend/libs/soundboard/README.md b/frontend/libs/soundboard/README.md
new file mode 100644
index 0000000000..6298bb5f80
--- /dev/null
+++ b/frontend/libs/soundboard/README.md
@@ -0,0 +1,7 @@
+# @frontend/soundboard
+
+This library was generated with [Nx](https://nx.dev).
+
+## Running unit tests
+
+Run `nx test @frontend/soundboard` to execute the unit tests via [Vitest](https://vitest.dev/).
diff --git a/frontend/libs/soundboard/package.json b/frontend/libs/soundboard/package.json
new file mode 100644
index 0000000000..333f216be6
--- /dev/null
+++ b/frontend/libs/soundboard/package.json
@@ -0,0 +1,23 @@
+{
+ "name": "@storyteller/soundboard",
+ "version": "0.0.1",
+ "type": "module",
+ "main": "./dist/index.js",
+ "module": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "exports": {
+ "./package.json": "./package.json",
+ ".": {
+ "development": "./src/index.ts",
+ "types": "./dist/index.d.ts",
+ "import": "./dist/index.js",
+ "default": "./dist/index.js"
+ }
+ },
+ "dependencies": {
+ "howler": "^2.2.4"
+ },
+ "devDependencies": {
+ "@types/howler": "^2.2.12"
+ }
+}
diff --git a/frontend/libs/soundboard/src/index.ts b/frontend/libs/soundboard/src/index.ts
new file mode 100644
index 0000000000..2003da994a
--- /dev/null
+++ b/frontend/libs/soundboard/src/index.ts
@@ -0,0 +1,3 @@
+
+export * from './lib/SoundEffect';
+export * from './lib/SoundRegistry';
diff --git a/frontend/libs/soundboard/src/lib/SoundEffect.ts b/frontend/libs/soundboard/src/lib/SoundEffect.ts
new file mode 100644
index 0000000000..8e2cdda810
--- /dev/null
+++ b/frontend/libs/soundboard/src/lib/SoundEffect.ts
@@ -0,0 +1,23 @@
+import {Howl} from 'howler';
+
+export class SoundEffect {
+
+ readonly soundUrl: string;
+ readonly howlerSound: Howl;
+ readonly defaultVolume: number;
+
+ constructor(soundUrl: string, defaultVolume?: number) {
+ this.soundUrl = soundUrl;
+ this.defaultVolume = defaultVolume || 1.0;
+ this.howlerSound = new Howl({
+ src: [soundUrl],
+ autoplay: false,
+ loop: false,
+ volume: defaultVolume,
+ });
+ }
+
+ public play() {
+ this.howlerSound.play();
+ }
+}
diff --git a/frontend/libs/soundboard/src/lib/SoundRegistry.ts b/frontend/libs/soundboard/src/lib/SoundRegistry.ts
new file mode 100644
index 0000000000..5108343c95
--- /dev/null
+++ b/frontend/libs/soundboard/src/lib/SoundRegistry.ts
@@ -0,0 +1,32 @@
+import { SoundEffect } from "./SoundEffect";
+
+export class SoundRegistry {
+ static #instance: SoundRegistry | undefined;
+
+ #sounds: Map;
+
+ private constructor() {
+ this.#sounds = new Map();
+ }
+
+ public static getInstance() {
+ if (SoundRegistry.#instance === undefined) {
+ SoundRegistry.#instance = new SoundRegistry();
+ }
+ return SoundRegistry.#instance;
+ }
+
+ public setSound(key: string, sound: SoundEffect) {
+ this.#sounds.set(key, sound);
+ }
+
+ public setSoundOnce(key: string, sound: SoundEffect) {
+ if (!this.#sounds.has(key)) {
+ this.#sounds.set(key, sound);
+ }
+ }
+
+ public playSound(key: string) {
+ this.#sounds.get(key)?.play()
+ }
+}
diff --git a/frontend/libs/soundboard/src/server.ts b/frontend/libs/soundboard/src/server.ts
new file mode 100644
index 0000000000..82a91ac5e2
--- /dev/null
+++ b/frontend/libs/soundboard/src/server.ts
@@ -0,0 +1 @@
+// This file should be used to export ONLY server-code from the library.
\ No newline at end of file
diff --git a/frontend/libs/soundboard/src/test-setup.ts b/frontend/libs/soundboard/src/test-setup.ts
new file mode 100644
index 0000000000..59f7836d9c
--- /dev/null
+++ b/frontend/libs/soundboard/src/test-setup.ts
@@ -0,0 +1,3 @@
+import { installGlobals } from '@remix-run/node';
+import "@testing-library/jest-dom/matchers";
+installGlobals();
\ No newline at end of file
diff --git a/frontend/libs/soundboard/tsconfig.json b/frontend/libs/soundboard/tsconfig.json
new file mode 100644
index 0000000000..8c5fc025ff
--- /dev/null
+++ b/frontend/libs/soundboard/tsconfig.json
@@ -0,0 +1,13 @@
+{
+ "files": [],
+ "include": [],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ },
+ {
+ "path": "./tsconfig.spec.json"
+ }
+ ],
+ "extends": "../../tsconfig.base.json"
+}
diff --git a/frontend/libs/soundboard/tsconfig.lib.json b/frontend/libs/soundboard/tsconfig.lib.json
new file mode 100644
index 0000000000..c43e2edbaa
--- /dev/null
+++ b/frontend/libs/soundboard/tsconfig.lib.json
@@ -0,0 +1,48 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "dist",
+ "types": [
+ "node",
+ "@nx/react/typings/cssmodule.d.ts",
+ "@nx/react/typings/image.d.ts",
+ "vite/client"
+ ],
+ "rootDir": "src",
+ "jsx": "react-jsx",
+ "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo"
+ },
+ "exclude": [
+ "out-tsc",
+ "dist",
+ "**/*.spec.ts",
+ "**/*.test.ts",
+ "**/*.spec.tsx",
+ "**/*.test.tsx",
+ "**/*.spec.js",
+ "**/*.test.js",
+ "**/*.spec.jsx",
+ "**/*.test.jsx",
+ "vite.config.ts",
+ "vite.config.mts",
+ "vitest.config.ts",
+ "vitest.config.mts",
+ "src/**/*.test.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.tsx",
+ "src/**/*.spec.tsx",
+ "src/**/*.test.js",
+ "src/**/*.spec.js",
+ "src/**/*.test.jsx",
+ "src/**/*.spec.jsx",
+ "eslint.config.js",
+ "eslint.config.cjs",
+ "eslint.config.mjs"
+ ],
+ "include": [
+ "src/**/*.js",
+ "src/**/*.jsx",
+ "src/**/*.ts",
+ "src/**/*.tsx"
+ ]
+}
diff --git a/frontend/libs/soundboard/tsconfig.spec.json b/frontend/libs/soundboard/tsconfig.spec.json
new file mode 100644
index 0000000000..c1faced82c
--- /dev/null
+++ b/frontend/libs/soundboard/tsconfig.spec.json
@@ -0,0 +1,34 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "./out-tsc/vitest",
+ "types": [
+ "vitest/globals",
+ "vitest/importMeta",
+ "vite/client",
+ "node",
+ "vitest"
+ ],
+ "jsx": "react-jsx"
+ },
+ "include": [
+ "vite.config.ts",
+ "vite.config.mts",
+ "vitest.config.ts",
+ "vitest.config.mts",
+ "src/**/*.test.ts",
+ "src/**/*.spec.ts",
+ "src/**/*.test.tsx",
+ "src/**/*.spec.tsx",
+ "src/**/*.test.js",
+ "src/**/*.spec.js",
+ "src/**/*.test.jsx",
+ "src/**/*.spec.jsx",
+ "src/**/*.d.ts"
+ ],
+ "references": [
+ {
+ "path": "./tsconfig.lib.json"
+ }
+ ]
+}
diff --git a/frontend/libs/soundboard/vite.config.ts b/frontend/libs/soundboard/vite.config.ts
new file mode 100644
index 0000000000..36aa0b79c2
--- /dev/null
+++ b/frontend/libs/soundboard/vite.config.ts
@@ -0,0 +1,49 @@
+///
+import { defineConfig } from 'vite';
+import react from '@vitejs/plugin-react';
+import dts from 'vite-plugin-dts';
+import * as path from 'path';
+
+export default defineConfig(() => ({
+root: __dirname,
+cacheDir: '../../node_modules/.vite/libs/soundboard',
+plugins: [react(), dts({ entryRoot: 'src', tsconfigPath: path.join(__dirname, 'tsconfig.lib.json') })],
+// Uncomment this if you are using workers.
+// worker: {
+// plugins: [ nxViteTsPaths() ],
+// },
+// Configuration for building your library.
+// See: https://vitejs.dev/guide/build.html#library-mode
+build: {
+outDir: './dist',
+emptyOutDir: true,
+reportCompressedSize: true,
+commonjsOptions: {
+transformMixedEsModules: true,
+},
+lib: {
+// Could also be a dictionary or array of multiple entry points.
+entry: 'src/index.ts',
+name: '@frontend/soundboard',
+fileName: 'index',
+// Change this to the formats you want to support.
+// Don't forget to update your package.json as well.
+formats: ['es' as const]
+},
+rollupOptions: {
+// External packages that should not be bundled into your library.
+external: ['react','react-dom','react/jsx-runtime']
+},
+},
+test: {setupFiles: ['./src/test-setup.ts'],
+watch: false,
+globals: true,
+environment: 'jsdom',
+include: ['{src,tests}/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
+reporters: ['default'],
+coverage: {
+reportsDirectory: './test-output/vitest/coverage',
+provider: 'v8' as const,
+}
+},
+}));
\ No newline at end of file
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index ce9fee89b5..2e333a49b4 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -2243,6 +2243,16 @@
"name": "@frontend/login",
"version": "0.0.1"
},
+ "libs/soundboard": {
+ "name": "@storyteller/soundboard",
+ "version": "0.0.1",
+ "dependencies": {
+ "howler": "^2.2.4"
+ },
+ "devDependencies": {
+ "@types/howler": "^2.2.12"
+ }
+ },
"libs/tauri-utils": {
"name": "@storyteller/tauri-utils",
"version": "0.0.1"
@@ -10253,6 +10263,10 @@
"resolved": "libs/api",
"link": true
},
+ "node_modules/@storyteller/soundboard": {
+ "resolved": "libs/soundboard",
+ "link": true
+ },
"node_modules/@storyteller/tauri-utils": {
"resolved": "libs/tauri-utils",
"link": true
@@ -11530,6 +11544,13 @@
"@types/unist": "^2"
}
},
+ "node_modules/@types/howler": {
+ "version": "2.2.12",
+ "resolved": "https://registry.npmjs.org/@types/howler/-/howler-2.2.12.tgz",
+ "integrity": "sha512-hy769UICzOSdK0Kn1FBk4gN+lswcj1EKRkmiDtMkUGvFfYJzgaDXmVXkSShS2m89ERAatGIPnTUlp2HhfkVo5g==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/http-proxy": {
"version": "1.17.16",
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz",
@@ -19392,6 +19413,12 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/howler": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/howler/-/howler-2.2.4.tgz",
+ "integrity": "sha512-iARIBPgcQrwtEr+tALF+rapJ8qSc+Set2GJQl7xT1MQzWaVkFebdJhR3alVlSiUf5U7nAANKuj3aWpwerocD5w==",
+ "license": "MIT"
+ },
"node_modules/html-encoding-sniffer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz",
diff --git a/frontend/tsconfig.base.json b/frontend/tsconfig.base.json
index b9d3fc546e..76fc721f9f 100644
--- a/frontend/tsconfig.base.json
+++ b/frontend/tsconfig.base.json
@@ -5,7 +5,10 @@
"emitDeclarationOnly": true,
"importHelpers": true,
"isolatedModules": true,
- "lib": ["es2022", "dom"],
+ "lib": [
+ "es2022",
+ "dom"
+ ],
"module": "esnext",
"moduleResolution": "bundler",
"noEmitOnError": true,
@@ -16,10 +19,17 @@
"skipLibCheck": true,
"strict": true,
"target": "es2022",
- "customConditions": ["development"],
+ "customConditions": [
+ "development"
+ ],
"baseUrl": ".",
"paths": {
- "@frontend/login": ["libs/login/src/index.ts"]
+ "@frontend/login": [
+ "libs/login/src/index.ts"
+ ],
+ "@frontend/soundboard": [
+ "libs/soundboard/src/index.ts"
+ ]
}
}
}
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index 41932f378f..f44f379a4e 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -59,6 +59,9 @@
},
{
"path": "./libs/components/loading-spinner"
+ },
+ {
+ "path": "./libs/soundboard"
}
]
}