Skip to content

Commit 92baaa9

Browse files
committed
feat: sound themes
1 parent da0d8ea commit 92baaa9

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

80 files changed

+326
-161
lines changed

docs/translations_report.md

+64-46

package-lock.json

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/keybr-intl/lib/messages/ar.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/bg.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/ca.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/cs.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/da.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/de.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/el.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/en.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/eo.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/es.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/et.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/fa.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/fr.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/he.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/hr.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/hu.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/id.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/it.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/ja.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/ko.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/ne.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/nl.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/pl.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/pt-br.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/pt-pt.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/ro.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/ru.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/sv.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/th.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/tr.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/uk.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/vi.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/zh-hans.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/lib/messages/zh-hant.json

+1-1
Large diffs are not rendered by default.

packages/keybr-intl/translations/en.json

+1
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@
373373
"settings.recoverKeys.prefix": "Unlock a next key only when:",
374374
"settings.repeatWords.description": "Repeat each word a number of times. Type a word for the first time to develop your muscle memory. Typing the same word consecutively should be easier.",
375375
"settings.repeatWords.label": "Repeat each word:",
376+
"settings.soundTheme.label": "Sound Theme:",
376377
"settings.soundVolume.label": "Volume:",
377378
"settings.sounds.allSoundsValue": "All Sounds",
378379
"settings.sounds.errorSoundsOnlyValue": "Error Sounds Only",

packages/keybr-sound/lib/library.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { nullPlayer, WebAudioPlayer } from "./player.ts";
55
import {
66
type Player,
77
type PlayerConfig,
8+
type PlayerId,
89
type SoundAssets,
9-
type SoundName,
1010
} from "./types.ts";
1111

1212
class PlayerLoader {
@@ -65,23 +65,23 @@ class PlayerLoader {
6565
}
6666
}
6767

68-
const loaders = new Map<SoundName, PlayerLoader>();
68+
const loaders = new Map<PlayerId, PlayerLoader>();
6969

