@prosopo/audio-assets 0.0.0-stage → 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/.turbo/turbo-build$colon$cjs.log +23 -0
- package/.turbo/turbo-build$colon$tsc.log +14 -0
- package/.turbo/turbo-build.log +25 -0
- package/CHANGELOG.md +19 -0
- package/LICENSE +201 -0
- package/dist/_virtual/_rolldown/runtime.js +3 -0
- package/dist/cjs/distort.cjs +91 -0
- package/dist/cjs/encode.cjs +40 -0
- package/dist/cjs/index.cjs +109 -0
- package/dist/cjs/levels.cjs +18 -0
- package/dist/cjs/phonemes.cjs +217 -0
- package/dist/cjs/synth.cjs +222 -0
- package/dist/distort.d.ts +7 -0
- package/dist/distort.d.ts.map +1 -0
- package/dist/distort.js +88 -0
- package/dist/distort.js.map +1 -0
- package/dist/encode.d.ts +4 -0
- package/dist/encode.d.ts.map +1 -0
- package/dist/encode.js +39 -0
- package/dist/encode.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +84 -0
- package/dist/index.js.map +1 -0
- package/dist/levels.d.ts +3 -0
- package/dist/levels.d.ts.map +1 -0
- package/dist/levels.js +17 -0
- package/dist/levels.js.map +1 -0
- package/dist/phonemes.d.ts +4 -0
- package/dist/phonemes.d.ts.map +1 -0
- package/dist/phonemes.js +216 -0
- package/dist/phonemes.js.map +1 -0
- package/dist/synth.d.ts +13 -0
- package/dist/synth.d.ts.map +1 -0
- package/dist/synth.js +220 -0
- package/dist/synth.js.map +1 -0
- package/dist/tests/audioAssets.unit.test.d.ts +2 -0
- package/dist/tests/audioAssets.unit.test.d.ts.map +1 -0
- package/dist/tests/audioAssets.unit.test.js +220 -0
- package/dist/tests/audioAssets.unit.test.js.map +1 -0
- package/dist/types.d.ts +35 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -3
- package/scripts/preview.ts +141 -0
- package/vite.cjs.config.ts +23 -0
- package/vite.esm.config.ts +20 -0
- package/vite.test.config.ts +32 -0
- package/README.md +0 -3
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import { describe, expect, test } from "vitest";
|
|
2
|
+
import { ANSWER_ALPHABET, DEFAULT_RENDER_SETTINGS, DIGITS, SAMPLE_RATE, addNoiseBed, createPrng, createSeed, encodeWav, randomVoice, renderAudioChallenge, rms, synthesiseUtterance, toDataUri, } from "../index.js";
|
|
3
|
+
const cleanSettings = (overrides = {}) => ({
|
|
4
|
+
...DEFAULT_RENDER_SETTINGS,
|
|
5
|
+
noiseSnrDb: 60,
|
|
6
|
+
babbleGain: 0,
|
|
7
|
+
babbleVoices: 0,
|
|
8
|
+
reverbMix: 0,
|
|
9
|
+
...overrides,
|
|
10
|
+
});
|
|
11
|
+
const decodeWav = (wav) => {
|
|
12
|
+
const dataBytes = wav.readUInt32LE(40);
|
|
13
|
+
const out = new Float32Array(dataBytes / 2);
|
|
14
|
+
for (let n = 0; n < out.length; n++) {
|
|
15
|
+
out[n] = wav.readInt16LE(44 + n * 2) / 0x7fff;
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
};
|
|
19
|
+
describe("phoneme table", () => {
|
|
20
|
+
test("covers every decimal digit exactly once", () => {
|
|
21
|
+
const answers = DIGITS.map((d) => d.answer);
|
|
22
|
+
expect(answers.sort()).toEqual([
|
|
23
|
+
"0",
|
|
24
|
+
"1",
|
|
25
|
+
"2",
|
|
26
|
+
"3",
|
|
27
|
+
"4",
|
|
28
|
+
"5",
|
|
29
|
+
"6",
|
|
30
|
+
"7",
|
|
31
|
+
"8",
|
|
32
|
+
"9",
|
|
33
|
+
]);
|
|
34
|
+
expect(new Set(answers).size).toBe(DIGITS.length);
|
|
35
|
+
});
|
|
36
|
+
test("every digit has at least one phoneme and a positive duration", () => {
|
|
37
|
+
for (const digit of DIGITS) {
|
|
38
|
+
expect(digit.phonemes.length).toBeGreaterThan(0);
|
|
39
|
+
const total = digit.phonemes.reduce((sum, p) => sum + p.durationMs, 0);
|
|
40
|
+
expect(total).toBeGreaterThan(0);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
test("ANSWER_ALPHABET matches the table", () => {
|
|
44
|
+
for (const digit of DIGITS) {
|
|
45
|
+
expect(ANSWER_ALPHABET).toContain(digit.answer);
|
|
46
|
+
}
|
|
47
|
+
expect(ANSWER_ALPHABET.length).toBe(DIGITS.length);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
describe("synthesiseUtterance", () => {
|
|
51
|
+
test("produces audible, finite, in-range samples", () => {
|
|
52
|
+
const prng = createPrng(createSeed());
|
|
53
|
+
const voice = randomVoice(prng);
|
|
54
|
+
for (const digit of DIGITS) {
|
|
55
|
+
const buffer = synthesiseUtterance(digit, voice, prng, SAMPLE_RATE);
|
|
56
|
+
expect(buffer.sampleRate).toBe(SAMPLE_RATE);
|
|
57
|
+
expect(buffer.samples.length).toBeGreaterThan(0);
|
|
58
|
+
for (const sample of buffer.samples) {
|
|
59
|
+
expect(Number.isFinite(sample)).toBe(true);
|
|
60
|
+
expect(Math.abs(sample)).toBeLessThanOrEqual(1.0001);
|
|
61
|
+
}
|
|
62
|
+
expect(rms(buffer.samples)).toBeGreaterThan(0.01);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
test("digit durations sit in a plausible speech range", () => {
|
|
66
|
+
const prng = createPrng(createSeed());
|
|
67
|
+
const voice = randomVoice(prng);
|
|
68
|
+
for (const digit of DIGITS) {
|
|
69
|
+
const buffer = synthesiseUtterance(digit, voice, prng, SAMPLE_RATE);
|
|
70
|
+
const ms = (buffer.samples.length / SAMPLE_RATE) * 1000;
|
|
71
|
+
expect(ms).toBeGreaterThan(120);
|
|
72
|
+
expect(ms).toBeLessThan(900);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
test("the same seed reproduces the same audio", () => {
|
|
76
|
+
const seed = createSeed();
|
|
77
|
+
const render = () => {
|
|
78
|
+
const prng = createPrng(seed);
|
|
79
|
+
const voice = randomVoice(prng);
|
|
80
|
+
const first = DIGITS[0];
|
|
81
|
+
if (!first)
|
|
82
|
+
throw new Error("empty digit table");
|
|
83
|
+
return synthesiseUtterance(first, voice, prng, SAMPLE_RATE).samples;
|
|
84
|
+
};
|
|
85
|
+
expect(Array.from(render())).toEqual(Array.from(render()));
|
|
86
|
+
});
|
|
87
|
+
test("different seeds produce different audio", () => {
|
|
88
|
+
const render = () => {
|
|
89
|
+
const prng = createPrng(createSeed());
|
|
90
|
+
const voice = randomVoice(prng);
|
|
91
|
+
const first = DIGITS[0];
|
|
92
|
+
if (!first)
|
|
93
|
+
throw new Error("empty digit table");
|
|
94
|
+
return synthesiseUtterance(first, voice, prng, SAMPLE_RATE).samples;
|
|
95
|
+
};
|
|
96
|
+
expect(Array.from(render())).not.toEqual(Array.from(render()));
|
|
97
|
+
});
|
|
98
|
+
test("starts and ends near silence so there is no click", () => {
|
|
99
|
+
const prng = createPrng(createSeed());
|
|
100
|
+
const voice = randomVoice(prng);
|
|
101
|
+
const digit = DIGITS[5];
|
|
102
|
+
if (!digit)
|
|
103
|
+
throw new Error("missing digit");
|
|
104
|
+
const { samples } = synthesiseUtterance(digit, voice, prng, SAMPLE_RATE);
|
|
105
|
+
expect(Math.abs(samples[0] ?? 1)).toBeLessThan(0.02);
|
|
106
|
+
expect(Math.abs(samples[samples.length - 1] ?? 1)).toBeLessThan(0.02);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
describe("renderAudioChallenge", () => {
|
|
110
|
+
test("answer length matches digitCount and uses only the alphabet", () => {
|
|
111
|
+
for (const digitCount of [3, 5, 8]) {
|
|
112
|
+
const challenge = renderAudioChallenge(cleanSettings({ digitCount }));
|
|
113
|
+
expect(challenge.answer).toHaveLength(digitCount);
|
|
114
|
+
for (const character of challenge.answer) {
|
|
115
|
+
expect(ANSWER_ALPHABET).toContain(character);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
test("emits a valid RIFF/WAVE header the reported length agrees with", () => {
|
|
120
|
+
const challenge = renderAudioChallenge(cleanSettings());
|
|
121
|
+
const { wav } = challenge;
|
|
122
|
+
expect(wav.subarray(0, 4).toString("ascii")).toBe("RIFF");
|
|
123
|
+
expect(wav.subarray(8, 12).toString("ascii")).toBe("WAVE");
|
|
124
|
+
expect(wav.subarray(12, 16).toString("ascii")).toBe("fmt ");
|
|
125
|
+
expect(wav.readUInt16LE(20)).toBe(1);
|
|
126
|
+
expect(wav.readUInt16LE(22)).toBe(1);
|
|
127
|
+
expect(wav.readUInt32LE(24)).toBe(SAMPLE_RATE);
|
|
128
|
+
expect(wav.readUInt16LE(34)).toBe(16);
|
|
129
|
+
expect(wav.subarray(36, 40).toString("ascii")).toBe("data");
|
|
130
|
+
const dataBytes = wav.readUInt32LE(40);
|
|
131
|
+
expect(wav.length).toBe(44 + dataBytes);
|
|
132
|
+
expect(wav.readUInt32LE(4)).toBe(36 + dataBytes);
|
|
133
|
+
const durationMs = (dataBytes / 2 / SAMPLE_RATE) * 1000;
|
|
134
|
+
expect(Math.abs(durationMs - challenge.durationMs)).toBeLessThan(2);
|
|
135
|
+
});
|
|
136
|
+
test("clip is long enough to hold every digit but stays reasonable", () => {
|
|
137
|
+
const challenge = renderAudioChallenge(cleanSettings({ digitCount: 5 }));
|
|
138
|
+
expect(challenge.durationMs).toBeGreaterThan(1500);
|
|
139
|
+
expect(challenge.durationMs).toBeLessThan(12000);
|
|
140
|
+
});
|
|
141
|
+
test("consecutive challenges differ in both answer and audio", () => {
|
|
142
|
+
const answers = new Set();
|
|
143
|
+
const digests = new Set();
|
|
144
|
+
for (let i = 0; i < 12; i++) {
|
|
145
|
+
const challenge = renderAudioChallenge(cleanSettings());
|
|
146
|
+
answers.add(challenge.answer);
|
|
147
|
+
digests.add(challenge.wav.toString("base64").slice(0, 256));
|
|
148
|
+
}
|
|
149
|
+
expect(answers.size).toBeGreaterThan(1);
|
|
150
|
+
expect(digests.size).toBe(12);
|
|
151
|
+
});
|
|
152
|
+
test("does not clip: no sample is pinned at the rail", () => {
|
|
153
|
+
const challenge = renderAudioChallenge(DEFAULT_RENDER_SETTINGS);
|
|
154
|
+
const samples = decodeWav(challenge.wav);
|
|
155
|
+
let pinned = 0;
|
|
156
|
+
for (const sample of samples) {
|
|
157
|
+
if (Math.abs(sample) >= 0.999)
|
|
158
|
+
pinned++;
|
|
159
|
+
}
|
|
160
|
+
expect(pinned).toBeLessThan(samples.length * 0.001);
|
|
161
|
+
});
|
|
162
|
+
test("babble adds energy without swamping the foreground", () => {
|
|
163
|
+
const withoutBabble = renderAudioChallenge(cleanSettings({ digitCount: 4 }));
|
|
164
|
+
const withBabble = renderAudioChallenge(cleanSettings({ digitCount: 4, babbleGain: 0.2, babbleVoices: 2 }));
|
|
165
|
+
expect(rms(decodeWav(withBabble.wav))).toBeGreaterThan(rms(decodeWav(withoutBabble.wav)) * 0.5);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
describe("addNoiseBed", () => {
|
|
169
|
+
test("hits the requested SNR", () => {
|
|
170
|
+
for (const snrDb of [6, 14, 24]) {
|
|
171
|
+
const prng = createPrng(createSeed());
|
|
172
|
+
const signal = new Float32Array(SAMPLE_RATE);
|
|
173
|
+
for (let n = 0; n < signal.length; n++) {
|
|
174
|
+
signal[n] = 0.5 * Math.sin((2 * Math.PI * 440 * n) / SAMPLE_RATE);
|
|
175
|
+
}
|
|
176
|
+
const clean = Float32Array.from(signal);
|
|
177
|
+
const before = rms(clean);
|
|
178
|
+
const buffer = { samples: signal, sampleRate: SAMPLE_RATE };
|
|
179
|
+
addNoiseBed(buffer, prng, snrDb);
|
|
180
|
+
const noise = new Float32Array(clean.length);
|
|
181
|
+
for (let n = 0; n < noise.length; n++) {
|
|
182
|
+
noise[n] = (buffer.samples[n] ?? 0) - (clean[n] ?? 0);
|
|
183
|
+
}
|
|
184
|
+
const measured = 20 * Math.log10(before / rms(noise));
|
|
185
|
+
expect(Math.abs(measured - snrDb)).toBeLessThan(0.5);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
test("leaves silence alone rather than dividing by zero", () => {
|
|
189
|
+
const prng = createPrng(createSeed());
|
|
190
|
+
const buffer = {
|
|
191
|
+
samples: new Float32Array(1000),
|
|
192
|
+
sampleRate: SAMPLE_RATE,
|
|
193
|
+
};
|
|
194
|
+
addNoiseBed(buffer, prng, 10);
|
|
195
|
+
expect(rms(buffer.samples)).toBe(0);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
describe("encodeWav", () => {
|
|
199
|
+
test("round-trips samples within 16-bit quantisation error", () => {
|
|
200
|
+
const samples = new Float32Array([0, 0.5, -0.5, 0.999, -0.999]);
|
|
201
|
+
const decoded = decodeWav(encodeWav({ samples, sampleRate: SAMPLE_RATE }));
|
|
202
|
+
for (let n = 0; n < samples.length; n++) {
|
|
203
|
+
expect(Math.abs((decoded[n] ?? 0) - (samples[n] ?? 0))).toBeLessThan(1 / 32767);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
test("saturates out-of-range samples instead of wrapping", () => {
|
|
207
|
+
const samples = new Float32Array([2, -2]);
|
|
208
|
+
const decoded = decodeWav(encodeWav({ samples, sampleRate: SAMPLE_RATE }));
|
|
209
|
+
expect(decoded[0]).toBeGreaterThan(0.99);
|
|
210
|
+
expect(decoded[1]).toBeLessThan(-0.99);
|
|
211
|
+
});
|
|
212
|
+
test("toDataUri produces a playable audio/wav URI", () => {
|
|
213
|
+
const challenge = renderAudioChallenge(cleanSettings({ digitCount: 2 }));
|
|
214
|
+
const uri = toDataUri(challenge.wav);
|
|
215
|
+
expect(uri.startsWith("data:audio/wav;base64,")).toBe(true);
|
|
216
|
+
const decoded = Buffer.from(uri.split(",")[1] ?? "", "base64");
|
|
217
|
+
expect(decoded.equals(challenge.wav)).toBe(true);
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
//# sourceMappingURL=audioAssets.unit.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audioAssets.unit.test.js","sourceRoot":"","sources":["../../src/tests/audioAssets.unit.test.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export interface AudioBuffer {
|
|
2
|
+
samples: Float32Array;
|
|
3
|
+
sampleRate: number;
|
|
4
|
+
}
|
|
5
|
+
export type Excitation = "voiced" | "unvoiced" | "mixed" | "silence";
|
|
6
|
+
export interface Phoneme {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly excitation: Excitation;
|
|
9
|
+
readonly formants: readonly [number, number, number];
|
|
10
|
+
readonly bandwidths: readonly [number, number, number];
|
|
11
|
+
readonly durationMs: number;
|
|
12
|
+
readonly voiceGain: number;
|
|
13
|
+
readonly noiseGain: number;
|
|
14
|
+
readonly noiseCentreHz: number;
|
|
15
|
+
readonly noiseBandwidthHz: number;
|
|
16
|
+
readonly abrupt?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface Utterance {
|
|
19
|
+
readonly answer: string;
|
|
20
|
+
readonly phonemes: readonly Phoneme[];
|
|
21
|
+
}
|
|
22
|
+
export interface AudioRenderSettings {
|
|
23
|
+
digitCount: number;
|
|
24
|
+
noiseSnrDb: number;
|
|
25
|
+
babbleGain: number;
|
|
26
|
+
babbleVoices: number;
|
|
27
|
+
reverbMix: number;
|
|
28
|
+
gapMs: number;
|
|
29
|
+
}
|
|
30
|
+
export interface RenderedAudioChallenge {
|
|
31
|
+
wav: Buffer;
|
|
32
|
+
answer: string;
|
|
33
|
+
durationMs: number;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAeA,MAAM,WAAW,WAAW;IAC3B,OAAO,EAAE,YAAY,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;CACnB;AAMD,MAAM,MAAM,UAAU,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAC;AAOrE,MAAM,WAAW,OAAO;IAEvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAEhC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IACrD,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;IAEvD,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAElC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,SAAS;IAEzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,QAAQ,EAAE,SAAS,OAAO,EAAE,CAAC;CACtC;AAGD,MAAM,WAAW,mBAAmB;IACnC,UAAU,EAAE,MAAM,CAAC;IAEnB,UAAU,EAAE,MAAM,CAAC;IAEnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAElB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,sBAAsB;IAEtC,GAAG,EAAE,MAAM,CAAC;IAEZ,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;CACnB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,55 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prosopo/audio-assets",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Procedural speech synthesis for the audio captcha",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": "^24"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"require": "./dist/cjs/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/prosopo/captcha.git",
|
|
21
|
+
"directory": "packages/audio-assets"
|
|
22
|
+
},
|
|
23
|
+
"author": "Prosopo Limited",
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/prosopo/captcha/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/prosopo/captcha#readme",
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@prosopo/puzzle-assets": "0.2.1"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@prosopo/config": "3.4.3",
|
|
34
|
+
"@types/node": "22.10.2",
|
|
35
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
36
|
+
"del-cli": "6.0.0",
|
|
37
|
+
"dotenv": "16.4.5",
|
|
38
|
+
"tslib": "2.7.0",
|
|
39
|
+
"tsx": "4.20.3",
|
|
40
|
+
"typescript": "5.6.2",
|
|
41
|
+
"vite": "8.1.5",
|
|
42
|
+
"vitest": "4.1.11"
|
|
43
|
+
},
|
|
44
|
+
"sideEffects": false,
|
|
45
|
+
"scripts": {
|
|
46
|
+
"clean": "del-cli --verbose dist tsconfig.tsbuildinfo",
|
|
47
|
+
"build": "pnpm run build:cross-env --mode ${NODE_ENV:-development}",
|
|
48
|
+
"build:cross-env": "vite build --config vite.esm.config.ts",
|
|
49
|
+
"build:tsc": "tsc --build --verbose",
|
|
50
|
+
"build:cjs": "NODE_ENV=${NODE_ENV:-development}; vite build --config vite.cjs.config.ts --mode $NODE_ENV",
|
|
51
|
+
"preview": "tsx scripts/preview.ts",
|
|
52
|
+
"test": "NODE_ENV=${NODE_ENV:-test}; pnpm exec vitest run --config ./vite.test.config.ts",
|
|
53
|
+
"typecheck": "tsc --project tsconfig.types.json"
|
|
54
|
+
}
|
|
6
55
|
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Intelligibility needs ears, not a unit test. Writes challenges, per-digit
|
|
17
|
+
* references and an HTML page with the answers behind a reveal button.
|
|
18
|
+
* `--clean` turns off noise, babble and reverb to judge the synthesiser alone.
|
|
19
|
+
*
|
|
20
|
+
* pnpm --filter @prosopo/audio-assets run preview --count 12 --out ./preview
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
24
|
+
import { join, resolve } from "node:path";
|
|
25
|
+
import {
|
|
26
|
+
type AudioRenderSettings,
|
|
27
|
+
DEFAULT_RENDER_SETTINGS,
|
|
28
|
+
DIGITS,
|
|
29
|
+
SAMPLE_RATE,
|
|
30
|
+
createPrng,
|
|
31
|
+
createSeed,
|
|
32
|
+
encodeWav,
|
|
33
|
+
randomVoice,
|
|
34
|
+
renderAudioChallenge,
|
|
35
|
+
synthesiseUtterance,
|
|
36
|
+
} from "../src/index.js";
|
|
37
|
+
|
|
38
|
+
interface Args {
|
|
39
|
+
count: number;
|
|
40
|
+
out: string;
|
|
41
|
+
clean: boolean;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const parseArgs = (argv: readonly string[]): Args => {
|
|
45
|
+
const args: Args = { count: 12, out: "./preview", clean: false };
|
|
46
|
+
for (let i = 0; i < argv.length; i++) {
|
|
47
|
+
const flag = argv[i];
|
|
48
|
+
if (flag === "--count") {
|
|
49
|
+
const value = argv[++i];
|
|
50
|
+
if (value) args.count = Number.parseInt(value, 10);
|
|
51
|
+
} else if (flag === "--out") {
|
|
52
|
+
const value = argv[++i];
|
|
53
|
+
if (value) args.out = value;
|
|
54
|
+
} else if (flag === "--clean") {
|
|
55
|
+
args.clean = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return args;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const main = async (): Promise<void> => {
|
|
62
|
+
const args = parseArgs(process.argv.slice(2));
|
|
63
|
+
const outDir = resolve(args.out);
|
|
64
|
+
mkdirSync(outDir, { recursive: true });
|
|
65
|
+
|
|
66
|
+
const settings: AudioRenderSettings = args.clean
|
|
67
|
+
? {
|
|
68
|
+
...DEFAULT_RENDER_SETTINGS,
|
|
69
|
+
noiseSnrDb: 60,
|
|
70
|
+
babbleGain: 0,
|
|
71
|
+
babbleVoices: 0,
|
|
72
|
+
reverbMix: 0,
|
|
73
|
+
}
|
|
74
|
+
: DEFAULT_RENDER_SETTINGS;
|
|
75
|
+
|
|
76
|
+
const prng = createPrng(createSeed());
|
|
77
|
+
const referenceVoice = randomVoice(prng);
|
|
78
|
+
const references: string[] = [];
|
|
79
|
+
for (const digit of DIGITS) {
|
|
80
|
+
const buffer = synthesiseUtterance(
|
|
81
|
+
digit,
|
|
82
|
+
referenceVoice,
|
|
83
|
+
prng,
|
|
84
|
+
SAMPLE_RATE,
|
|
85
|
+
);
|
|
86
|
+
const name = `digit-${digit.answer}.wav`;
|
|
87
|
+
writeFileSync(join(outDir, name), encodeWav(buffer));
|
|
88
|
+
references.push(
|
|
89
|
+
`<figure><figcaption>${digit.answer}</figcaption><audio controls src="${name}"></audio></figure>`,
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const challenges: string[] = [];
|
|
94
|
+
for (let i = 0; i < args.count; i++) {
|
|
95
|
+
const challenge = renderAudioChallenge(settings);
|
|
96
|
+
const name = `challenge-${String(i).padStart(2, "0")}.wav`;
|
|
97
|
+
writeFileSync(join(outDir, name), challenge.wav);
|
|
98
|
+
challenges.push(
|
|
99
|
+
`<figure>
|
|
100
|
+
<audio controls src="${name}"></audio>
|
|
101
|
+
<figcaption>
|
|
102
|
+
<button type="button" data-answer="${challenge.answer}">reveal</button>
|
|
103
|
+
<span>${(challenge.wav.length / 1024).toFixed(0)} KB · ${(challenge.durationMs / 1000).toFixed(1)}s</span>
|
|
104
|
+
</figcaption>
|
|
105
|
+
</figure>`,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const html = `<!doctype html>
|
|
110
|
+
<meta charset="utf-8">
|
|
111
|
+
<title>audio-assets preview${args.clean ? " (clean)" : ""}</title>
|
|
112
|
+
<style>
|
|
113
|
+
body { font: 15px/1.5 system-ui, sans-serif; margin: 2rem; max-width: 60rem; }
|
|
114
|
+
h2 { margin-top: 2rem; }
|
|
115
|
+
.grid { display: grid; gap: 1rem; grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr)); }
|
|
116
|
+
figure { margin: 0; padding: .75rem; border: 1px solid #ccc; border-radius: .5rem; }
|
|
117
|
+
figcaption { display: flex; gap: .75rem; align-items: center; margin-top: .5rem; font-size: .85em; color: #666; }
|
|
118
|
+
audio { width: 100%; }
|
|
119
|
+
button { font: inherit; cursor: pointer; }
|
|
120
|
+
</style>
|
|
121
|
+
<h1>audio-assets preview${args.clean ? " — clean (no noise, babble or reverb)" : ""}</h1>
|
|
122
|
+
<p>Settings: ${JSON.stringify(settings)}</p>
|
|
123
|
+
<h2>Challenges</h2>
|
|
124
|
+
<p>Listen before revealing — that is the only honest way to judge whether these are solvable.</p>
|
|
125
|
+
<div class="grid">${challenges.join("\n")}</div>
|
|
126
|
+
<h2>Digit references (clean, one voice)</h2>
|
|
127
|
+
<div class="grid">${references.join("\n")}</div>
|
|
128
|
+
<script>
|
|
129
|
+
for (const button of document.querySelectorAll("button[data-answer]")) {
|
|
130
|
+
button.addEventListener("click", () => {
|
|
131
|
+
button.textContent = button.dataset.answer;
|
|
132
|
+
button.disabled = true;
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
</script>`;
|
|
136
|
+
|
|
137
|
+
writeFileSync(join(outDir, "index.html"), html);
|
|
138
|
+
process.stdout.write(`wrote ${args.count} challenges to ${outDir}\n`);
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
await main();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { ViteCommonJSConfig } from "@prosopo/config";
|
|
17
|
+
|
|
18
|
+
export default function () {
|
|
19
|
+
return ViteCommonJSConfig(
|
|
20
|
+
path.basename("."),
|
|
21
|
+
path.resolve("./tsconfig.json"),
|
|
22
|
+
);
|
|
23
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { ViteEsmConfig } from "@prosopo/config";
|
|
17
|
+
|
|
18
|
+
export default function () {
|
|
19
|
+
return ViteEsmConfig(path.basename("."), path.resolve("./tsconfig.json"));
|
|
20
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
// Copyright 2021-2026 Prosopo (UK) Ltd.
|
|
4
|
+
//
|
|
5
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
// you may not use this file except in compliance with the License.
|
|
7
|
+
// You may obtain a copy of the License at
|
|
8
|
+
//
|
|
9
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
//
|
|
11
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
// See the License for the specific language governing permissions and
|
|
15
|
+
// limitations under the License.
|
|
16
|
+
import { ViteTestConfig } from "@prosopo/config";
|
|
17
|
+
import dotenv from "dotenv";
|
|
18
|
+
process.env.NODE_ENV = "test";
|
|
19
|
+
// if .env.test exists at this level, use it, otherwise use the one at the root
|
|
20
|
+
const envFile = `.env.${process.env.NODE_ENV || "development"}`;
|
|
21
|
+
let envPath = envFile;
|
|
22
|
+
if (fs.existsSync(envFile)) {
|
|
23
|
+
envPath = path.resolve(envFile);
|
|
24
|
+
} else if (fs.existsSync(`../../${envFile}`)) {
|
|
25
|
+
envPath = path.resolve(`../../${envFile}`);
|
|
26
|
+
} else {
|
|
27
|
+
throw new Error(`No ${envFile} file found`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
dotenv.config({ path: envPath });
|
|
31
|
+
|
|
32
|
+
export default ViteTestConfig();
|
package/README.md
DELETED