@dice-o-rolla/dice-assets 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/NOTICE +4 -0
- package/README.md +61 -0
- package/THIRD_PARTY_NOTICES.md +17 -0
- package/assets/runtime/audio/classic-coin.webm +0 -0
- package/assets/runtime/audio/classic-dice.webm +0 -0
- package/assets/runtime/audio/classic-felt.webm +0 -0
- package/assets/runtime/audio/classic-metal.webm +0 -0
- package/assets/runtime/audio/classic-wood-table.webm +0 -0
- package/assets/runtime/audio/classic-wood-tray.webm +0 -0
- package/assets/runtime/audio/resin.webm +0 -0
- package/assets/runtime/audio/wood.webm +0 -0
- package/assets/runtime/catalog.json +616 -0
- package/assets/runtime/textures/digits.ktx2 +0 -0
- package/assets/runtime/textures/speckle-base.ktx2 +0 -0
- package/assets/runtime/textures/speckle-normal.ktx2 +0 -0
- package/assets/runtime/textures/speckle-orm.ktx2 +0 -0
- package/dist/asset-registry.d.ts +31 -0
- package/dist/asset-registry.js +203 -0
- package/dist/catalog-loader.d.ts +15 -0
- package/dist/catalog-loader.js +80 -0
- package/dist/impact-sound-gate.d.ts +20 -0
- package/dist/impact-sound-gate.js +30 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/three-material-provider.d.ts +16 -0
- package/dist/three-material-provider.js +132 -0
- package/dist/types.d.ts +94 -0
- package/dist/types.js +0 -0
- package/dist/web-audio-player.d.ts +45 -0
- package/dist/web-audio-player.js +110 -0
- package/package.json +53 -0
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
export class WebAudioSpritePlayer {
|
|
2
|
+
registry;
|
|
3
|
+
#context;
|
|
4
|
+
#fetch;
|
|
5
|
+
#random;
|
|
6
|
+
#buffers = new Map();
|
|
7
|
+
#loading = new Map();
|
|
8
|
+
#voices = new Map();
|
|
9
|
+
constructor(registry, options) {
|
|
10
|
+
this.registry = registry;
|
|
11
|
+
this.#context = options.context;
|
|
12
|
+
this.#fetch = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
13
|
+
this.#random = options.random ?? Math.random;
|
|
14
|
+
}
|
|
15
|
+
async preloadBank(bankId) {
|
|
16
|
+
const bank = this.#requireBank(bankId);
|
|
17
|
+
await this.#loadSprite(this.#requireSprite(bank.spriteId));
|
|
18
|
+
}
|
|
19
|
+
async playImpact(options) {
|
|
20
|
+
await Promise.all([
|
|
21
|
+
this.playBank(options.dieMaterialBankId, options.force),
|
|
22
|
+
...(options.surfaceMaterialBankId === undefined
|
|
23
|
+
? []
|
|
24
|
+
: [this.playBank(options.surfaceMaterialBankId, options.force)]),
|
|
25
|
+
]);
|
|
26
|
+
}
|
|
27
|
+
async playBank(bankId, force) {
|
|
28
|
+
const bank = this.#requireBank(bankId);
|
|
29
|
+
const maxVoices = bank.maxVoices ?? 6;
|
|
30
|
+
if ((this.#voices.get(bankId) ?? 0) >= maxVoices)
|
|
31
|
+
return false;
|
|
32
|
+
const sprite = this.#requireSprite(bank.spriteId);
|
|
33
|
+
const clipId = chooseClip(bank, sprite, this.#random());
|
|
34
|
+
const clip = sprite.clips[clipId];
|
|
35
|
+
if (clip === undefined)
|
|
36
|
+
return false;
|
|
37
|
+
const buffer = await this.#loadSprite(sprite);
|
|
38
|
+
const source = this.#context.createBufferSource();
|
|
39
|
+
const gain = this.#context.createGain();
|
|
40
|
+
source.buffer = buffer;
|
|
41
|
+
source.playbackRate.value = centsToRate(randomSigned(this.#random) * (bank.pitchVariationCents ?? 24));
|
|
42
|
+
gain.gain.value = resolveImpactGain(bank, clip, force, randomSigned(this.#random));
|
|
43
|
+
source.connect(gain);
|
|
44
|
+
gain.connect(this.#context.destination);
|
|
45
|
+
this.#voices.set(bankId, (this.#voices.get(bankId) ?? 0) + 1);
|
|
46
|
+
source.addEventListener('ended', () => {
|
|
47
|
+
this.#voices.set(bankId, Math.max(0, (this.#voices.get(bankId) ?? 1) - 1));
|
|
48
|
+
}, { once: true });
|
|
49
|
+
source.start(0, clip.offsetSeconds, clip.durationSeconds);
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
async #loadSprite(sprite) {
|
|
53
|
+
const cached = this.#buffers.get(sprite.id);
|
|
54
|
+
if (cached !== undefined)
|
|
55
|
+
return cached;
|
|
56
|
+
const pending = this.#loading.get(sprite.id);
|
|
57
|
+
if (pending !== undefined)
|
|
58
|
+
return pending;
|
|
59
|
+
const promise = this.#fetch(sprite.audio.uri)
|
|
60
|
+
.then((response) => response.arrayBuffer())
|
|
61
|
+
.then((data) => this.#context.decodeAudioData(data))
|
|
62
|
+
.then((buffer) => {
|
|
63
|
+
this.#buffers.set(sprite.id, buffer);
|
|
64
|
+
this.#loading.delete(sprite.id);
|
|
65
|
+
return buffer;
|
|
66
|
+
}, (error) => {
|
|
67
|
+
this.#loading.delete(sprite.id);
|
|
68
|
+
throw error;
|
|
69
|
+
});
|
|
70
|
+
this.#loading.set(sprite.id, promise);
|
|
71
|
+
return promise;
|
|
72
|
+
}
|
|
73
|
+
#requireBank(id) {
|
|
74
|
+
const bank = this.registry.audioBanks.get(id);
|
|
75
|
+
if (bank === undefined)
|
|
76
|
+
throw new Error(`Unknown audio bank: ${id}`);
|
|
77
|
+
return bank;
|
|
78
|
+
}
|
|
79
|
+
#requireSprite(id) {
|
|
80
|
+
const sprite = this.registry.audio.get(id);
|
|
81
|
+
if (sprite === undefined)
|
|
82
|
+
throw new Error(`Unknown audio sprite: ${id}`);
|
|
83
|
+
return sprite;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export function resolveImpactGain(bank, clip, force, signedVariation = 0) {
|
|
87
|
+
const forceSpan = bank.forceRange[1] - bank.forceRange[0];
|
|
88
|
+
const normalized = Math.min(1, Math.max(0, (force - bank.forceRange[0]) / forceSpan));
|
|
89
|
+
const shaped = Math.sqrt(normalized);
|
|
90
|
+
const base = bank.gainRange[0] + shaped * (bank.gainRange[1] - bank.gainRange[0]);
|
|
91
|
+
const variation = 1 + signedVariation * (bank.gainVariation ?? 0.04);
|
|
92
|
+
return Math.max(0, base * (clip.gain ?? 1) * variation);
|
|
93
|
+
}
|
|
94
|
+
function chooseClip(bank, sprite, random) {
|
|
95
|
+
const weighted = bank.clipIds.map((id) => ({ id, weight: sprite.clips[id]?.weight ?? 1 }));
|
|
96
|
+
const total = weighted.reduce((sum, entry) => sum + entry.weight, 0);
|
|
97
|
+
let cursor = random * total;
|
|
98
|
+
for (const entry of weighted) {
|
|
99
|
+
cursor -= entry.weight;
|
|
100
|
+
if (cursor <= 0)
|
|
101
|
+
return entry.id;
|
|
102
|
+
}
|
|
103
|
+
return bank.clipIds.at(-1) ?? '';
|
|
104
|
+
}
|
|
105
|
+
function centsToRate(cents) {
|
|
106
|
+
return 2 ** (cents / 1_200);
|
|
107
|
+
}
|
|
108
|
+
function randomSigned(random) {
|
|
109
|
+
return random() * 2 - 1;
|
|
110
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dice-o-rolla/dice-assets",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Optional Web Audio and KTX2/PBR asset system for Dice O Rolla.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"assets",
|
|
7
|
+
"dice",
|
|
8
|
+
"skins",
|
|
9
|
+
"sounds",
|
|
10
|
+
"typescript"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/creepiest-space/dice-o-rolla#readme",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/creepiest-space/dice-o-rolla/issues"
|
|
15
|
+
},
|
|
16
|
+
"license": "Apache-2.0",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "https://github.com/creepiest-space/dice-o-rolla.git",
|
|
20
|
+
"directory": "packages/dice-assets"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist",
|
|
24
|
+
"assets/runtime",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE",
|
|
27
|
+
"NOTICE",
|
|
28
|
+
"THIRD_PARTY_NOTICES.md"
|
|
29
|
+
],
|
|
30
|
+
"type": "module",
|
|
31
|
+
"sideEffects": false,
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"default": "./dist/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./catalog.json": "./assets/runtime/catalog.json"
|
|
40
|
+
},
|
|
41
|
+
"publishConfig": {
|
|
42
|
+
"access": "public",
|
|
43
|
+
"registry": "https://registry.npmjs.org/"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@dice-o-rolla/dice-renderer-three": "0.2.0",
|
|
47
|
+
"@types/three": "^0.185.0",
|
|
48
|
+
"three": "^0.185.0"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20.0.0"
|
|
52
|
+
}
|
|
53
|
+
}
|