7070
export function loadSounds(assets: SoundAssets) {
71-
for (const [name, config] of Object.entries(assets)) {
72-
let loader = loaders.get(name);
71+
for (const [id, config] of Object.entries(assets)) {
72+
let loader = loaders.get(id);
7373
if (loader == null || loader.config !== config) {
7474
loader = new PlayerLoader(config);
75-
loaders.set(name, loader);
75+
loaders.set(id, loader);
7676
loader.load().catch(catchError);
7777
}
7878
}
7979
}
8080

81-
export function playSound(name: SoundName, volume: number = 1) {
82-
const loader = loaders.get(name);
81+
export function playSound(id: PlayerId, volume: number = 1) {
82+
const loader = loaders.get(id);
8383
if (loader == null) {
84-
throw new Error(String(name));
84+
throw new Error(String(id));
8585
}
8686
loader
8787
.init()

packages/keybr-sound/lib/types.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
export type SoundName = symbol | string | number;
1+
export type PlayerId = symbol | string | number;
22

33
export type SoundAssets = {
4-
readonly [name: SoundName]: PlayerConfig;
4+
readonly [id: PlayerId]: PlayerConfig;
55
};
66

77
export type PlayerConfig = {
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

packages/keybr-textinput-sounds/lib/assets.ts

-23
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./player.ts";
2+
export * from "./settings.ts";
+24-9
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,51 @@
11
import { type Settings, useSettings } from "@keybr/settings";
2-
import { loadSounds, playSound } from "@keybr/sound";
3-
import { Feedback, PlaySounds, textDisplayProps } from "@keybr/textinput";
2+
import { Feedback } from "@keybr/textinput";
43
import { useMemo } from "react";
5-
import { TextInputSound, textInputSounds } from "./assets.ts";
4+
import { PlaySounds, soundProps, SoundTheme } from "./settings.ts";
5+
import { Theme } from "./sound.ts";
6+
import { themeDefault } from "./themes/default.ts";
7+
import { themeMechanical1 } from "./themes/mechanical1.ts";
8+
import { themeTypewriter1 } from "./themes/typewriter1.ts";
69

710
export function useSoundPlayer() {
811
const { settings } = useSettings();
912
return useMemo(() => makeSoundPlayer(settings), [settings]);
1013
}
1114

1215
export function makeSoundPlayer(settings: Settings) {
13-
const playSounds = settings.get(textDisplayProps.playSounds);
14-
const soundVolume = settings.get(textDisplayProps.soundVolume);
15-
loadSounds(textInputSounds);
16+
const playSounds = settings.get(soundProps.playSounds);
17+
const soundVolume = settings.get(soundProps.soundVolume);
18+
const soundTheme = settings.get(soundProps.soundTheme);
19+
const theme = loadTheme(soundTheme);
1620
return (feedback: Feedback) => {
1721
if (playSounds === PlaySounds.All) {
1822
switch (feedback) {
1923
case Feedback.Succeeded:
2024
case Feedback.Recovered:
21-
playSound(TextInputSound.Click, soundVolume);
25+
theme.play("click", soundVolume);
2226
break;
2327
case Feedback.Failed:
24-
playSound(TextInputSound.Blip, soundVolume);
28+
theme.play("blip", soundVolume);
2529
break;
2630
}
2731
}
2832
if (playSounds === PlaySounds.ErrorsOnly) {
2933
switch (feedback) {
3034
case Feedback.Failed:
31-
playSound(TextInputSound.Blip, soundVolume);
35+
theme.play("blip", soundVolume);
3236
break;
3337
}
3438
}
3539
};
3640
}
41+
42+
function loadTheme(soundTheme: SoundTheme) {
43+
switch (soundTheme) {
44+
case SoundTheme.MECHANICAL1:
45+
return new Theme(themeMechanical1);
46+
case SoundTheme.TYPEWRITER1:
47+
return new Theme(themeTypewriter1);
48+
default:
49+
return new Theme(themeDefault);
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Enum, type EnumItem } from "@keybr/lang";
2+
import { enumProp, itemProp, numberProp } from "@keybr/settings";
3+
4+
export enum PlaySounds {
5+
None = 1,
6+
ErrorsOnly = 2,
7+
All = 3,
8+
}
9+
10+
export class SoundTheme implements EnumItem {
11+
static readonly DEFAULT = new SoundTheme("default", "Default");
12+
static readonly MECHANICAL1 = new SoundTheme("mechanical1", "Mechanical");
13+
static readonly TYPEWRITER1 = new SoundTheme("typewriter1", "Typewriter");
14+
15+
static readonly ALL = new Enum<SoundTheme>(
16+
SoundTheme.DEFAULT,
17+
SoundTheme.MECHANICAL1,
18+
SoundTheme.TYPEWRITER1,
19+
);
20+
21+
private constructor(
22+
readonly id: string,
23+
readonly name: string,
24+
) {}
25+
26+
toString() {
27+
return this.id;
28+
}
29+
30+
toJSON() {
31+
return this.id;
32+
}
33+
}
34+
35+
export const soundProps = {
36+
playSounds: enumProp("textInput.playSounds", PlaySounds, PlaySounds.None),
37+
soundVolume: numberProp("textInput.soundVolume", 0.5, { min: 0, max: 1 }),
38+
soundTheme: itemProp(
39+
"textInput.soundTheme",
40+
SoundTheme.ALL,
41+
SoundTheme.DEFAULT,
42+
),
43+
} as const;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { randomSample } from "@keybr/rand";
2+
import {
3+
loadSounds,
4+
type PlayerConfig,
5+
type PlayerId,
6+
playSound,
7+
type SoundAssets,
8+
} from "@keybr/sound";
9+
10+
export type SoundId = "click" | "blip";
11+
12+
export type ThemeConfig = Record<SoundId, readonly PlayerConfig[]>;
13+
14+
export class Theme {
15+
readonly #assets: Record<SoundId, SoundAssets>;
16+
17+
constructor(theme: ThemeConfig) {
18+
const click = collect("click", theme.click);
19+
const blip = collect("blip", theme.blip);
20+
this.#assets = { click, blip };
21+
loadSounds({ ...click, ...blip });
22+
}
23+
24+
play(id: SoundId, volume: number) {
25+
playSound(randomSample(Object.keys(this.#assets[id])), volume);
26+
}
27+
}
28+
29+
function collect(id: string, configs: readonly PlayerConfig[]): SoundAssets {
30+
const sounds = {} as Record<PlayerId, PlayerConfig>;
31+
for (let i = 0; i < configs.length; i++) {
32+
sounds[`${id}-${i}`] = configs[i];
33+
}
34+
return sounds;
35+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { type PlayerConfig } from "@keybr/sound";
2+
import blipMp3 from "../../assets/blip.mp3";
3+
import blipWav from "../../assets/blip.wav";
4+
5+
export const blip = {
6+
urls: [blipMp3, blipWav],
7+
} as const satisfies PlayerConfig;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import clickMp3 from "../../assets/default-click.mp3";
2+
import clickWav from "../../assets/default-click.wav";
3+
import { type ThemeConfig } from "../sound.ts";
4+
import { blip } from "./common.ts";
5+
6+
export const themeDefault = {
7+
blip: [blip],
8+
click: [{ urls: [clickMp3, clickWav] }],
9+
} as const satisfies ThemeConfig;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import click1Mp3 from "../../assets/mechanical1-click1.mp3";
2+
import click1Wav from "../../assets/mechanical1-click1.wav";
3+
import click2Mp3 from "../../assets/mechanical1-click2.mp3";
4+
import click2Wav from "../../assets/mechanical1-click2.wav";
5+
import click3Mp3 from "../../assets/mechanical1-click3.mp3";
6+
import click3Wav from "../../assets/mechanical1-click3.wav";
7+
import click4Mp3 from "../../assets/mechanical1-click4.mp3";
8+
import click4Wav from "../../assets/mechanical1-click4.wav";
9+
import click5Mp3 from "../../assets/mechanical1-click5.mp3";
10+
import click5Wav from "../../assets/mechanical1-click5.wav";
11+
import { type ThemeConfig } from "../sound.ts";
12+
import { blip } from "./common.ts";
13+
14+
export const themeMechanical1 = {
15+
blip: [blip],
16+
click: [
17+
{ urls: [click1Mp3, click1Wav] },
18+
{ urls: [click2Mp3, click2Wav] },
19+
{ urls: [click3Mp3, click3Wav] },
20+
{ urls: [click4Mp3, click4Wav] },
21+
{ urls: [click5Mp3, click5Wav] },
22+
],
23+
} as const satisfies ThemeConfig;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import click1Mp3 from "../../assets/typewriter1-click1.mp3";
2+
import click1Wav from "../../assets/typewriter1-click1.wav";
3+
import click2Mp3 from "../../assets/typewriter1-click2.mp3";
4+
import click2Wav from "../../assets/typewriter1-click2.wav";
5+
import click3Mp3 from "../../assets/typewriter1-click3.mp3";
6+
import click3Wav from "../../assets/typewriter1-click3.wav";
7+
import click4Mp3 from "../../assets/typewriter1-click4.mp3";
8+
import click4Wav from "../../assets/typewriter1-click4.wav";
9+
import click5Mp3 from "../../assets/typewriter1-click5.mp3";
10+
import click5Wav from "../../assets/typewriter1-click5.wav";
11+
import { type ThemeConfig } from "../sound.ts";
12+
import { blip } from "./common.ts";
13+
14+
export const themeTypewriter1 = {
15+
blip: [blip],
16+
click: [
17+
{ urls: [click1Mp3, click1Wav] },
18+
{ urls: [click2Mp3, click2Wav] },
19+
{ urls: [click3Mp3, click3Wav] },
20+
{ urls: [click4Mp3, click4Wav] },
21+
{ urls: [click5Mp3, click5Wav] },
22+
],
23+
} as const satisfies ThemeConfig;

packages/keybr-textinput-sounds/package.json

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
"main": "lib/index.ts",
77
"types": ".types/index.d.ts",
88
"dependencies": {
9+
"@keybr/lang": "*",
10+
"@keybr/rand": "*",
911
"@keybr/settings": "*",
1012
"@keybr/sound": "*",
1113
"@keybr/textinput": "*"

0 commit comments

Comments
 (0)