earcons 0.1.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 +21 -0
- package/README.md +169 -0
- package/dist/earcon.min.global.js +1 -0
- package/dist/index.cjs +449 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +153 -0
- package/dist/index.d.ts +153 -0
- package/dist/index.js +437 -0
- package/dist/index.js.map +1 -0
- package/package.json +65 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,437 @@
|
|
|
1
|
+
// src/context.ts
|
|
2
|
+
var _ctx = null;
|
|
3
|
+
async function getAudioContext() {
|
|
4
|
+
if (!_ctx) {
|
|
5
|
+
_ctx = new AudioContext();
|
|
6
|
+
}
|
|
7
|
+
if (_ctx.state === "suspended") {
|
|
8
|
+
await _ctx.resume();
|
|
9
|
+
}
|
|
10
|
+
return _ctx;
|
|
11
|
+
}
|
|
12
|
+
async function closeAudioContext() {
|
|
13
|
+
if (_ctx) {
|
|
14
|
+
await _ctx.close();
|
|
15
|
+
_ctx = null;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function setAudioContext(ctx) {
|
|
19
|
+
_ctx = ctx;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/synth.ts
|
|
23
|
+
var DEFAULT_ENVELOPE = {
|
|
24
|
+
attack: 0.01,
|
|
25
|
+
decay: 0.05,
|
|
26
|
+
sustain: 0.7,
|
|
27
|
+
release: 0.08,
|
|
28
|
+
hold: 0
|
|
29
|
+
};
|
|
30
|
+
function semitonesToMultiplier(semitones) {
|
|
31
|
+
return Math.pow(2, semitones / 12);
|
|
32
|
+
}
|
|
33
|
+
function scheduleNote(ctx, note, masterGain, pitchMultiplier, timeOffset) {
|
|
34
|
+
const env = {
|
|
35
|
+
...DEFAULT_ENVELOPE,
|
|
36
|
+
...note.envelope
|
|
37
|
+
};
|
|
38
|
+
const t0 = timeOffset + note.startAt;
|
|
39
|
+
const noteGain = note.gain ?? 1;
|
|
40
|
+
const waveShape = note.waveShape ?? "sine";
|
|
41
|
+
const freq = note.frequency * pitchMultiplier;
|
|
42
|
+
const gainNode = ctx.createGain();
|
|
43
|
+
gainNode.connect(masterGain);
|
|
44
|
+
gainNode.gain.setValueAtTime(0, t0);
|
|
45
|
+
gainNode.gain.linearRampToValueAtTime(noteGain, t0 + env.attack);
|
|
46
|
+
const holdEnd = t0 + env.attack + (env.hold ?? 0);
|
|
47
|
+
gainNode.gain.setValueAtTime(noteGain, holdEnd);
|
|
48
|
+
gainNode.gain.linearRampToValueAtTime(
|
|
49
|
+
noteGain * env.sustain,
|
|
50
|
+
holdEnd + env.decay
|
|
51
|
+
);
|
|
52
|
+
const noteEnd = t0 + note.duration;
|
|
53
|
+
gainNode.gain.setValueAtTime(noteGain * env.sustain, noteEnd);
|
|
54
|
+
gainNode.gain.linearRampToValueAtTime(0, noteEnd + env.release);
|
|
55
|
+
const osc = ctx.createOscillator();
|
|
56
|
+
osc.type = waveShape;
|
|
57
|
+
osc.frequency.setValueAtTime(freq, t0);
|
|
58
|
+
osc.connect(gainNode);
|
|
59
|
+
osc.start(t0);
|
|
60
|
+
osc.stop(noteEnd + env.release + 0.01);
|
|
61
|
+
return noteEnd + env.release;
|
|
62
|
+
}
|
|
63
|
+
function playSound(ctx, sound, volume, pitchSemitones, onEnded) {
|
|
64
|
+
const masterGain = ctx.createGain();
|
|
65
|
+
masterGain.gain.setValueAtTime(Math.max(0, Math.min(1, volume)), ctx.currentTime);
|
|
66
|
+
masterGain.connect(ctx.destination);
|
|
67
|
+
const pitchMultiplier = semitonesToMultiplier(pitchSemitones);
|
|
68
|
+
const timeOffset = ctx.currentTime;
|
|
69
|
+
let latestEnd = timeOffset;
|
|
70
|
+
for (const note of sound.notes) {
|
|
71
|
+
const end = scheduleNote(ctx, note, masterGain, pitchMultiplier, timeOffset);
|
|
72
|
+
if (end > latestEnd) latestEnd = end;
|
|
73
|
+
}
|
|
74
|
+
if (onEnded) {
|
|
75
|
+
const sentinel = ctx.createOscillator();
|
|
76
|
+
const silentGain = ctx.createGain();
|
|
77
|
+
silentGain.gain.setValueAtTime(0, timeOffset);
|
|
78
|
+
sentinel.connect(silentGain);
|
|
79
|
+
silentGain.connect(ctx.destination);
|
|
80
|
+
sentinel.start(timeOffset);
|
|
81
|
+
sentinel.stop(latestEnd);
|
|
82
|
+
sentinel.onended = onEnded;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// src/sounds/success.ts
|
|
87
|
+
var VARIANTS = {
|
|
88
|
+
short: {
|
|
89
|
+
name: "success",
|
|
90
|
+
duration: 0.25,
|
|
91
|
+
notes: [
|
|
92
|
+
{ frequency: 523.25, duration: 0.1, startAt: 0, waveShape: "sine", gain: 0.8 },
|
|
93
|
+
{ frequency: 783.99, duration: 0.15, startAt: 0.1, waveShape: "sine", gain: 0.9 }
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
medium: {
|
|
97
|
+
name: "success",
|
|
98
|
+
duration: 0.45,
|
|
99
|
+
notes: [
|
|
100
|
+
{ frequency: 523.25, duration: 0.12, startAt: 0, waveShape: "sine", gain: 0.7 },
|
|
101
|
+
{ frequency: 659.25, duration: 0.12, startAt: 0.12, waveShape: "sine", gain: 0.8 },
|
|
102
|
+
{ frequency: 783.99, duration: 0.2, startAt: 0.24, waveShape: "sine", gain: 0.9 }
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
long: {
|
|
106
|
+
name: "success",
|
|
107
|
+
duration: 0.7,
|
|
108
|
+
notes: [
|
|
109
|
+
{ frequency: 392, duration: 0.1, startAt: 0, waveShape: "sine", gain: 0.6 },
|
|
110
|
+
{ frequency: 523.25, duration: 0.12, startAt: 0.1, waveShape: "sine", gain: 0.7 },
|
|
111
|
+
{ frequency: 659.25, duration: 0.12, startAt: 0.22, waveShape: "sine", gain: 0.8 },
|
|
112
|
+
{ frequency: 783.99, duration: 0.25, startAt: 0.34, waveShape: "sine", gain: 0.9 }
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
function successPreset(variant, pitchSemitones) {
|
|
117
|
+
return VARIANTS[variant];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/sounds/error.ts
|
|
121
|
+
var VARIANTS2 = {
|
|
122
|
+
short: {
|
|
123
|
+
name: "error",
|
|
124
|
+
duration: 0.25,
|
|
125
|
+
notes: [
|
|
126
|
+
{
|
|
127
|
+
frequency: 440,
|
|
128
|
+
duration: 0.1,
|
|
129
|
+
startAt: 0,
|
|
130
|
+
waveShape: "square",
|
|
131
|
+
gain: 0.5,
|
|
132
|
+
envelope: { attack: 5e-3, decay: 0.04, sustain: 0.6, release: 0.06 }
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
frequency: 311,
|
|
136
|
+
duration: 0.15,
|
|
137
|
+
startAt: 0.1,
|
|
138
|
+
waveShape: "square",
|
|
139
|
+
gain: 0.6,
|
|
140
|
+
envelope: { attack: 5e-3, decay: 0.04, sustain: 0.6, release: 0.06 }
|
|
141
|
+
}
|
|
142
|
+
]
|
|
143
|
+
},
|
|
144
|
+
medium: {
|
|
145
|
+
name: "error",
|
|
146
|
+
duration: 0.5,
|
|
147
|
+
notes: [
|
|
148
|
+
{
|
|
149
|
+
frequency: 440,
|
|
150
|
+
duration: 0.12,
|
|
151
|
+
startAt: 0,
|
|
152
|
+
waveShape: "sawtooth",
|
|
153
|
+
gain: 0.4,
|
|
154
|
+
envelope: { attack: 5e-3, decay: 0.06, sustain: 0.5, release: 0.08 }
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
frequency: 392,
|
|
158
|
+
duration: 0.12,
|
|
159
|
+
startAt: 0.13,
|
|
160
|
+
waveShape: "sawtooth",
|
|
161
|
+
gain: 0.45,
|
|
162
|
+
envelope: { attack: 5e-3, decay: 0.06, sustain: 0.5, release: 0.08 }
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
frequency: 311,
|
|
166
|
+
duration: 0.2,
|
|
167
|
+
startAt: 0.26,
|
|
168
|
+
waveShape: "sawtooth",
|
|
169
|
+
gain: 0.55,
|
|
170
|
+
envelope: { attack: 5e-3, decay: 0.06, sustain: 0.5, release: 0.12 }
|
|
171
|
+
}
|
|
172
|
+
]
|
|
173
|
+
},
|
|
174
|
+
long: {
|
|
175
|
+
name: "error",
|
|
176
|
+
duration: 0.75,
|
|
177
|
+
notes: [
|
|
178
|
+
{
|
|
179
|
+
frequency: 440,
|
|
180
|
+
duration: 0.12,
|
|
181
|
+
startAt: 0,
|
|
182
|
+
waveShape: "sawtooth",
|
|
183
|
+
gain: 0.4,
|
|
184
|
+
envelope: { attack: 5e-3, decay: 0.07, sustain: 0.5, release: 0.1 }
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
frequency: 415,
|
|
188
|
+
duration: 0.1,
|
|
189
|
+
startAt: 0.14,
|
|
190
|
+
waveShape: "sawtooth",
|
|
191
|
+
gain: 0.4,
|
|
192
|
+
envelope: { attack: 5e-3, decay: 0.07, sustain: 0.5, release: 0.1 }
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
frequency: 370,
|
|
196
|
+
duration: 0.12,
|
|
197
|
+
startAt: 0.26,
|
|
198
|
+
waveShape: "sawtooth",
|
|
199
|
+
gain: 0.45,
|
|
200
|
+
envelope: { attack: 5e-3, decay: 0.07, sustain: 0.5, release: 0.1 }
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
frequency: 311,
|
|
204
|
+
duration: 0.25,
|
|
205
|
+
startAt: 0.4,
|
|
206
|
+
waveShape: "sawtooth",
|
|
207
|
+
gain: 0.55,
|
|
208
|
+
envelope: { attack: 5e-3, decay: 0.08, sustain: 0.5, release: 0.15 }
|
|
209
|
+
}
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
function errorPreset(variant, pitchSemitones) {
|
|
214
|
+
return VARIANTS2[variant];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/sounds/warning.ts
|
|
218
|
+
var VARIANTS3 = {
|
|
219
|
+
short: {
|
|
220
|
+
name: "warning",
|
|
221
|
+
duration: 0.28,
|
|
222
|
+
notes: [
|
|
223
|
+
{ frequency: 587.33, duration: 0.1, startAt: 0, waveShape: "triangle", gain: 0.7 },
|
|
224
|
+
{ frequency: 523.25, duration: 0.18, startAt: 0.1, waveShape: "triangle", gain: 0.75 }
|
|
225
|
+
]
|
|
226
|
+
},
|
|
227
|
+
medium: {
|
|
228
|
+
name: "warning",
|
|
229
|
+
duration: 0.5,
|
|
230
|
+
notes: [
|
|
231
|
+
{ frequency: 587.33, duration: 0.12, startAt: 0, waveShape: "triangle", gain: 0.65 },
|
|
232
|
+
{ frequency: 587.33, duration: 0.12, startAt: 0.14, waveShape: "triangle", gain: 0.65 },
|
|
233
|
+
{ frequency: 493.88, duration: 0.2, startAt: 0.28, waveShape: "triangle", gain: 0.75 }
|
|
234
|
+
]
|
|
235
|
+
},
|
|
236
|
+
long: {
|
|
237
|
+
name: "warning",
|
|
238
|
+
duration: 0.8,
|
|
239
|
+
notes: [
|
|
240
|
+
{ frequency: 587.33, duration: 0.12, startAt: 0, waveShape: "triangle", gain: 0.6 },
|
|
241
|
+
{ frequency: 587.33, duration: 0.12, startAt: 0.15, waveShape: "triangle", gain: 0.6 },
|
|
242
|
+
{ frequency: 554.37, duration: 0.12, startAt: 0.3, waveShape: "triangle", gain: 0.65 },
|
|
243
|
+
{ frequency: 493.88, duration: 0.25, startAt: 0.45, waveShape: "triangle", gain: 0.75 }
|
|
244
|
+
]
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
function warningPreset(variant, pitchSemitones) {
|
|
248
|
+
return VARIANTS3[variant];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/sounds/notification.ts
|
|
252
|
+
var VARIANTS4 = {
|
|
253
|
+
short: {
|
|
254
|
+
name: "notification",
|
|
255
|
+
duration: 0.22,
|
|
256
|
+
notes: [
|
|
257
|
+
{ frequency: 880, duration: 0.08, startAt: 0, waveShape: "sine", gain: 0.6 },
|
|
258
|
+
{ frequency: 660, duration: 0.14, startAt: 0.08, waveShape: "sine", gain: 0.65 }
|
|
259
|
+
]
|
|
260
|
+
},
|
|
261
|
+
medium: {
|
|
262
|
+
name: "notification",
|
|
263
|
+
duration: 0.38,
|
|
264
|
+
notes: [
|
|
265
|
+
{ frequency: 880, duration: 0.1, startAt: 0, waveShape: "sine", gain: 0.55 },
|
|
266
|
+
{ frequency: 660, duration: 0.28, startAt: 0.1, waveShape: "sine", gain: 0.6 }
|
|
267
|
+
]
|
|
268
|
+
},
|
|
269
|
+
long: {
|
|
270
|
+
name: "notification",
|
|
271
|
+
duration: 0.55,
|
|
272
|
+
notes: [
|
|
273
|
+
{ frequency: 1046.5, duration: 0.08, startAt: 0, waveShape: "sine", gain: 0.5 },
|
|
274
|
+
{ frequency: 880, duration: 0.1, startAt: 0.1, waveShape: "sine", gain: 0.55 },
|
|
275
|
+
{ frequency: 660, duration: 0.3, startAt: 0.22, waveShape: "sine", gain: 0.6 }
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
function notificationPreset(variant, pitchSemitones) {
|
|
280
|
+
return VARIANTS4[variant];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// src/sounds/click.ts
|
|
284
|
+
var VARIANTS5 = {
|
|
285
|
+
short: {
|
|
286
|
+
name: "click",
|
|
287
|
+
duration: 0.04,
|
|
288
|
+
notes: [
|
|
289
|
+
{
|
|
290
|
+
frequency: 1200,
|
|
291
|
+
duration: 0.03,
|
|
292
|
+
startAt: 0,
|
|
293
|
+
waveShape: "sine",
|
|
294
|
+
gain: 0.5,
|
|
295
|
+
envelope: { attack: 2e-3, decay: 0.015, sustain: 0, release: 0.01 }
|
|
296
|
+
}
|
|
297
|
+
]
|
|
298
|
+
},
|
|
299
|
+
medium: {
|
|
300
|
+
name: "click",
|
|
301
|
+
duration: 0.07,
|
|
302
|
+
notes: [
|
|
303
|
+
{
|
|
304
|
+
frequency: 900,
|
|
305
|
+
duration: 0.05,
|
|
306
|
+
startAt: 0,
|
|
307
|
+
waveShape: "sine",
|
|
308
|
+
gain: 0.55,
|
|
309
|
+
envelope: { attack: 2e-3, decay: 0.03, sustain: 0, release: 0.015 }
|
|
310
|
+
}
|
|
311
|
+
]
|
|
312
|
+
},
|
|
313
|
+
long: {
|
|
314
|
+
name: "click",
|
|
315
|
+
duration: 0.12,
|
|
316
|
+
notes: [
|
|
317
|
+
{
|
|
318
|
+
frequency: 700,
|
|
319
|
+
duration: 0.08,
|
|
320
|
+
startAt: 0,
|
|
321
|
+
waveShape: "triangle",
|
|
322
|
+
gain: 0.6,
|
|
323
|
+
envelope: { attack: 2e-3, decay: 0.05, sustain: 0, release: 0.025 }
|
|
324
|
+
}
|
|
325
|
+
]
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
function clickPreset(variant, pitchSemitones) {
|
|
329
|
+
return VARIANTS5[variant];
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/sounds/info.ts
|
|
333
|
+
var VARIANTS6 = {
|
|
334
|
+
short: {
|
|
335
|
+
name: "info",
|
|
336
|
+
duration: 0.2,
|
|
337
|
+
notes: [
|
|
338
|
+
{
|
|
339
|
+
frequency: 698.46,
|
|
340
|
+
// F5
|
|
341
|
+
duration: 0.18,
|
|
342
|
+
startAt: 0,
|
|
343
|
+
waveShape: "sine",
|
|
344
|
+
gain: 0.55,
|
|
345
|
+
envelope: { attack: 0.015, decay: 0.04, sustain: 0.5, release: 0.1 }
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
},
|
|
349
|
+
medium: {
|
|
350
|
+
name: "info",
|
|
351
|
+
duration: 0.35,
|
|
352
|
+
notes: [
|
|
353
|
+
{
|
|
354
|
+
frequency: 698.46,
|
|
355
|
+
duration: 0.32,
|
|
356
|
+
startAt: 0,
|
|
357
|
+
waveShape: "sine",
|
|
358
|
+
gain: 0.55,
|
|
359
|
+
envelope: { attack: 0.02, decay: 0.05, sustain: 0.5, release: 0.12 }
|
|
360
|
+
}
|
|
361
|
+
]
|
|
362
|
+
},
|
|
363
|
+
long: {
|
|
364
|
+
name: "info",
|
|
365
|
+
duration: 0.55,
|
|
366
|
+
notes: [
|
|
367
|
+
{
|
|
368
|
+
frequency: 587.33,
|
|
369
|
+
// D5
|
|
370
|
+
duration: 0.18,
|
|
371
|
+
startAt: 0,
|
|
372
|
+
waveShape: "sine",
|
|
373
|
+
gain: 0.5,
|
|
374
|
+
envelope: { attack: 0.02, decay: 0.05, sustain: 0.4, release: 0.1 }
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
frequency: 698.46,
|
|
378
|
+
// F5
|
|
379
|
+
duration: 0.3,
|
|
380
|
+
startAt: 0.2,
|
|
381
|
+
waveShape: "sine",
|
|
382
|
+
gain: 0.55,
|
|
383
|
+
envelope: { attack: 0.02, decay: 0.06, sustain: 0.5, release: 0.12 }
|
|
384
|
+
}
|
|
385
|
+
]
|
|
386
|
+
}
|
|
387
|
+
};
|
|
388
|
+
function infoPreset(variant, pitchSemitones) {
|
|
389
|
+
return VARIANTS6[variant];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/index.ts
|
|
393
|
+
async function play(preset, opts = {}) {
|
|
394
|
+
const { volume = 0.5, pitch = 0, audioContext, onEnded } = opts;
|
|
395
|
+
const ctx = audioContext ?? await getAudioContext();
|
|
396
|
+
playSound(ctx, preset, volume, pitch, onEnded);
|
|
397
|
+
}
|
|
398
|
+
async function playSuccess(opts = {}) {
|
|
399
|
+
return play(successPreset(opts.variant ?? "medium", opts.pitch ?? 0), opts);
|
|
400
|
+
}
|
|
401
|
+
async function playError(opts = {}) {
|
|
402
|
+
return play(errorPreset(opts.variant ?? "medium", opts.pitch ?? 0), opts);
|
|
403
|
+
}
|
|
404
|
+
async function playWarning(opts = {}) {
|
|
405
|
+
return play(warningPreset(opts.variant ?? "medium", opts.pitch ?? 0), opts);
|
|
406
|
+
}
|
|
407
|
+
async function playNotification(opts = {}) {
|
|
408
|
+
return play(
|
|
409
|
+
notificationPreset(opts.variant ?? "medium", opts.pitch ?? 0),
|
|
410
|
+
opts
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
async function playClick(opts = {}) {
|
|
414
|
+
return play(clickPreset(opts.variant ?? "short", opts.pitch ?? 0), opts);
|
|
415
|
+
}
|
|
416
|
+
async function playInfo(opts = {}) {
|
|
417
|
+
return play(infoPreset(opts.variant ?? "medium", opts.pitch ?? 0), opts);
|
|
418
|
+
}
|
|
419
|
+
var BANK = {
|
|
420
|
+
success: successPreset,
|
|
421
|
+
error: errorPreset,
|
|
422
|
+
warning: warningPreset,
|
|
423
|
+
notification: notificationPreset,
|
|
424
|
+
click: clickPreset,
|
|
425
|
+
info: infoPreset
|
|
426
|
+
};
|
|
427
|
+
async function earcon(name, opts = {}) {
|
|
428
|
+
const preset = BANK[name];
|
|
429
|
+
return play(preset(opts.variant ?? "medium", opts.pitch ?? 0), opts);
|
|
430
|
+
}
|
|
431
|
+
function registerSound(name, preset) {
|
|
432
|
+
BANK[name] = preset;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
export { closeAudioContext, earcon, playClick, playError, playInfo, playNotification, playSuccess, playWarning, registerSound, semitonesToMultiplier, setAudioContext };
|
|
436
|
+
//# sourceMappingURL=index.js.map
|
|
437
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/context.ts","../src/synth.ts","../src/sounds/success.ts","../src/sounds/error.ts","../src/sounds/warning.ts","../src/sounds/notification.ts","../src/sounds/click.ts","../src/sounds/info.ts","../src/index.ts"],"names":["VARIANTS"],"mappings":"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","file":"index.js","sourcesContent":["// ---------------------------------------------------------------------------\n// earcon — AudioContext singleton with autoplay-policy handling\n// ---------------------------------------------------------------------------\n\nlet _ctx: AudioContext | null = null;\n\n/**\n * Returns the shared AudioContext, creating it on first call.\n * Automatically resumes a suspended context (needed after browser\n * autoplay-policy restrictions kick in before a user gesture).\n */\nexport async function getAudioContext(): Promise<AudioContext> {\n if (!_ctx) {\n _ctx = new AudioContext();\n }\n\n if (_ctx.state === \"suspended\") {\n await _ctx.resume();\n }\n\n return _ctx;\n}\n\n/**\n * Returns the shared AudioContext synchronously.\n * The context may still be suspended — callers that need it resumed\n * should use {@link getAudioContext} instead.\n */\nexport function getAudioContextSync(): AudioContext {\n if (!_ctx) {\n _ctx = new AudioContext();\n }\n return _ctx;\n}\n\n/**\n * Closes and discards the shared AudioContext.\n * Useful in tests or when tearing down an SPA.\n */\nexport async function closeAudioContext(): Promise<void> {\n if (_ctx) {\n await _ctx.close();\n _ctx = null;\n }\n}\n\n/**\n * Replace the internal singleton — useful in tests to inject a mock.\n */\nexport function setAudioContext(ctx: AudioContext): void {\n _ctx = ctx;\n}\n","// ---------------------------------------------------------------------------\n// earcon — synthesis engine\n// Schedules NoteEvents on a given AudioContext using oscillator nodes\n// and a GainNode-based ADSR envelope.\n// ---------------------------------------------------------------------------\n\nimport type { EarconSound, Envelope, NoteEvent } from \"./types.js\";\n\nconst DEFAULT_ENVELOPE: Envelope = {\n attack: 0.01,\n decay: 0.05,\n sustain: 0.7,\n release: 0.08,\n hold: 0,\n};\n\n/**\n * Converts a semitone offset to a frequency multiplier.\n * e.g. +12 semitones → ×2 (one octave up)\n */\nexport function semitonesToMultiplier(semitones: number): number {\n return Math.pow(2, semitones / 12);\n}\n\n/**\n * Schedules a single NoteEvent on the provided AudioContext.\n * Returns the scheduled end time (AudioContext.currentTime + startAt + duration + release).\n */\nfunction scheduleNote(\n ctx: AudioContext,\n note: NoteEvent,\n masterGain: GainNode,\n pitchMultiplier: number,\n timeOffset: number\n): number {\n const env: Envelope = {\n ...DEFAULT_ENVELOPE,\n ...note.envelope,\n };\n\n const t0 = timeOffset + note.startAt;\n const noteGain = note.gain ?? 1;\n const waveShape = note.waveShape ?? \"sine\";\n const freq = note.frequency * pitchMultiplier;\n\n // Per-note gain envelope node\n const gainNode = ctx.createGain();\n gainNode.connect(masterGain);\n gainNode.gain.setValueAtTime(0, t0);\n\n // Attack\n gainNode.gain.linearRampToValueAtTime(noteGain, t0 + env.attack);\n\n // Hold (optional flat segment)\n const holdEnd = t0 + env.attack + (env.hold ?? 0);\n gainNode.gain.setValueAtTime(noteGain, holdEnd);\n\n // Decay → sustain level\n gainNode.gain.linearRampToValueAtTime(\n noteGain * env.sustain,\n holdEnd + env.decay\n );\n\n // Sustain until note end\n const noteEnd = t0 + note.duration;\n gainNode.gain.setValueAtTime(noteGain * env.sustain, noteEnd);\n\n // Release\n gainNode.gain.linearRampToValueAtTime(0, noteEnd + env.release);\n\n // Oscillator\n const osc = ctx.createOscillator();\n osc.type = waveShape;\n osc.frequency.setValueAtTime(freq, t0);\n osc.connect(gainNode);\n osc.start(t0);\n osc.stop(noteEnd + env.release + 0.01); // tiny buffer to avoid click\n\n return noteEnd + env.release;\n}\n\n/**\n * Plays an {@link EarconSound} on the provided AudioContext.\n *\n * @param ctx - AudioContext to schedule on\n * @param sound - The sound descriptor to play\n * @param volume - Master volume 0–1\n * @param pitchSemitones - Semitone offset applied to all frequencies\n * @param onEnded - Optional callback when the sound finishes\n */\nexport function playSound(\n ctx: AudioContext,\n sound: EarconSound,\n volume: number,\n pitchSemitones: number,\n onEnded?: () => void\n): void {\n const masterGain = ctx.createGain();\n masterGain.gain.setValueAtTime(Math.max(0, Math.min(1, volume)), ctx.currentTime);\n masterGain.connect(ctx.destination);\n\n const pitchMultiplier = semitonesToMultiplier(pitchSemitones);\n const timeOffset = ctx.currentTime;\n\n let latestEnd = timeOffset;\n\n for (const note of sound.notes) {\n const end = scheduleNote(ctx, note, masterGain, pitchMultiplier, timeOffset);\n if (end > latestEnd) latestEnd = end;\n }\n\n // Schedule onEnded callback via a silent OscillatorNode that stops at latestEnd\n if (onEnded) {\n const sentinel = ctx.createOscillator();\n const silentGain = ctx.createGain();\n silentGain.gain.setValueAtTime(0, timeOffset);\n sentinel.connect(silentGain);\n silentGain.connect(ctx.destination);\n sentinel.start(timeOffset);\n sentinel.stop(latestEnd);\n sentinel.onended = onEnded;\n }\n}\n","// ---------------------------------------------------------------------------\n// earcon — \"success\" preset\n// A short ascending two-tone chime (major third → fifth), bright and clean.\n// ---------------------------------------------------------------------------\n\nimport type { EarconSound, SoundVariant } from \"../types.js\";\n\nconst VARIANTS: Record<SoundVariant, EarconSound> = {\n short: {\n name: \"success\",\n duration: 0.25,\n notes: [\n { frequency: 523.25, duration: 0.1, startAt: 0, waveShape: \"sine\", gain: 0.8 },\n { frequency: 783.99, duration: 0.15, startAt: 0.1, waveShape: \"sine\", gain: 0.9 },\n ],\n },\n medium: {\n name: \"success\",\n duration: 0.45,\n notes: [\n { frequency: 523.25, duration: 0.12, startAt: 0, waveShape: \"sine\", gain: 0.7 },\n { frequency: 659.25, duration: 0.12, startAt: 0.12, waveShape: \"sine\", gain: 0.8 },\n { frequency: 783.99, duration: 0.2, startAt: 0.24, waveShape: \"sine\", gain: 0.9 },\n ],\n },\n long: {\n name: \"success\",\n duration: 0.7,\n notes: [\n { frequency: 392.00, duration: 0.1, startAt: 0, waveShape: \"sine\", gain: 0.6 },\n { frequency: 523.25, duration: 0.12, startAt: 0.1, waveShape: \"sine\", gain: 0.7 },\n { frequency: 659.25, duration: 0.12, startAt: 0.22, waveShape: \"sine\", gain: 0.8 },\n { frequency: 783.99, duration: 0.25, startAt: 0.34, waveShape: \"sine\", gain: 0.9 },\n ],\n },\n};\n\nexport function successPreset(\n variant: SoundVariant,\n pitchSemitones: number\n): EarconSound {\n // pitchSemitones is applied by the synth engine — just return the descriptor\n void pitchSemitones;\n return VARIANTS[variant];\n}\n","// ---------------------------------------------------------------------------\n// earcon — \"error\" preset\n// Descending dissonant tones (tritone), harsh and attention-grabbing.\n// ---------------------------------------------------------------------------\n\nimport type { EarconSound, SoundVariant } from \"../types.js\";\n\nconst VARIANTS: Record<SoundVariant, EarconSound> = {\n short: {\n name: \"error\",\n duration: 0.25,\n notes: [\n { frequency: 440, duration: 0.1, startAt: 0, waveShape: \"square\", gain: 0.5,\n envelope: { attack: 0.005, decay: 0.04, sustain: 0.6, release: 0.06 } },\n { frequency: 311, duration: 0.15, startAt: 0.1, waveShape: \"square\", gain: 0.6,\n envelope: { attack: 0.005, decay: 0.04, sustain: 0.6, release: 0.06 } },\n ],\n },\n medium: {\n name: \"error\",\n duration: 0.5,\n notes: [\n { frequency: 440, duration: 0.12, startAt: 0, waveShape: \"sawtooth\", gain: 0.4,\n envelope: { attack: 0.005, decay: 0.06, sustain: 0.5, release: 0.08 } },\n { frequency: 392, duration: 0.12, startAt: 0.13, waveShape: \"sawtooth\", gain: 0.45,\n envelope: { attack: 0.005, decay: 0.06, sustain: 0.5, release: 0.08 } },\n { frequency: 311, duration: 0.2, startAt: 0.26, waveShape: \"sawtooth\", gain: 0.55,\n envelope: { attack: 0.005, decay: 0.06, sustain: 0.5, release: 0.12 } },\n ],\n },\n long: {\n name: \"error\",\n duration: 0.75,\n notes: [\n { frequency: 440, duration: 0.12, startAt: 0, waveShape: \"sawtooth\", gain: 0.4,\n envelope: { attack: 0.005, decay: 0.07, sustain: 0.5, release: 0.1 } },\n { frequency: 415, duration: 0.1, startAt: 0.14, waveShape: \"sawtooth\", gain: 0.4,\n envelope: { attack: 0.005, decay: 0.07, sustain: 0.5, release: 0.1 } },\n { frequency: 370, duration: 0.12, startAt: 0.26, waveShape: \"sawtooth\", gain: 0.45,\n envelope: { attack: 0.005, decay: 0.07, sustain: 0.5, release: 0.1 } },\n { frequency: 311, duration: 0.25, startAt: 0.4, waveShape: \"sawtooth\", gain: 0.55,\n envelope: { attack: 0.005, decay: 0.08, sustain: 0.5, release: 0.15 } },\n ],\n },\n};\n\nexport function errorPreset(\n variant: SoundVariant,\n pitchSemitones: number\n): EarconSound {\n void pitchSemitones;\n return VARIANTS[variant];\n}\n","// ---------------------------------------------------------------------------\n// earcon — \"warning\" preset\n// A flat-then-descending minor second — cautious, not alarming.\n// ---------------------------------------------------------------------------\n\nimport type { EarconSound, SoundVariant } from \"../types.js\";\n\nconst VARIANTS: Record<SoundVariant, EarconSound> = {\n short: {\n name: \"warning\",\n duration: 0.28,\n notes: [\n { frequency: 587.33, duration: 0.1, startAt: 0, waveShape: \"triangle\", gain: 0.7 },\n { frequency: 523.25, duration: 0.18, startAt: 0.1, waveShape: \"triangle\", gain: 0.75 },\n ],\n },\n medium: {\n name: \"warning\",\n duration: 0.5,\n notes: [\n { frequency: 587.33, duration: 0.12, startAt: 0, waveShape: \"triangle\", gain: 0.65 },\n { frequency: 587.33, duration: 0.12, startAt: 0.14, waveShape: \"triangle\", gain: 0.65 },\n { frequency: 493.88, duration: 0.2, startAt: 0.28, waveShape: \"triangle\", gain: 0.75 },\n ],\n },\n long: {\n name: \"warning\",\n duration: 0.8,\n notes: [\n { frequency: 587.33, duration: 0.12, startAt: 0, waveShape: \"triangle\", gain: 0.6 },\n { frequency: 587.33, duration: 0.12, startAt: 0.15, waveShape: \"triangle\", gain: 0.6 },\n { frequency: 554.37, duration: 0.12, startAt: 0.3, waveShape: \"triangle\", gain: 0.65 },\n { frequency: 493.88, duration: 0.25, startAt: 0.45, waveShape: \"triangle\", gain: 0.75 },\n ],\n },\n};\n\nexport function warningPreset(\n variant: SoundVariant,\n pitchSemitones: number\n): EarconSound {\n void pitchSemitones;\n return VARIANTS[variant];\n}\n","// ---------------------------------------------------------------------------\n// earcon — \"notification\" preset\n// Two clean sine tones — a gentle, neutral \"ding\" suitable for messages.\n// ---------------------------------------------------------------------------\n\nimport type { EarconSound, SoundVariant } from \"../types.js\";\n\nconst VARIANTS: Record<SoundVariant, EarconSound> = {\n short: {\n name: \"notification\",\n duration: 0.22,\n notes: [\n { frequency: 880, duration: 0.08, startAt: 0, waveShape: \"sine\", gain: 0.6 },\n { frequency: 660, duration: 0.14, startAt: 0.08, waveShape: \"sine\", gain: 0.65 },\n ],\n },\n medium: {\n name: \"notification\",\n duration: 0.38,\n notes: [\n { frequency: 880, duration: 0.1, startAt: 0, waveShape: \"sine\", gain: 0.55 },\n { frequency: 660, duration: 0.28, startAt: 0.1, waveShape: \"sine\", gain: 0.6 },\n ],\n },\n long: {\n name: \"notification\",\n duration: 0.55,\n notes: [\n { frequency: 1046.5, duration: 0.08, startAt: 0, waveShape: \"sine\", gain: 0.5 },\n { frequency: 880, duration: 0.1, startAt: 0.1, waveShape: \"sine\", gain: 0.55 },\n { frequency: 660, duration: 0.3, startAt: 0.22, waveShape: \"sine\", gain: 0.6 },\n ],\n },\n};\n\nexport function notificationPreset(\n variant: SoundVariant,\n pitchSemitones: number\n): EarconSound {\n void pitchSemitones;\n return VARIANTS[variant];\n}\n","// ---------------------------------------------------------------------------\n// earcon — \"click\" preset\n// A very short transient — simulates a tactile UI button press/toggle.\n// ---------------------------------------------------------------------------\n\nimport type { EarconSound, SoundVariant } from \"../types.js\";\n\nconst VARIANTS: Record<SoundVariant, EarconSound> = {\n short: {\n name: \"click\",\n duration: 0.04,\n notes: [\n {\n frequency: 1200,\n duration: 0.03,\n startAt: 0,\n waveShape: \"sine\",\n gain: 0.5,\n envelope: { attack: 0.002, decay: 0.015, sustain: 0, release: 0.01 },\n },\n ],\n },\n medium: {\n name: \"click\",\n duration: 0.07,\n notes: [\n {\n frequency: 900,\n duration: 0.05,\n startAt: 0,\n waveShape: \"sine\",\n gain: 0.55,\n envelope: { attack: 0.002, decay: 0.03, sustain: 0, release: 0.015 },\n },\n ],\n },\n long: {\n name: \"click\",\n duration: 0.12,\n notes: [\n {\n frequency: 700,\n duration: 0.08,\n startAt: 0,\n waveShape: \"triangle\",\n gain: 0.6,\n envelope: { attack: 0.002, decay: 0.05, sustain: 0, release: 0.025 },\n },\n ],\n },\n};\n\nexport function clickPreset(\n variant: SoundVariant,\n pitchSemitones: number\n): EarconSound {\n void pitchSemitones;\n return VARIANTS[variant];\n}\n","// ---------------------------------------------------------------------------\n// earcon — \"info\" preset\n// A neutral single soft tone — informational, non-intrusive.\n// ---------------------------------------------------------------------------\n\nimport type { EarconSound, SoundVariant } from \"../types.js\";\n\nconst VARIANTS: Record<SoundVariant, EarconSound> = {\n short: {\n name: \"info\",\n duration: 0.2,\n notes: [\n {\n frequency: 698.46, // F5\n duration: 0.18,\n startAt: 0,\n waveShape: \"sine\",\n gain: 0.55,\n envelope: { attack: 0.015, decay: 0.04, sustain: 0.5, release: 0.1 },\n },\n ],\n },\n medium: {\n name: \"info\",\n duration: 0.35,\n notes: [\n {\n frequency: 698.46,\n duration: 0.32,\n startAt: 0,\n waveShape: \"sine\",\n gain: 0.55,\n envelope: { attack: 0.02, decay: 0.05, sustain: 0.5, release: 0.12 },\n },\n ],\n },\n long: {\n name: \"info\",\n duration: 0.55,\n notes: [\n {\n frequency: 587.33, // D5\n duration: 0.18,\n startAt: 0,\n waveShape: \"sine\",\n gain: 0.5,\n envelope: { attack: 0.02, decay: 0.05, sustain: 0.4, release: 0.1 },\n },\n {\n frequency: 698.46, // F5\n duration: 0.3,\n startAt: 0.2,\n waveShape: \"sine\",\n gain: 0.55,\n envelope: { attack: 0.02, decay: 0.06, sustain: 0.5, release: 0.12 },\n },\n ],\n },\n};\n\nexport function infoPreset(\n variant: SoundVariant,\n pitchSemitones: number\n): EarconSound {\n void pitchSemitones;\n return VARIANTS[variant];\n}\n","// ---------------------------------------------------------------------------\n// earcon — public API\n// ---------------------------------------------------------------------------\n\nexport type {\n WaveShape,\n SoundVariant,\n SoundOptions,\n Envelope,\n NoteEvent,\n EarconSound,\n SoundPreset,\n} from \"./types.js\";\n\nexport { closeAudioContext, setAudioContext } from \"./context.js\";\nexport { semitonesToMultiplier } from \"./synth.js\";\n\nimport { getAudioContext } from \"./context.js\";\nimport { playSound } from \"./synth.js\";\nimport type { SoundOptions } from \"./types.js\";\n\nimport {\n successPreset,\n errorPreset,\n warningPreset,\n notificationPreset,\n clickPreset,\n infoPreset,\n} from \"./sounds/index.js\";\n\n// ---------------------------------------------------------------------------\n// Internal helper — resolves options and dispatches to the synth engine\n// ---------------------------------------------------------------------------\n\nasync function play(\n preset: ReturnType<typeof successPreset>,\n opts: SoundOptions = {}\n): Promise<void> {\n const { volume = 0.5, pitch = 0, audioContext, onEnded } = opts;\n const ctx = audioContext ?? (await getAudioContext());\n playSound(ctx, preset, volume, pitch, onEnded);\n}\n\n// ---------------------------------------------------------------------------\n// Named play functions — tree-shakable, one per sound type\n// ---------------------------------------------------------------------------\n\n/** Play a success / confirmation sound */\nexport async function playSuccess(opts: SoundOptions = {}): Promise<void> {\n return play(successPreset(opts.variant ?? \"medium\", opts.pitch ?? 0), opts);\n}\n\n/** Play an error sound */\nexport async function playError(opts: SoundOptions = {}): Promise<void> {\n return play(errorPreset(opts.variant ?? \"medium\", opts.pitch ?? 0), opts);\n}\n\n/** Play a warning sound */\nexport async function playWarning(opts: SoundOptions = {}): Promise<void> {\n return play(warningPreset(opts.variant ?? \"medium\", opts.pitch ?? 0), opts);\n}\n\n/** Play a notification / message sound */\nexport async function playNotification(opts: SoundOptions = {}): Promise<void> {\n return play(\n notificationPreset(opts.variant ?? \"medium\", opts.pitch ?? 0),\n opts\n );\n}\n\n/** Play a UI click / toggle transient */\nexport async function playClick(opts: SoundOptions = {}): Promise<void> {\n return play(clickPreset(opts.variant ?? \"short\", opts.pitch ?? 0), opts);\n}\n\n/** Play an informational / neutral tone */\nexport async function playInfo(opts: SoundOptions = {}): Promise<void> {\n return play(infoPreset(opts.variant ?? \"medium\", opts.pitch ?? 0), opts);\n}\n\n// ---------------------------------------------------------------------------\n// Sound bank — generic play-by-name API\n// ---------------------------------------------------------------------------\n\nconst BANK = {\n success: successPreset,\n error: errorPreset,\n warning: warningPreset,\n notification: notificationPreset,\n click: clickPreset,\n info: infoPreset,\n} as const;\n\nexport type SoundName = keyof typeof BANK;\n\n/**\n * Play any registered sound by name.\n *\n * @example\n * await earcon(\"success\");\n * await earcon(\"error\", { variant: \"long\", volume: 0.8 });\n */\nexport async function earcon(\n name: SoundName,\n opts: SoundOptions = {}\n): Promise<void> {\n const preset = BANK[name];\n return play(preset(opts.variant ?? \"medium\", opts.pitch ?? 0), opts);\n}\n\n/**\n * Register a custom sound under a new name so it can be played via {@link earcon}.\n *\n * @example\n * import type { SoundPreset } from \"earcons\";\n *\n * const mySound: SoundPreset = (variant, pitch) => ({\n * name: \"mySound\",\n * duration: 0.3,\n * notes: [{ frequency: 440, duration: 0.3, startAt: 0 }],\n * });\n *\n * registerSound(\"mySound\", mySound);\n * await earcon(\"mySound\");\n */\nexport function registerSound(name: string, preset: SoundPreset): void {\n (BANK as Record<string, SoundPreset>)[name] = preset;\n}\n\nimport type { SoundPreset } from \"./types.js\";\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "earcons",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tiny vanilla TypeScript library for playing UI notification sounds via the Web Audio API — no audio files, no dependencies.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"web-audio-api",
|
|
7
|
+
"notification",
|
|
8
|
+
"sound",
|
|
9
|
+
"ui",
|
|
10
|
+
"earcon",
|
|
11
|
+
"sfx",
|
|
12
|
+
"typescript",
|
|
13
|
+
"browser",
|
|
14
|
+
"audio",
|
|
15
|
+
"beep"
|
|
16
|
+
],
|
|
17
|
+
"author": {
|
|
18
|
+
"name": "smolgroot",
|
|
19
|
+
"url": "https://github.com/smolgroot"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "https://github.com/smolgroot/earcons.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/smolgroot/earcons#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/smolgroot/earcons/issues"
|
|
28
|
+
},
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"type": "module",
|
|
31
|
+
"main": "./dist/index.cjs",
|
|
32
|
+
"module": "./dist/index.js",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"types": "./dist/index.d.ts",
|
|
37
|
+
"import": "./dist/index.js",
|
|
38
|
+
"require": "./dist/index.cjs"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"browser": "./dist/earcon.min.global.js",
|
|
42
|
+
"files": [
|
|
43
|
+
"dist",
|
|
44
|
+
"LICENSE",
|
|
45
|
+
"README.md"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsup",
|
|
49
|
+
"dev": "tsup --watch",
|
|
50
|
+
"prepublishOnly": "npm run typecheck && npm test && npm run build",
|
|
51
|
+
"test": "vitest run",
|
|
52
|
+
"test:watch": "vitest",
|
|
53
|
+
"test:coverage": "vitest run --coverage",
|
|
54
|
+
"typecheck": "tsc --noEmit",
|
|
55
|
+
"lint": "tsc --noEmit"
|
|
56
|
+
},
|
|
57
|
+
"devDependencies": {
|
|
58
|
+
"@types/jsdom": "^28.0.0",
|
|
59
|
+
"@vitest/coverage-v8": "^1.4.0",
|
|
60
|
+
"jsdom": "^29.0.0",
|
|
61
|
+
"tsup": "^8.0.0",
|
|
62
|
+
"typescript": "^5.4.0",
|
|
63
|
+
"vitest": "^1.4.0"
|
|
64
|
+
}
|
|
65
|
+
}
|