@adatechnology/game-audio 0.7.0 → 0.9.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.
@@ -0,0 +1,180 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
3
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
4
+
5
+ // src/manifest.ts
6
+ var SDK_LICENSES = ["CC0", "CC-BY"];
7
+ var SFX_LAYERS = ["close", "far", "tail_in", "tail_out"];
8
+ var SFX_TIERS = { light: 0, medium: 0.34, heavy: 0.67 };
9
+ function parseSfxName(name) {
10
+ const match = /^(.*?)(?:_(close|far|tail_in|tail_out))?(?:_(\d+))?$/.exec(name);
11
+ const id = match?.[1] || name;
12
+ const layer = match?.[2] ?? "close";
13
+ const take = match?.[3] ? Number(match[3]) : 1;
14
+ return { id, layer, take };
15
+ }
16
+ function splitTier(id) {
17
+ const match = /^(.*)_(light|medium|heavy)$/.exec(id);
18
+ return match ? { base: match[1], tier: match[2] } : { base: id };
19
+ }
20
+ var refsOf = (entries, baseUrl) => (entries ?? []).map((entry) => ({ url: `${baseUrl}${entry.file}` }));
21
+ function sampleBankFromManifest(manifest, options) {
22
+ const baseUrl = options.baseUrl ?? "";
23
+ const samples = [];
24
+ const tiersOf = /* @__PURE__ */ new Map();
25
+ for (const sound of manifest.sounds) {
26
+ const { base, tier } = splitTier(sound.id);
27
+ if (tier) tiersOf.set(base, [...tiersOf.get(base) ?? [], sound]);
28
+ }
29
+ const bases = /* @__PURE__ */ new Map();
30
+ for (const sound of manifest.sounds) {
31
+ if (!splitTier(sound.id).tier) bases.set(sound.id, sound);
32
+ }
33
+ for (const [base, tiers] of tiersOf) {
34
+ if (!bases.has(base)) bases.set(base, { id: base, category: tiers[0]?.category ?? "", layers: { close: tiers[0]?.layers.close ?? [] } });
35
+ }
36
+ for (const sound of bases.values()) {
37
+ const numbers = options.sounds?.[sound.id] ?? options.defaults?.(sound);
38
+ const close = refsOf(sound.layers.close, baseUrl);
39
+ if (!numbers || close.length === 0) continue;
40
+ const tiers = (tiersOf.get(sound.id) ?? []).map((tier) => {
41
+ const name = splitTier(tier.id).tier;
42
+ const tierFar = refsOf(tier.layers.far, baseUrl);
43
+ return { name, from: SFX_TIERS[name] ?? 0, close: refsOf(tier.layers.close, baseUrl), ...tierFar.length > 0 ? { far: tierFar } : {} };
44
+ }).sort((a, b) => a.from - b.from);
45
+ const far = refsOf(sound.layers.far, baseUrl);
46
+ const indoor = refsOf(sound.layers.tail_in, baseUrl);
47
+ const outdoor = refsOf(sound.layers.tail_out, baseUrl);
48
+ const files = [sound, ...tiersOf.get(sound.id) ?? []].flatMap((entry) => Object.values(entry.layers).flatMap((entries) => entries ?? []));
49
+ samples.push({
50
+ id: sound.id,
51
+ bus: numbers.bus,
52
+ close,
53
+ ...far.length > 0 ? { far } : {},
54
+ ...tiers.length > 0 ? { tiers } : {},
55
+ ...numbers.crossover ? { crossover: numbers.crossover } : {},
56
+ ...indoor.length > 0 || outdoor.length > 0 ? { tail: { ...indoor.length > 0 ? { indoor } : {}, ...outdoor.length > 0 ? { outdoor } : {}, ...numbers.tailGain === void 0 ? {} : { gain: numbers.tailGain } } } : {},
57
+ ...numbers.pitch === void 0 ? {} : { pitch: numbers.pitch },
58
+ ...numbers.gain === void 0 ? {} : { gain: numbers.gain },
59
+ ...numbers.falloff ? { falloff: numbers.falloff } : {},
60
+ ...numbers.cutoffK === void 0 ? {} : { cutoffK: numbers.cutoffK },
61
+ ...numbers.group ? { group: numbers.group } : {},
62
+ ...numbers.priority === void 0 ? {} : { priority: numbers.priority },
63
+ ...numbers.fallback ? { fallback: numbers.fallback } : {},
64
+ sources: files.map((file) => `${file.file}: ${file.source} ${file.author} ${file.license}`)
65
+ });
66
+ }
67
+ return { id: options.id, samples };
68
+ }
69
+
70
+ // src/kit-samples.ts
71
+ var KIT_CATEGORY_BUS = {
72
+ armas: "weapons",
73
+ impactos: "world",
74
+ "explosoes-fogo": "world",
75
+ personagem: "world",
76
+ veiculos: "world",
77
+ "itens-portas": "world",
78
+ ambiente: "ambience"
79
+ };
80
+ var VOICE_IDS = /^(player_(hurt|death|effort|breath)|scream|zombie_)/;
81
+ var KIT_SAMPLE_ALIASES = {
82
+ // a house door: the latch giving, the hinge, the leaf meeting the frame, a locked one shaken; a drawer on its runners
83
+ latch: "metal_latch",
84
+ creak_wood: "door_creak",
85
+ shut_wood: "door_wood_shut",
86
+ rattle: "door_locked_rattle",
87
+ // a drawer stays on its recipe: the recorded runs (1 to 1.9 s) outlast a drawer's pull
88
+ // an item taken in the hand (game-destruction-three grab_* keys)
89
+ grab_hard: "item_pickup_generic",
90
+ grab_soft: "item_pickup_leather",
91
+ // the hand foley of the test room (game-audio FOLEY_BANK) whose id is not a file id: a wooden leaf shut and slammed, an item picked up by material.
92
+ // The metal door stays on its recipe (two of its takes run 10 s), and so do the fridge, the microwave and paper (no file for them).
93
+ door_wood_close: "door_wood_shut",
94
+ door_wood_slam: "door_slam_wood",
95
+ item_pickup_metal: "item_pickup_generic",
96
+ item_pickup_plastic: "item_pickup_generic",
97
+ item_pickup_glass: "item_pickup_generic",
98
+ item_pickup_wood: "item_pickup_generic",
99
+ item_pickup_fabric: "item_pickup_leather"
100
+ };
101
+ function kitSampleDefaults(sound) {
102
+ const bus = VOICE_IDS.test(sound.id) ? "voices" : KIT_CATEGORY_BUS[sound.category];
103
+ if (!bus) return void 0;
104
+ return { bus, pitch: sound.category === "ambiente" || /_loop/.test(sound.id) ? 0 : 0.04 };
105
+ }
106
+ function withAliases(manifest, aliases) {
107
+ const byId = new Map(manifest.sounds.map((sound) => [sound.id, sound]));
108
+ const extra = [];
109
+ for (const [alias, target] of Object.entries(aliases)) {
110
+ const sound = byId.get(target);
111
+ if (sound && !byId.has(alias)) extra.push({ ...sound, id: alias });
112
+ }
113
+ return { ...manifest, sounds: [...manifest.sounds, ...extra] };
114
+ }
115
+ function kitSampleBank(manifest, options = {}) {
116
+ return sampleBankFromManifest(withAliases(manifest, options.aliases ?? KIT_SAMPLE_ALIASES), {
117
+ id: options.id ?? "kit_samples",
118
+ baseUrl: options.baseUrl ?? "",
119
+ defaults: kitSampleDefaults
120
+ });
121
+ }
122
+ var isFileUrl = (url) => {
123
+ const page = globalThis.location?.href;
124
+ try {
125
+ return new URL(url, page).protocol === "file:";
126
+ } catch {
127
+ return false;
128
+ }
129
+ };
130
+ var fetchJson = async (url) => {
131
+ if (isFileUrl(url)) throw new Error(`${url}: file:// is not fetched`);
132
+ const response = await fetch(url);
133
+ if (!response.ok) throw new Error(`${url}: ${response.status}`);
134
+ return await response.json();
135
+ };
136
+ async function loadKitSamples(engine, options = {}) {
137
+ const baseUrl = options.baseUrl ?? "";
138
+ let manifest;
139
+ try {
140
+ manifest = await (options.fetchManifest ?? fetchJson)(`${baseUrl}manifest.json`);
141
+ } catch {
142
+ return null;
143
+ }
144
+ const bank = kitSampleBank(manifest, options);
145
+ engine.register(bank);
146
+ if (options.preload && engine.loadSamples) void engine.loadSamples(options.preload).catch(() => void 0);
147
+ return bank;
148
+ }
149
+ function pointSamples(engine, files, options = {}) {
150
+ const baseUrl = options.baseUrl ?? "";
151
+ const samples = Object.entries(files).map(([id, urls]) => {
152
+ const close = (typeof urls === "string" ? [urls] : [...urls]).map((url) => ({ url: `${baseUrl}${url}` }));
153
+ if (close.length === 0) throw new Error(`pointSamples: ${id} has no file`);
154
+ const kit = engine.registry.sample(id);
155
+ if (!kit) return { id, bus: options.bus ?? "world", close, sources: ["game: pointSamples"] };
156
+ const { far: _far, tiers: _tiers, tail: _tail, ...numbers } = kit;
157
+ return { ...numbers, close };
158
+ });
159
+ const bank = { id: options.id ?? "game_samples", samples };
160
+ engine.register(bank);
161
+ return bank;
162
+ }
163
+
164
+ export {
165
+ __publicField,
166
+ SDK_LICENSES,
167
+ SFX_LAYERS,
168
+ SFX_TIERS,
169
+ parseSfxName,
170
+ splitTier,
171
+ sampleBankFromManifest,
172
+ KIT_CATEGORY_BUS,
173
+ KIT_SAMPLE_ALIASES,
174
+ kitSampleDefaults,
175
+ withAliases,
176
+ kitSampleBank,
177
+ loadKitSamples,
178
+ pointSamples
179
+ };
180
+ //# sourceMappingURL=chunk-JV5XXCQE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/manifest.ts","../src/kit-samples.ts"],"sourcesContent":["/**\n * The manifest the converter (`scripts/sfx-convert.mjs`) writes next to the converted files: every sound id with its takes per layer, the file\n * facts the criteria check (rate, channels, peak, length) and where each file came from. `sampleBankFromManifest` turns it into a `SoundBank`\n * of `SampleSound`s, with the bus and the numbers of each sound given by the caller.\n */\nimport type { BusId, Falloff, SoundBank } from './recipe'\nimport type { SampleCrossover, SampleRef, SampleSound } from './sample'\n\n/** Licences the SDK accepts (the files are redistributed on npm): public domain or attribution only. */\nexport const SDK_LICENSES = ['CC0', 'CC-BY'] as const\nexport type SdkLicense = (typeof SDK_LICENSES)[number]\n\nexport type SfxFileEntry = {\n /** Path from the manifest's folder: `armas/pistol_9mm_close_01.ogg`. */\n readonly file: string\n readonly sampleRate: number\n readonly channels: number\n /** dBFS. */\n readonly peak: number\n readonly seconds: number\n readonly source: string\n readonly author: string\n readonly license: SdkLicense\n}\n\n/** The layers a file name can end in: `<id>_<layer>_<nn>` (`pistol_9mm_close_01`); a name without one is a `close` take. */\nexport const SFX_LAYERS = ['close', 'far', 'tail_in', 'tail_out'] as const\nexport type SfxLayer = (typeof SFX_LAYERS)[number]\n\nexport type SfxManifestSound = {\n readonly id: string\n readonly category: string\n readonly layers: Partial<Readonly<Record<SfxLayer, readonly SfxFileEntry[]>>>\n}\n\nexport type SfxManifest = {\n readonly version: 1\n readonly format: { readonly codec: 'vorbis'; readonly sampleRate: number; readonly channels: number; readonly peakDb: number }\n readonly sounds: readonly SfxManifestSound[]\n}\n\n/** The numbers of one sound that the files do not say (bus, curve, pitch...). */\nexport type SampleDefaults = {\n readonly bus: BusId\n readonly gain?: number\n readonly pitch?: number\n readonly crossover?: SampleCrossover\n readonly tailGain?: number\n readonly falloff?: Falloff\n readonly cutoffK?: number\n readonly group?: string\n readonly priority?: number\n readonly fallback?: string\n}\n\nexport type SampleBankOptions = {\n readonly id: string\n /** Prefix of every url (where the manifest's folder is served: `/sfx/`). Default ''. */\n readonly baseUrl?: string\n /** Numbers per sound id, or one function; a sound with neither is left out (it has no bus). */\n readonly sounds?: Readonly<Record<string, SampleDefaults>>\n readonly defaults?: (sound: { readonly id: string; readonly category: string }) => SampleDefaults | undefined\n}\n\n/** The strength suffixes of a sound id (`kick_hit_heavy`): takes of the tiers of `kick_hit`, from these intensities. */\nexport const SFX_TIERS: Readonly<Record<string, number>> = { light: 0, medium: 0.34, heavy: 0.67 }\n\n/** Splits a file name (no extension) into its sound id, layer and take number. */\nexport function parseSfxName(name: string): { readonly id: string; readonly layer: SfxLayer; readonly take: number } {\n const match = /^(.*?)(?:_(close|far|tail_in|tail_out))?(?:_(\\d+))?$/.exec(name)\n const id = match?.[1] || name\n const layer = (match?.[2] as SfxLayer | undefined) ?? 'close'\n const take = match?.[3] ? Number(match[3]) : 1\n return { id, layer, take }\n}\n\n/** The base id and tier of a sound id: `kick_hit_heavy` → `kick_hit`, `heavy`. */\nexport function splitTier(id: string): { readonly base: string; readonly tier?: string } {\n const match = /^(.*)_(light|medium|heavy)$/.exec(id)\n return match ? { base: match[1] as string, tier: match[2] as string } : { base: id }\n}\n\n/** What `sampleBankFromManifest` reads of a manifest: the sounds, their layers and where each file came from (any licence string). */\nexport type SfxManifestLike = {\n readonly sounds: readonly {\n readonly id: string\n readonly category: string\n readonly layers: Partial<Readonly<Record<SfxLayer, readonly { readonly file: string; readonly source: string; readonly author: string; readonly license: string }[]>>>\n }[]\n}\n\nconst refsOf = (entries: readonly { readonly file: string }[] | undefined, baseUrl: string): SampleRef[] => (entries ?? []).map((entry) => ({ url: `${baseUrl}${entry.file}` }))\n\n/** A bank of sample sounds from a manifest. */\nexport function sampleBankFromManifest(manifest: SfxManifestLike, options: SampleBankOptions): SoundBank {\n const baseUrl = options.baseUrl ?? ''\n const samples: SampleSound[] = []\n // `<id>_light`, `_medium`, `_heavy` are the tiers of `<id>`, gathered under it\n type Sound = SfxManifestLike['sounds'][number]\n const tiersOf = new Map<string, Sound[]>()\n for (const sound of manifest.sounds) {\n const { base, tier } = splitTier(sound.id)\n if (tier) tiersOf.set(base, [...(tiersOf.get(base) ?? []), sound])\n }\n const bases = new Map<string, Sound>()\n for (const sound of manifest.sounds) {\n if (!splitTier(sound.id).tier) bases.set(sound.id, sound)\n }\n for (const [base, tiers] of tiersOf) {\n if (!bases.has(base)) bases.set(base, { id: base, category: tiers[0]?.category ?? '', layers: { close: tiers[0]?.layers.close ?? [] } })\n }\n for (const sound of bases.values()) {\n const numbers = options.sounds?.[sound.id] ?? options.defaults?.(sound)\n const close = refsOf(sound.layers.close, baseUrl)\n if (!numbers || close.length === 0) continue\n const tiers = (tiersOf.get(sound.id) ?? [])\n .map((tier) => {\n const name = splitTier(tier.id).tier as string\n const tierFar = refsOf(tier.layers.far, baseUrl)\n return { name, from: SFX_TIERS[name] ?? 0, close: refsOf(tier.layers.close, baseUrl), ...(tierFar.length > 0 ? { far: tierFar } : {}) }\n })\n .sort((a, b) => a.from - b.from)\n const far = refsOf(sound.layers.far, baseUrl)\n const indoor = refsOf(sound.layers.tail_in, baseUrl)\n const outdoor = refsOf(sound.layers.tail_out, baseUrl)\n const files = [sound, ...(tiersOf.get(sound.id) ?? [])].flatMap((entry) => Object.values(entry.layers).flatMap((entries) => entries ?? []))\n samples.push({\n id: sound.id,\n bus: numbers.bus,\n close,\n ...(far.length > 0 ? { far } : {}),\n ...(tiers.length > 0 ? { tiers } : {}),\n ...(numbers.crossover ? { crossover: numbers.crossover } : {}),\n ...(indoor.length > 0 || outdoor.length > 0\n ? { tail: { ...(indoor.length > 0 ? { indoor } : {}), ...(outdoor.length > 0 ? { outdoor } : {}), ...(numbers.tailGain === undefined ? {} : { gain: numbers.tailGain }) } }\n : {}),\n ...(numbers.pitch === undefined ? {} : { pitch: numbers.pitch }),\n ...(numbers.gain === undefined ? {} : { gain: numbers.gain }),\n ...(numbers.falloff ? { falloff: numbers.falloff } : {}),\n ...(numbers.cutoffK === undefined ? {} : { cutoffK: numbers.cutoffK }),\n ...(numbers.group ? { group: numbers.group } : {}),\n ...(numbers.priority === undefined ? {} : { priority: numbers.priority }),\n ...(numbers.fallback ? { fallback: numbers.fallback } : {}),\n sources: files.map((file) => `${file.file}: ${file.source} ${file.author} ${file.license}`),\n })\n }\n return { id: options.id, samples }\n}\n","/**\n * The kit's own recorded sounds: the files in `assets/sfx/` (CC0 / CC-BY, credited in `assets/sfx/CREDITOS.md`) and the numbers each one plays\n * with. `kitSampleBank` turns the converter's manifest into a bank; `KIT_SAMPLE_ALIASES` lets a recipe id the kit already plays (`shut_wood`,\n * `rattle`...) be answered by the takes of a file id (`door_wood_shut`), so a sound that was synthesized becomes the recording with no change where\n * it is played: the recipe of the same id plays while the files load, and wherever they cannot be fetched (`file://`). `loadKitSamples` fetches the\n * manifest from where the folder is served and registers the bank on an engine. `pointSamples` lets a game play its own files for any id instead.\n */\nimport { sampleBankFromManifest } from './manifest'\nimport type { SampleDefaults, SfxManifest, SfxManifestSound } from './manifest'\nimport type { BusId, SampleRef, SampleSound, SoundBank } from './recipe'\n\n/** The bus of a manifest category (the folder the file sits in). */\nexport const KIT_CATEGORY_BUS: Readonly<Record<string, BusId>> = {\n armas: 'weapons',\n impactos: 'world',\n 'explosoes-fogo': 'world',\n personagem: 'world',\n veiculos: 'world',\n 'itens-portas': 'world',\n ambiente: 'ambience',\n}\n\n/** Voice sounds go on the voices bus whatever folder they are in. */\nconst VOICE_IDS = /^(player_(hurt|death|effort|breath)|scream|zombie_)/\n\n/**\n * Recipe id → file id. The recipe keeps playing until the takes of the file id load. Hand-worked leaves (doors, drawers) of `game-destruction-three`\n * and the hand sounds of items.\n */\nexport const KIT_SAMPLE_ALIASES: Readonly<Record<string, string>> = {\n // a house door: the latch giving, the hinge, the leaf meeting the frame, a locked one shaken; a drawer on its runners\n latch: 'metal_latch',\n creak_wood: 'door_creak',\n shut_wood: 'door_wood_shut',\n rattle: 'door_locked_rattle',\n // a drawer stays on its recipe: the recorded runs (1 to 1.9 s) outlast a drawer's pull\n // an item taken in the hand (game-destruction-three grab_* keys)\n grab_hard: 'item_pickup_generic',\n grab_soft: 'item_pickup_leather',\n // the hand foley of the test room (game-audio FOLEY_BANK) whose id is not a file id: a wooden leaf shut and slammed, an item picked up by material.\n // The metal door stays on its recipe (two of its takes run 10 s), and so do the fridge, the microwave and paper (no file for them).\n door_wood_close: 'door_wood_shut',\n door_wood_slam: 'door_slam_wood',\n item_pickup_metal: 'item_pickup_generic',\n item_pickup_plastic: 'item_pickup_generic',\n item_pickup_glass: 'item_pickup_generic',\n item_pickup_wood: 'item_pickup_generic',\n item_pickup_fabric: 'item_pickup_leather',\n}\n\n/** The numbers of a sound the files do not say: the bus of its folder, a little pitch spread. */\nexport function kitSampleDefaults(sound: { readonly id: string; readonly category: string }): SampleDefaults | undefined {\n const bus = VOICE_IDS.test(sound.id) ? 'voices' : KIT_CATEGORY_BUS[sound.category]\n if (!bus) return undefined\n return { bus, pitch: sound.category === 'ambiente' || /_loop/.test(sound.id) ? 0 : 0.04 }\n}\n\nexport type KitSampleBankOptions = {\n /** Where the `assets/sfx/` folder is served (`./sfx/`). */\n readonly baseUrl?: string\n /** Recipe id → file id (default `KIT_SAMPLE_ALIASES`); `{}` adds none. */\n readonly aliases?: Readonly<Record<string, string>>\n readonly id?: string\n}\n\n/** The manifest with each alias as one more sound, holding the takes of its file id. */\nexport function withAliases(manifest: SfxManifest, aliases: Readonly<Record<string, string>>): SfxManifest {\n const byId = new Map(manifest.sounds.map((sound) => [sound.id, sound]))\n const extra: SfxManifestSound[] = []\n for (const [alias, target] of Object.entries(aliases)) {\n const sound = byId.get(target)\n if (sound && !byId.has(alias)) extra.push({ ...sound, id: alias })\n }\n return { ...manifest, sounds: [...manifest.sounds, ...extra] }\n}\n\n/** The bank of the kit's files. */\nexport function kitSampleBank(manifest: SfxManifest, options: KitSampleBankOptions = {}): SoundBank {\n return sampleBankFromManifest(withAliases(manifest, options.aliases ?? KIT_SAMPLE_ALIASES), {\n id: options.id ?? 'kit_samples',\n baseUrl: options.baseUrl ?? '',\n defaults: kitSampleDefaults,\n })\n}\n\nexport type LoadKitSamplesOptions = KitSampleBankOptions & {\n /** Gets the manifest (default `fetch` of `<baseUrl>manifest.json`). */\n readonly fetchManifest?: (url: string) => Promise<SfxManifest>\n /** Ids to load right away instead of on their first play (the sounds of the first seconds, so they are not the recipe). */\n readonly preload?: readonly string[]\n}\n\ntype Registers = { register(bank: SoundBank): void; loadSamples?(ids?: readonly string[]): Promise<unknown> }\n\n/** True when `url`, read against the page, is a `file://` one: `fetch` refuses that scheme and the browser logs an error for each try. */\nconst isFileUrl = (url: string): boolean => {\n const page = (globalThis as { location?: { href?: string } }).location?.href\n try {\n return new URL(url, page).protocol === 'file:'\n } catch {\n return false\n }\n}\n\nconst fetchJson = async (url: string): Promise<SfxManifest> => {\n // a page opened from disk keeps the recipes without asking: the refused fetch would only put an error in the console\n if (isFileUrl(url)) throw new Error(`${url}: file:// is not fetched`)\n const response = await fetch(url)\n if (!response.ok) throw new Error(`${url}: ${response.status}`)\n return (await response.json()) as SfxManifest\n}\n\n/**\n * Fetches the manifest and registers the bank on the engine. Resolves to the bank, or `null` when the manifest cannot be had (a page opened from\n * `file://`, the folder not served): the recipes keep playing, so that is not an error.\n */\nexport async function loadKitSamples(engine: Registers, options: LoadKitSamplesOptions = {}): Promise<SoundBank | null> {\n const baseUrl = options.baseUrl ?? ''\n let manifest: SfxManifest\n try {\n manifest = await (options.fetchManifest ?? fetchJson)(`${baseUrl}manifest.json`)\n } catch {\n return null\n }\n const bank = kitSampleBank(manifest, options)\n engine.register(bank)\n if (options.preload && engine.loadSamples) void engine.loadSamples(options.preload).catch(() => undefined)\n return bank\n}\n\n/** A game's own files, by sound id: one url or several takes (relative to `baseUrl`). */\nexport type SampleFiles = Readonly<Record<string, string | readonly string[]>>\n\nexport type PointSamplesOptions = {\n /** Put before every url (the game's folder, `./sfx/` say). */\n readonly baseUrl?: string\n /** The bank id (default `game_samples`). */\n readonly id?: string\n /** Bus of an id the kit has no sound for (default `world`). */\n readonly bus?: BusId\n}\n\ntype PointsSamples = { register(bank: SoundBank): void; readonly registry: { sample(id: string): SampleSound | undefined } }\n\n/**\n * Points sound ids at a game's own files: each id keeps the kit's numbers (bus, level, pitch spread, falloff, choke) and plays the game's takes in\n * place of the kit's (its far takes, tiers and room tails included, since they are the kit's files); an id the kit has no sound for is added on\n * `bus`. Call it after the kit's bank (`loadKitSamples`): a later bank wins, so the rest of the kit's sounds stay. For a whole folder laid out like\n * the kit's (`<categoria>/<nome>.ogg` and a `manifest.json`), `loadKitSamples(engine, { baseUrl: '<folder>/', id: 'game_samples' })` does the same.\n */\nexport function pointSamples(engine: PointsSamples, files: SampleFiles, options: PointSamplesOptions = {}): SoundBank {\n const baseUrl = options.baseUrl ?? ''\n const samples = Object.entries(files).map(([id, urls]): SampleSound => {\n const close: SampleRef[] = (typeof urls === 'string' ? [urls] : [...urls]).map((url) => ({ url: `${baseUrl}${url}` }))\n if (close.length === 0) throw new Error(`pointSamples: ${id} has no file`)\n const kit = engine.registry.sample(id)\n if (!kit) return { id, bus: options.bus ?? 'world', close, sources: ['game: pointSamples'] }\n const { far: _far, tiers: _tiers, tail: _tail, ...numbers } = kit\n return { ...numbers, close }\n })\n const bank: SoundBank = { id: options.id ?? 'game_samples', samples }\n engine.register(bank)\n return bank\n}\n"],"mappings":"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","names":[]}