@adatechnology/game-audio 0.5.0 → 0.6.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/assets/sfx/CREDITOS.md +11 -0
- package/assets/sfx/explosoes-fogo/fire_loop_gas_01.ogg +0 -0
- package/assets/sfx/explosoes-fogo/fire_loop_gas_02.ogg +0 -0
- package/assets/sfx/explosoes-fogo/fire_loop_gas_03.ogg +0 -0
- package/assets/sfx/explosoes-fogo/fire_loop_gas_04.ogg +0 -0
- package/assets/sfx/explosoes-fogo/fire_loop_liquid_01.ogg +0 -0
- package/assets/sfx/explosoes-fogo/fire_loop_liquid_02.ogg +0 -0
- package/assets/sfx/explosoes-fogo/fire_loop_liquid_03.ogg +0 -0
- package/assets/sfx/explosoes-fogo/fire_loop_plastic_01.ogg +0 -0
- package/assets/sfx/explosoes-fogo/fire_loop_plastic_02.ogg +0 -0
- package/assets/sfx/explosoes-fogo/fire_loop_plastic_03.ogg +0 -0
- package/assets/sfx/explosoes-fogo/fire_loop_plastic_04.ogg +0 -0
- package/assets/sfx/manifest.json +134 -0
- package/dist/{chunk-7TLFY7VO.js → chunk-KBTUANNG.js} +267 -8
- package/dist/chunk-KBTUANNG.js.map +1 -0
- package/dist/index.d.ts +10 -80
- package/dist/index.global.js +166 -4
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +104 -138
- package/dist/index.js.map +1 -1
- package/dist/{locale-BugSe8dA.d.ts → locale-CJfDpWnI.d.ts} +180 -1
- package/dist/testing.d.ts +7 -5
- package/dist/testing.js +1 -1
- package/package.json +3 -2
- package/dist/chunk-7TLFY7VO.js.map +0 -1
|
@@ -783,6 +783,8 @@ type VoiceStartEvent = {
|
|
|
783
783
|
readonly kind: 'recipe' | 'sample';
|
|
784
784
|
/** The sample sound this recipe stood in for while its files were not loaded. */
|
|
785
785
|
readonly fallbackOf?: string;
|
|
786
|
+
/** The bank the played id is registered in (`kit_samples`, `user_samples`, a recipe bank...). */
|
|
787
|
+
readonly bank?: string;
|
|
786
788
|
readonly requestedAt: number;
|
|
787
789
|
readonly startAt: number;
|
|
788
790
|
readonly outputLatency: number;
|
|
@@ -1011,6 +1013,183 @@ declare function sfxState(entry: SfxEntry, has: {
|
|
|
1011
1013
|
sound(id: string): unknown;
|
|
1012
1014
|
}): SfxState;
|
|
1013
1015
|
|
|
1016
|
+
/**
|
|
1017
|
+
* The manifest the converter (`scripts/sfx-convert.mjs`) writes next to the converted files: every sound id with its takes per layer, the file
|
|
1018
|
+
* facts the criteria check (rate, channels, peak, length) and where each file came from. `sampleBankFromManifest` turns it into a `SoundBank`
|
|
1019
|
+
* of `SampleSound`s, with the bus and the numbers of each sound given by the caller.
|
|
1020
|
+
*/
|
|
1021
|
+
|
|
1022
|
+
/** Licences the SDK accepts (the files are redistributed on npm): public domain or attribution only. */
|
|
1023
|
+
declare const SDK_LICENSES: readonly ["CC0", "CC-BY"];
|
|
1024
|
+
type SdkLicense = (typeof SDK_LICENSES)[number];
|
|
1025
|
+
type SfxFileEntry = {
|
|
1026
|
+
/** Path from the manifest's folder: `armas/pistol_9mm_close_01.ogg`. */
|
|
1027
|
+
readonly file: string;
|
|
1028
|
+
readonly sampleRate: number;
|
|
1029
|
+
readonly channels: number;
|
|
1030
|
+
/** dBFS. */
|
|
1031
|
+
readonly peak: number;
|
|
1032
|
+
readonly seconds: number;
|
|
1033
|
+
readonly source: string;
|
|
1034
|
+
readonly author: string;
|
|
1035
|
+
readonly license: SdkLicense;
|
|
1036
|
+
};
|
|
1037
|
+
/** The layers a file name can end in: `<id>_<layer>_<nn>` (`pistol_9mm_close_01`); a name without one is a `close` take. */
|
|
1038
|
+
declare const SFX_LAYERS: readonly ["close", "far", "tail_in", "tail_out"];
|
|
1039
|
+
type SfxLayer = (typeof SFX_LAYERS)[number];
|
|
1040
|
+
type SfxManifestSound = {
|
|
1041
|
+
readonly id: string;
|
|
1042
|
+
readonly category: string;
|
|
1043
|
+
readonly layers: Partial<Readonly<Record<SfxLayer, readonly SfxFileEntry[]>>>;
|
|
1044
|
+
};
|
|
1045
|
+
type SfxManifest = {
|
|
1046
|
+
readonly version: 1;
|
|
1047
|
+
readonly format: {
|
|
1048
|
+
readonly codec: 'vorbis';
|
|
1049
|
+
readonly sampleRate: number;
|
|
1050
|
+
readonly channels: number;
|
|
1051
|
+
readonly peakDb: number;
|
|
1052
|
+
};
|
|
1053
|
+
readonly sounds: readonly SfxManifestSound[];
|
|
1054
|
+
};
|
|
1055
|
+
/** The numbers of one sound that the files do not say (bus, curve, pitch...). */
|
|
1056
|
+
type SampleDefaults = {
|
|
1057
|
+
readonly bus: BusId;
|
|
1058
|
+
readonly gain?: number;
|
|
1059
|
+
readonly pitch?: number;
|
|
1060
|
+
readonly crossover?: SampleCrossover;
|
|
1061
|
+
readonly tailGain?: number;
|
|
1062
|
+
readonly falloff?: Falloff;
|
|
1063
|
+
readonly cutoffK?: number;
|
|
1064
|
+
readonly group?: string;
|
|
1065
|
+
readonly priority?: number;
|
|
1066
|
+
readonly fallback?: string;
|
|
1067
|
+
};
|
|
1068
|
+
type SampleBankOptions = {
|
|
1069
|
+
readonly id: string;
|
|
1070
|
+
/** Prefix of every url (where the manifest's folder is served: `/sfx/`). Default ''. */
|
|
1071
|
+
readonly baseUrl?: string;
|
|
1072
|
+
/** Numbers per sound id, or one function; a sound with neither is left out (it has no bus). */
|
|
1073
|
+
readonly sounds?: Readonly<Record<string, SampleDefaults>>;
|
|
1074
|
+
readonly defaults?: (sound: {
|
|
1075
|
+
readonly id: string;
|
|
1076
|
+
readonly category: string;
|
|
1077
|
+
}) => SampleDefaults | undefined;
|
|
1078
|
+
};
|
|
1079
|
+
/** The strength suffixes of a sound id (`kick_hit_heavy`): takes of the tiers of `kick_hit`, from these intensities. */
|
|
1080
|
+
declare const SFX_TIERS: Readonly<Record<string, number>>;
|
|
1081
|
+
/** Splits a file name (no extension) into its sound id, layer and take number. */
|
|
1082
|
+
declare function parseSfxName(name: string): {
|
|
1083
|
+
readonly id: string;
|
|
1084
|
+
readonly layer: SfxLayer;
|
|
1085
|
+
readonly take: number;
|
|
1086
|
+
};
|
|
1087
|
+
/** The base id and tier of a sound id: `kick_hit_heavy` → `kick_hit`, `heavy`. */
|
|
1088
|
+
declare function splitTier(id: string): {
|
|
1089
|
+
readonly base: string;
|
|
1090
|
+
readonly tier?: string;
|
|
1091
|
+
};
|
|
1092
|
+
/** What `sampleBankFromManifest` reads of a manifest: the sounds, their layers and where each file came from (any licence string). */
|
|
1093
|
+
type SfxManifestLike = {
|
|
1094
|
+
readonly sounds: readonly {
|
|
1095
|
+
readonly id: string;
|
|
1096
|
+
readonly category: string;
|
|
1097
|
+
readonly layers: Partial<Readonly<Record<SfxLayer, readonly {
|
|
1098
|
+
readonly file: string;
|
|
1099
|
+
readonly source: string;
|
|
1100
|
+
readonly author: string;
|
|
1101
|
+
readonly license: string;
|
|
1102
|
+
}[]>>>;
|
|
1103
|
+
}[];
|
|
1104
|
+
};
|
|
1105
|
+
/** A bank of sample sounds from a manifest. */
|
|
1106
|
+
declare function sampleBankFromManifest(manifest: SfxManifestLike, options: SampleBankOptions): SoundBank;
|
|
1107
|
+
|
|
1108
|
+
declare const USER_SAMPLES_BANK = "user_samples";
|
|
1109
|
+
declare const KIT_SAMPLES_BANK = "kit_samples";
|
|
1110
|
+
/** Prefix of the id each pick is also registered under. */
|
|
1111
|
+
declare const USER_SOUND_PREFIX = "user:";
|
|
1112
|
+
type UserSoundRole = 'primary' | 'fallback';
|
|
1113
|
+
/** One sound the owner's files answer. */
|
|
1114
|
+
type UserSoundPick = {
|
|
1115
|
+
readonly id: string;
|
|
1116
|
+
readonly role: UserSoundRole;
|
|
1117
|
+
/** The kit category (`armas`, `personagem`...): the bus and the folder of the converted files. */
|
|
1118
|
+
readonly category: string;
|
|
1119
|
+
readonly label: string;
|
|
1120
|
+
/** The pack the files come from (a folder of the owner's sounds). */
|
|
1121
|
+
readonly pack: string;
|
|
1122
|
+
/** Paths inside the pack (matched by their end, so the pack may be nested differently). */
|
|
1123
|
+
readonly files: readonly string[];
|
|
1124
|
+
/** 0–10: how well the files fit the sound (rate, clipping, band). */
|
|
1125
|
+
readonly score: number;
|
|
1126
|
+
readonly loop?: boolean;
|
|
1127
|
+
};
|
|
1128
|
+
type UserSoundPicks = {
|
|
1129
|
+
readonly version: 1;
|
|
1130
|
+
readonly author: string;
|
|
1131
|
+
readonly license: string;
|
|
1132
|
+
readonly picks: readonly UserSoundPick[];
|
|
1133
|
+
};
|
|
1134
|
+
/** One converted file, as `sfx:user` writes it. */
|
|
1135
|
+
type UserSfxFile = {
|
|
1136
|
+
readonly file: string;
|
|
1137
|
+
readonly sampleRate: number;
|
|
1138
|
+
readonly channels: number;
|
|
1139
|
+
readonly peak: number;
|
|
1140
|
+
readonly seconds: number;
|
|
1141
|
+
readonly source: string;
|
|
1142
|
+
readonly author: string;
|
|
1143
|
+
readonly license: string;
|
|
1144
|
+
};
|
|
1145
|
+
type UserSfxSound = {
|
|
1146
|
+
readonly id: string;
|
|
1147
|
+
readonly category: string;
|
|
1148
|
+
readonly role: UserSoundRole;
|
|
1149
|
+
readonly label: string;
|
|
1150
|
+
readonly layers: Partial<Readonly<Record<SfxLayer, readonly UserSfxFile[]>>>;
|
|
1151
|
+
};
|
|
1152
|
+
/** The manifest `sfx:user` writes in `.sfx-local/manifest.json`. */
|
|
1153
|
+
type UserSfxManifest = {
|
|
1154
|
+
readonly version: 1;
|
|
1155
|
+
readonly sounds: readonly UserSfxSound[];
|
|
1156
|
+
};
|
|
1157
|
+
/** Where a voice came from: the kit's recording, the owner's file, or the synthesized recipe. */
|
|
1158
|
+
type SoundSource = 'sdk' | 'user' | 'synth';
|
|
1159
|
+
/** The source of a started voice, from its kind and the bank its id is registered in. */
|
|
1160
|
+
declare function soundSourceOf(event: {
|
|
1161
|
+
readonly kind: 'recipe' | 'sample';
|
|
1162
|
+
readonly bank?: string;
|
|
1163
|
+
}): SoundSource;
|
|
1164
|
+
type UserSampleBankOptions = {
|
|
1165
|
+
readonly baseUrl?: string;
|
|
1166
|
+
/** True for an id the kit already plays from a file: a `fallback` pick is then only registered under `user:<id>`. */
|
|
1167
|
+
readonly kitHas?: (id: string) => boolean;
|
|
1168
|
+
};
|
|
1169
|
+
type UserSampleBankResult = {
|
|
1170
|
+
readonly bank: SoundBank;
|
|
1171
|
+
/** Ids the owner's files now answer (primary picks, and fallbacks the kit lacks). */
|
|
1172
|
+
readonly playing: readonly string[];
|
|
1173
|
+
/** Fallback ids the kit answers, kept under `user:<id>`. */
|
|
1174
|
+
readonly behindKit: readonly string[];
|
|
1175
|
+
};
|
|
1176
|
+
/** The bank of the owner's converted files. */
|
|
1177
|
+
declare function userSampleBank(manifest: UserSfxManifest, options?: UserSampleBankOptions): UserSampleBankResult;
|
|
1178
|
+
type LoadUserSamplesOptions = UserSampleBankOptions & {
|
|
1179
|
+
readonly fetchManifest?: (url: string) => Promise<UserSfxManifest>;
|
|
1180
|
+
};
|
|
1181
|
+
type Registers = {
|
|
1182
|
+
register(bank: SoundBank): void;
|
|
1183
|
+
readonly registry?: {
|
|
1184
|
+
sample(id: string): unknown;
|
|
1185
|
+
};
|
|
1186
|
+
};
|
|
1187
|
+
/**
|
|
1188
|
+
* Fetches `<baseUrl>manifest.json` (default `./sfx-local/`) and registers the owner's files. Call it after the kit's bank loaded, so a fallback
|
|
1189
|
+
* pick stays behind the kit's recording. Resolves to `null` when the folder is not there (a machine without the owner's sounds): nothing changes.
|
|
1190
|
+
*/
|
|
1191
|
+
declare function loadUserSamples(engine: Registers, options?: LoadUserSamplesOptions): Promise<UserSampleBankResult | null>;
|
|
1192
|
+
|
|
1014
1193
|
/**
|
|
1015
1194
|
* Two probe sounds of the bench itself (a ping and a hiss), so panning, distance and the seed can be heard on something that has no subject. They are
|
|
1016
1195
|
* made by the kit, not delivered by any team (`sdk` in the sources).
|
|
@@ -1068,4 +1247,4 @@ type AudioBenchText = {
|
|
|
1068
1247
|
};
|
|
1069
1248
|
declare const audioBenchTextOf: (locale: string) => AudioBenchText;
|
|
1070
1249
|
|
|
1071
|
-
export {
|
|
1250
|
+
export { type FormantVoiceSource as $, type AudioLoopHandle as A, type BusId as B, CRITERIA as C, CRITERIA_TARGETS as D, type CriterionId as E, type Falloff as F, type CriterionResult as G, type Curve as H, DEFAULT_CROSSOVER as I, DEFAULT_LISTENER as J, DEFAULT_MAX_VOICES as K, type LoopRecipe as L, type MixProfile as M, DEFAULT_MIX as N, DEFAULT_SAMPLE_CUTOFF_K as O, type ParamRef as P, DEFAULT_SAMPLE_FALLOFF as Q, type Range as R, type SampleSound as S, DEFAULT_SAMPLE_PITCH as T, DEFAULT_TAIL_GAIN as U, type DeepPartial as V, type DistanceMode as W, type Envelope as X, type EnvelopePoint as Y, type FilterBank as Z, type FilterSpec as _, type SampleLoader as a, type UserSampleBankOptions as a$, KIT_SAMPLES_BANK as a0, LAYER_FLOOR as a1, type LayerSource as a2, type Listener as a3, type LoadUserSamplesOptions as a4, type LoopOrder as a5, type LoopSet as a6, MIX_PROFILES as a7, type MeasureOptions as a8, type Modulator as a9, type SampleLayerRole as aA, type SampleLoadReport as aB, type SampleMemory as aC, type SampleOptions as aD, type SamplePlanInput as aE, type SampleRef as aF, type SampleStatus as aG, type SampleStore as aH, type SampleTail as aI, type SampleTier as aJ, type SdkLicense as aK, type SfxBlow as aL, type SfxCategory as aM, type SfxEntry as aN, type SfxFileEntry as aO, type SfxLayer as aP, type SfxManifestLike as aQ, type SfxManifestSound as aR, type SfxMaterial as aS, type SfxPriority as aT, type SfxState as aU, type SoundRegistry as aV, type SoundSource as aW, type StartQueueOptions as aX, type Time as aY, USER_SAMPLES_BANK as aZ, USER_SOUND_PREFIX as a_, type NoiseSource as aa, OPEN_CUTOFF as ab, type OscSource as ac, PAN_LIMIT as ad, PAN_MIN_DISTANCE as ae, type PlanInput as af, type PlannedAutomation as ag, type PlannedEnvelopePoint as ah, type PlannedFilter as ai, type PlannedLayer as aj, type PlannedModulator as ak, type PlannedSampleLayer as al, type PlannedSource as am, type PlayOptions as an, SAMPLE_ENVIRONMENTS as ao, SDK_LICENSES as ap, SFX_BLOWS as aq, SFX_CATALOG as ar, SFX_CATEGORIES as as, SFX_IMPACTS as at, SFX_LAYERS as au, SFX_MATERIALS as av, SFX_TIERS as aw, type SampleBankOptions as ax, type SampleCrossover as ay, type SampleEnvironment as az, type SfxManifest as b, type UserSampleBankResult as b0, type UserSfxFile as b1, type UserSfxManifest as b2, type UserSfxSound as b3, type UserSoundPick as b4, type UserSoundPicks as b5, type UserSoundRole as b6, VOICE_GROUPS as b7, type VoiceDecision as b8, type VoiceGroup as b9, pickTake as bA, planSample as bB, planSound as bC, recordOutput as bD, resolveMix as bE, sampleBankFromManifest as bF, sampleCutoff as bG, sampleUrls as bH, sfxState as bI, soundSourceOf as bJ, splitTier as bK, tierFor as bL, userSampleBank as bM, type VoiceHandle as ba, type VoiceManager as bb, type VoiceManagerOptions as bc, type VoiceRefusal as bd, type VoiceRequest as be, type VoiceStartEvent as bf, type VoiceStats as bg, audioBenchTextOf as bh, busGain as bi, busPriority as bj, createAudioEngine as bk, createAudioTestModule as bl, createSampleStore as bm, createSoundRegistry as bn, createVoiceManager as bo, crossGains as bp, farMixAt as bq, fetchLoader as br, impactSoundId as bs, inlineLoader as bt, limiterCurve as bu, listenerDistance as bv, loadUserSamples as bw, measureCriteria as bx, panFor as by, parseSfxName as bz, type SoundBank as c, type SampleDefaults as d, type SoundRecipe as e, type Layer as f, type SoundSet as g, type SoundPlan as h, type SamplePlan as i, type LoopEntry as j, type AudioEngine as k, type ActionCheck as l, type AudioBenchText as m, type AudioChannel as n, type AudioEngineOptions as o, type AudioScenarioLike as p, type AudioStats as q, type AudioTestModule as r, type AudioTestModuleOptions as s, type Automation as t, BENCH_BANK as u, BUILD_ORDERS as v, BUS_IDS as w, BUS_PRIORITY as x, type BuildOrder as y, type BusSetting as z };
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { aL as SfxBlow, aS as SfxMaterial, az as SampleEnvironment, aU as SfxState, E as CriterionId, aW as SoundSource, p as AudioScenarioLike, k as AudioEngine, G as CriterionResult, bf as VoiceStartEvent, ba as VoiceHandle, c as SoundBank, a as SampleLoader, aN as SfxEntry, l as ActionCheck } from './locale-CJfDpWnI.js';
|
|
2
|
+
export { m as AudioBenchText, r as AudioTestModule, s as AudioTestModuleOptions, u as BENCH_BANK, bh as audioBenchTextOf, bl as createAudioTestModule } from './locale-CJfDpWnI.js';
|
|
3
3
|
import { TestableModule } from '@adatechnology/game-contracts';
|
|
4
4
|
|
|
5
5
|
type Strength = 'light' | 'medium' | 'heavy';
|
|
6
6
|
type SoundCheckText = {
|
|
7
7
|
readonly group: string;
|
|
8
8
|
readonly name: string;
|
|
9
|
-
readonly controls: Readonly<Record<'category' | 'sound' | 'play' | 'material' | 'blow' | 'strike' | 'distance' | 'bearing' | 'environment' | 'strength' | 'wall' | 'burst' | 'play_a' | 'play_b' | 'ab_cycle' | 'blind' | 'reveal' | 'prefer_a' | 'prefer_b' | 'measure' | 'record' | 'approve', string>>;
|
|
10
|
-
readonly hints: Readonly<Record<'sound' | 'strike' | 'wall' | 'burst' | 'ab_cycle' | 'blind' | 'measure' | 'record' | 'approve', string>>;
|
|
9
|
+
readonly controls: Readonly<Record<'category' | 'sound' | 'play' | 'play_user' | 'material' | 'blow' | 'strike' | 'distance' | 'bearing' | 'environment' | 'strength' | 'wall' | 'burst' | 'play_a' | 'play_b' | 'ab_cycle' | 'blind' | 'reveal' | 'prefer_a' | 'prefer_b' | 'measure' | 'record' | 'approve', string>>;
|
|
10
|
+
readonly hints: Readonly<Record<'play_user' | 'sound' | 'strike' | 'wall' | 'burst' | 'ab_cycle' | 'blind' | 'measure' | 'record' | 'approve', string>>;
|
|
11
11
|
readonly categories: Readonly<Record<string, string>>;
|
|
12
12
|
readonly blows: Readonly<Record<SfxBlow, string>>;
|
|
13
13
|
readonly materials: Readonly<Record<SfxMaterial, string>>;
|
|
@@ -15,9 +15,11 @@ type SoundCheckText = {
|
|
|
15
15
|
readonly strengths: Readonly<Record<Strength, string>>;
|
|
16
16
|
readonly states: Readonly<Record<SfxState, string>>;
|
|
17
17
|
readonly criteria: Readonly<Record<CriterionId, string>>;
|
|
18
|
-
readonly metrics: Readonly<Record<'chosen' | 'matrix' | 'last' | 'ab' | 'blind' | 'verdicts' | 'state' | 'count', string>>;
|
|
18
|
+
readonly metrics: Readonly<Record<'source' | 'chosen' | 'matrix' | 'last' | 'ab' | 'blind' | 'verdicts' | 'state' | 'count', string>>;
|
|
19
19
|
readonly run: Readonly<Record<'idle' | 'measuring' | 'done' | 'failed' | 'recording' | 'recorded', string>>;
|
|
20
20
|
readonly words: Readonly<Record<'none' | 'hidden' | 'prefers' | 'approved', string>>;
|
|
21
|
+
/** Where the voice came from: the kit's file, the owner's file, the synthesized recipe. */
|
|
22
|
+
readonly sources: Readonly<Record<SoundSource, string>>;
|
|
21
23
|
readonly probe: string;
|
|
22
24
|
readonly wall: string;
|
|
23
25
|
readonly scenarios: Readonly<Record<'pipeline' | 'criteria' | 'loop' | 'panel', string>>;
|
package/dist/testing.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@adatechnology/game-audio",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Sound as data for games: recipes planned into resolved, seeded sound plans, a WebAudio layer that builds them, distance curves, pan, a voice limiter, loops and mixing profiles taken from the teams (the game and the art scene). Sounds from recorded files too (.ogg takes with round-robin, pitch, near/far layers, indoor/outdoor tails and strength tiers), a converter for the files the team drops in, and a measuring harness of the sound criteria. Knows nothing about any game.",
|
|
6
6
|
"keywords": [
|
|
@@ -57,6 +57,7 @@
|
|
|
57
57
|
"build": "tsup",
|
|
58
58
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
59
59
|
"test": "vitest run",
|
|
60
|
-
"sfx:convert": "node scripts/sfx-convert.mjs"
|
|
60
|
+
"sfx:convert": "node scripts/sfx-convert.mjs",
|
|
61
|
+
"sfx:user": "node scripts/sfx-user.mjs"
|
|
61
62
|
}
|
|
62
63
|
}
|