@siteed/audio-studio 3.1.0 → 3.2.0-beta.1

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.
Files changed (71) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +97 -50
  3. package/android/src/androidTest/java/net/siteed/audiostudio/AudioFinalMetadataContractInstrumentedTest.kt +190 -0
  4. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderInstrumentedTest.kt +29 -83
  5. package/android/src/androidTest/java/net/siteed/audiostudio/AudioRecorderPerformanceInstrumentedTest.kt +17 -1
  6. package/android/src/androidTest/java/net/siteed/audiostudio/OpusRangeDecodeRegressionInstrumentedTest.kt +186 -0
  7. package/android/src/main/java/net/siteed/audiostudio/AudioProcessor.kt +473 -380
  8. package/android/src/main/java/net/siteed/audiostudio/AudioStreamDecoder.kt +640 -0
  9. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +187 -13
  10. package/android/src/main/java/net/siteed/audiostudio/AudioTrimmer.kt +174 -212
  11. package/android/src/main/java/net/siteed/audiostudio/Constants.kt +4 -0
  12. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
  13. package/build/cjs/AudioAnalysis/extractPreview.js +92 -15
  14. package/build/cjs/AudioAnalysis/extractPreview.js.map +1 -1
  15. package/build/cjs/AudioAnalysis/extractPreviewBars.js +134 -0
  16. package/build/cjs/AudioAnalysis/extractPreviewBars.js.map +1 -0
  17. package/build/cjs/errors/AudioExtractionError.js +127 -0
  18. package/build/cjs/errors/AudioExtractionError.js.map +1 -0
  19. package/build/cjs/errors/AudioStreamError.js +152 -0
  20. package/build/cjs/errors/AudioStreamError.js.map +1 -0
  21. package/build/cjs/errors/AudioStreamError.test.js +61 -0
  22. package/build/cjs/errors/AudioStreamError.test.js.map +1 -0
  23. package/build/cjs/index.js +12 -1
  24. package/build/cjs/index.js.map +1 -1
  25. package/build/cjs/streamAudioData.js +467 -0
  26. package/build/cjs/streamAudioData.js.map +1 -0
  27. package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
  28. package/build/esm/AudioAnalysis/extractPreview.js +92 -15
  29. package/build/esm/AudioAnalysis/extractPreview.js.map +1 -1
  30. package/build/esm/AudioAnalysis/extractPreviewBars.js +128 -0
  31. package/build/esm/AudioAnalysis/extractPreviewBars.js.map +1 -0
  32. package/build/esm/errors/AudioExtractionError.js +122 -0
  33. package/build/esm/errors/AudioExtractionError.js.map +1 -0
  34. package/build/esm/errors/AudioStreamError.js +147 -0
  35. package/build/esm/errors/AudioStreamError.js.map +1 -0
  36. package/build/esm/errors/AudioStreamError.test.js +59 -0
  37. package/build/esm/errors/AudioStreamError.test.js.map +1 -0
  38. package/build/esm/index.js +5 -1
  39. package/build/esm/index.js.map +1 -1
  40. package/build/esm/streamAudioData.js +460 -0
  41. package/build/esm/streamAudioData.js.map +1 -0
  42. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +79 -0
  43. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -1
  44. package/build/types/AudioAnalysis/extractPreview.d.ts +2 -2
  45. package/build/types/AudioAnalysis/extractPreview.d.ts.map +1 -1
  46. package/build/types/AudioAnalysis/extractPreviewBars.d.ts +12 -0
  47. package/build/types/AudioAnalysis/extractPreviewBars.d.ts.map +1 -0
  48. package/build/types/errors/AudioExtractionError.d.ts +24 -0
  49. package/build/types/errors/AudioExtractionError.d.ts.map +1 -0
  50. package/build/types/errors/AudioStreamError.d.ts +25 -0
  51. package/build/types/errors/AudioStreamError.d.ts.map +1 -0
  52. package/build/types/errors/AudioStreamError.test.d.ts +2 -0
  53. package/build/types/errors/AudioStreamError.test.d.ts.map +1 -0
  54. package/build/types/index.d.ts +8 -1
  55. package/build/types/index.d.ts.map +1 -1
  56. package/build/types/streamAudioData.d.ts +114 -0
  57. package/build/types/streamAudioData.d.ts.map +1 -0
  58. package/ios/AudioProcessingHelpers.swift +10 -5
  59. package/ios/AudioProcessor.swift +99 -0
  60. package/ios/AudioStreamDecoder.swift +523 -0
  61. package/ios/AudioStudioModule.swift +210 -3
  62. package/ios/AudioStudioTests/AudioStreamDecoderTests.swift +128 -0
  63. package/package.json +7 -7
  64. package/src/AudioAnalysis/AudioAnalysis.types.ts +82 -0
  65. package/src/AudioAnalysis/extractPreview.ts +118 -17
  66. package/src/AudioAnalysis/extractPreviewBars.ts +193 -0
  67. package/src/errors/AudioExtractionError.ts +167 -0
  68. package/src/errors/AudioStreamError.test.ts +65 -0
  69. package/src/errors/AudioStreamError.ts +185 -0
  70. package/src/index.ts +34 -0
  71. package/src/streamAudioData.ts +654 -0
@@ -0,0 +1,467 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.streamAudioData = streamAudioData;
7
+ exports.getAudioDecodeCapabilities = getAudioDecodeCapabilities;
8
+ const expo_modules_core_1 = require("expo-modules-core");
9
+ const AudioStudioModule_1 = __importDefault(require("./AudioStudioModule"));
10
+ const constants_1 = require("./constants");
11
+ const AudioStreamError_1 = require("./errors/AudioStreamError");
12
+ const audioProcessing_1 = require("./utils/audioProcessing");
13
+ const CHUNK_EVENT = 'AudioDataStreamChunk';
14
+ const PROGRESS_EVENT = 'AudioDataStreamProgress';
15
+ const COMPLETE_EVENT = 'AudioDataStreamComplete';
16
+ const ERROR_EVENT = 'AudioDataStreamError';
17
+ let cachedEmitter = null;
18
+ function getEmitter() {
19
+ if (!cachedEmitter) {
20
+ cachedEmitter = new expo_modules_core_1.LegacyEventEmitter(AudioStudioModule_1.default);
21
+ }
22
+ return cachedEmitter;
23
+ }
24
+ function generateRequestId() {
25
+ const g = globalThis;
26
+ if (typeof g.crypto?.randomUUID === 'function') {
27
+ try {
28
+ return g.crypto.randomUUID();
29
+ }
30
+ catch {
31
+ // fall through
32
+ }
33
+ }
34
+ return `asd_${Date.now().toString(36)}_${Math.random()
35
+ .toString(36)
36
+ .slice(2, 10)}`;
37
+ }
38
+ function toFloat32(samples) {
39
+ if (samples instanceof Float32Array)
40
+ return samples;
41
+ if (Array.isArray(samples)) {
42
+ const out = new Float32Array(samples.length);
43
+ for (let i = 0; i < samples.length; i++) {
44
+ const v = Number(samples[i]);
45
+ out[i] = Number.isFinite(v) ? v : 0;
46
+ }
47
+ return out;
48
+ }
49
+ if (typeof samples === 'string') {
50
+ const bytes = base64ToBytes(samples);
51
+ const aligned = bytes.byteOffset % 4 === 0
52
+ ? new Float32Array(bytes.buffer, bytes.byteOffset, bytes.byteLength / 4)
53
+ : new Float32Array(bytes.buffer.slice(bytes.byteOffset));
54
+ return new Float32Array(aligned);
55
+ }
56
+ if (samples && typeof samples === 'object' && 'length' in samples) {
57
+ // ArrayLike fallback
58
+ const arr = samples;
59
+ const out = new Float32Array(arr.length);
60
+ for (let i = 0; i < arr.length; i++) {
61
+ const v = Number(arr[i]);
62
+ out[i] = Number.isFinite(v) ? v : 0;
63
+ }
64
+ return out;
65
+ }
66
+ return new Float32Array(0);
67
+ }
68
+ function base64ToBytes(input) {
69
+ const g = globalThis;
70
+ if (typeof g.atob !== 'function') {
71
+ // Buffer path for environments without atob; React Native has atob.
72
+ const Buf = globalThis.Buffer;
73
+ if (Buf)
74
+ return new Uint8Array(Buf.from(input, 'base64'));
75
+ return new Uint8Array(0);
76
+ }
77
+ const bin = g.atob(input);
78
+ const out = new Uint8Array(bin.length);
79
+ for (let i = 0; i < bin.length; i++)
80
+ out[i] = bin.charCodeAt(i);
81
+ return out;
82
+ }
83
+ function validateOptions(options) {
84
+ if (!options.fileUri) {
85
+ throw new AudioStreamError_1.AudioStreamError({
86
+ code: 'ERR_AUDIO_STREAM_INVALID_RANGE',
87
+ message: 'fileUri is required',
88
+ recoverable: false,
89
+ });
90
+ }
91
+ if (options.startTimeMs !== undefined &&
92
+ options.endTimeMs !== undefined &&
93
+ options.startTimeMs >= options.endTimeMs) {
94
+ throw new AudioStreamError_1.AudioStreamError({
95
+ code: 'ERR_AUDIO_STREAM_INVALID_RANGE',
96
+ message: 'startTimeMs must be < endTimeMs',
97
+ recoverable: false,
98
+ });
99
+ }
100
+ if (options.chunkDurationMs !== undefined &&
101
+ (options.chunkDurationMs < 10 || options.chunkDurationMs > 60000)) {
102
+ throw new AudioStreamError_1.AudioStreamError({
103
+ code: 'ERR_AUDIO_STREAM_INVALID_RANGE',
104
+ message: 'chunkDurationMs must be in [10, 60000]',
105
+ recoverable: false,
106
+ });
107
+ }
108
+ if (options.streamFormat !== undefined &&
109
+ options.streamFormat !== 'float32') {
110
+ throw new AudioStreamError_1.AudioStreamError({
111
+ code: 'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT',
112
+ message: `Unsupported streamFormat: ${options.streamFormat}`,
113
+ recoverable: false,
114
+ });
115
+ }
116
+ }
117
+ /**
118
+ * Stream decoded audio from a stored file as bounded Float32 PCM chunks.
119
+ *
120
+ * Memory bound:
121
+ * `chunkDurationMs * sampleRate * channels * 4 * maxBufferedChunks` +
122
+ * native decoder buffers.
123
+ *
124
+ * Cancellation: pass `options.signal` and call `abort()`. The returned promise
125
+ * resolves with `cancelled: true` (it does not reject) when cancellation wins.
126
+ *
127
+ * Backpressure: if `onChunk` returns a Promise, native decode is paused until
128
+ * it resolves; if it throws, the stream is aborted with a `decode_failed` error.
129
+ */
130
+ async function streamAudioData(options, callbacks) {
131
+ validateOptions(options);
132
+ if (constants_1.isWeb) {
133
+ return streamAudioDataWeb(options, callbacks);
134
+ }
135
+ return streamAudioDataNative(options, callbacks);
136
+ }
137
+ /** Discover what the running platform supports. */
138
+ async function getAudioDecodeCapabilities() {
139
+ if (constants_1.isWeb) {
140
+ return {
141
+ platform: 'web',
142
+ supportedInputFormats: [
143
+ 'audio/wav',
144
+ 'audio/mpeg',
145
+ 'audio/mp4',
146
+ 'audio/aac',
147
+ 'audio/ogg',
148
+ 'audio/webm',
149
+ ],
150
+ supportedOutputFormats: ['float32'],
151
+ supportsCancellation: true,
152
+ supportsBackpressure: true,
153
+ supportsTimeRange: true,
154
+ supportsTargetSampleRate: true,
155
+ supportsChannelMixing: true,
156
+ knownLimitations: [
157
+ 'Web decodes the entire file via AudioContext.decodeAudioData before chunking; very long files may exceed browser memory.',
158
+ ],
159
+ };
160
+ }
161
+ if (typeof AudioStudioModule_1.default.getAudioDecodeCapabilities === 'function') {
162
+ try {
163
+ const caps = await AudioStudioModule_1.default.getAudioDecodeCapabilities();
164
+ return caps;
165
+ }
166
+ catch (err) {
167
+ throw (0, AudioStreamError_1.mapStreamError)(err, undefined, 'native');
168
+ }
169
+ }
170
+ throw new AudioStreamError_1.AudioStreamError({
171
+ code: 'ERR_AUDIO_STREAM_NATIVE_UNAVAILABLE',
172
+ message: 'getAudioDecodeCapabilities is not available on this build',
173
+ recoverable: false,
174
+ });
175
+ }
176
+ async function streamAudioDataNative(options, callbacks) {
177
+ if (typeof AudioStudioModule_1.default.streamAudioData !== 'function') {
178
+ throw new AudioStreamError_1.AudioStreamError({
179
+ code: 'ERR_AUDIO_STREAM_NATIVE_UNAVAILABLE',
180
+ message: 'streamAudioData native method missing — rebuild the host app with the latest @siteed/audio-studio.',
181
+ recoverable: false,
182
+ });
183
+ }
184
+ const requestId = generateRequestId();
185
+ const emitter = getEmitter();
186
+ const subs = [];
187
+ let chunkCount = 0;
188
+ let sampleCount = 0;
189
+ let processingChain = Promise.resolve();
190
+ let settled = false;
191
+ let abortListener = null;
192
+ const finalize = () => {
193
+ for (const sub of subs) {
194
+ try {
195
+ sub.remove();
196
+ }
197
+ catch {
198
+ /* noop */
199
+ }
200
+ }
201
+ subs.length = 0;
202
+ if (abortListener && options.signal) {
203
+ options.signal.removeEventListener('abort', abortListener);
204
+ }
205
+ };
206
+ return new Promise((resolve, reject) => {
207
+ const settle = (fn, mode, value) => {
208
+ if (settled)
209
+ return;
210
+ settled = true;
211
+ finalize();
212
+ fn();
213
+ if (mode === 'resolve') {
214
+ resolve(value);
215
+ }
216
+ else {
217
+ reject(value);
218
+ }
219
+ };
220
+ const handleError = (err) => {
221
+ settle(() => {
222
+ try {
223
+ AudioStudioModule_1.default.cancelStreamAudioData?.(requestId);
224
+ }
225
+ catch {
226
+ /* noop */
227
+ }
228
+ }, 'reject', (0, AudioStreamError_1.mapStreamError)(err, options.fileUri, 'native'));
229
+ };
230
+ subs.push(emitter.addListener(CHUNK_EVENT, (raw) => {
231
+ const evt = raw;
232
+ if (evt.requestId !== requestId)
233
+ return;
234
+ const chunk = {
235
+ requestId: evt.requestId,
236
+ chunkIndex: evt.chunkIndex,
237
+ startTimeMs: evt.startTimeMs,
238
+ endTimeMs: evt.endTimeMs,
239
+ durationMs: evt.endTimeMs - evt.startTimeMs,
240
+ startSample: evt.startSample,
241
+ sampleCount: evt.sampleCount,
242
+ sampleRate: evt.sampleRate,
243
+ channels: evt.channels,
244
+ samples: toFloat32(evt.samples),
245
+ isFinal: evt.isFinal,
246
+ };
247
+ chunkCount += 1;
248
+ sampleCount += chunk.sampleCount;
249
+ processingChain = processingChain
250
+ .then(async () => {
251
+ await callbacks.onChunk(chunk);
252
+ try {
253
+ AudioStudioModule_1.default.acknowledgeStreamAudioChunk?.(requestId, chunk.chunkIndex);
254
+ }
255
+ catch {
256
+ /* noop */
257
+ }
258
+ })
259
+ .catch(handleError);
260
+ }));
261
+ if (callbacks.onProgress) {
262
+ subs.push(emitter.addListener(PROGRESS_EVENT, (raw) => {
263
+ const evt = raw;
264
+ if (evt.requestId !== requestId)
265
+ return;
266
+ callbacks.onProgress(evt);
267
+ }));
268
+ }
269
+ subs.push(emitter.addListener(COMPLETE_EVENT, (raw) => {
270
+ const evt = raw;
271
+ if (evt.requestId !== requestId)
272
+ return;
273
+ // Wait for in-flight onChunk callbacks before resolving.
274
+ processingChain
275
+ .then(() => {
276
+ settle(() => { }, 'resolve', {
277
+ requestId,
278
+ durationMs: evt.durationMs,
279
+ sampleRate: evt.sampleRate,
280
+ channels: evt.channels,
281
+ chunks: evt.chunks ?? chunkCount,
282
+ samples: evt.samples ?? sampleCount,
283
+ cancelled: evt.cancelled,
284
+ });
285
+ })
286
+ .catch(handleError);
287
+ }));
288
+ subs.push(emitter.addListener(ERROR_EVENT, (raw) => {
289
+ const evt = raw;
290
+ if (evt.requestId !== requestId)
291
+ return;
292
+ if (evt.code === 'ERR_AUDIO_STREAM_CANCELLED') {
293
+ processingChain
294
+ .catch(() => { })
295
+ .then(() => {
296
+ settle(() => { }, 'resolve', {
297
+ requestId,
298
+ durationMs: 0,
299
+ sampleRate: options.targetSampleRate ??
300
+ options.sampleRate ??
301
+ 0,
302
+ channels: options.channels ?? 1,
303
+ chunks: chunkCount,
304
+ samples: sampleCount,
305
+ cancelled: true,
306
+ });
307
+ });
308
+ return;
309
+ }
310
+ handleError(new AudioStreamError_1.AudioStreamError({
311
+ code: evt.code ??
312
+ 'ERR_AUDIO_STREAM_UNKNOWN',
313
+ message: evt.message ?? 'native stream error',
314
+ nativeMessage: evt.nativeMessage,
315
+ fileUri: options.fileUri,
316
+ platform: 'native',
317
+ recoverable: false,
318
+ }));
319
+ }));
320
+ if (options.signal) {
321
+ if (options.signal.aborted) {
322
+ settle(() => { }, 'resolve', {
323
+ requestId,
324
+ durationMs: 0,
325
+ sampleRate: options.targetSampleRate ?? options.sampleRate ?? 0,
326
+ channels: options.channels ?? 1,
327
+ chunks: 0,
328
+ samples: 0,
329
+ cancelled: true,
330
+ });
331
+ return;
332
+ }
333
+ abortListener = () => {
334
+ try {
335
+ AudioStudioModule_1.default.cancelStreamAudioData?.(requestId);
336
+ }
337
+ catch {
338
+ /* noop */
339
+ }
340
+ };
341
+ options.signal.addEventListener('abort', abortListener);
342
+ }
343
+ const { signal: _signal, ...nativeOptions } = options;
344
+ AudioStudioModule_1.default.streamAudioData({
345
+ ...nativeOptions,
346
+ requestId,
347
+ streamFormat: options.streamFormat ?? 'float32',
348
+ chunkDurationMs: options.chunkDurationMs ?? 1000,
349
+ maxBufferedChunks: options.maxBufferedChunks ?? 4,
350
+ normalizeAudio: options.normalizeAudio ?? true,
351
+ }).catch(handleError);
352
+ });
353
+ }
354
+ async function streamAudioDataWeb(options, callbacks) {
355
+ const requestId = generateRequestId();
356
+ const cancelled = () => options.signal?.aborted === true;
357
+ if (cancelled()) {
358
+ return {
359
+ requestId,
360
+ durationMs: 0,
361
+ sampleRate: options.targetSampleRate ?? options.sampleRate ?? 0,
362
+ channels: options.channels ?? 1,
363
+ chunks: 0,
364
+ samples: 0,
365
+ cancelled: true,
366
+ };
367
+ }
368
+ try {
369
+ const processed = await (0, audioProcessing_1.processAudioBuffer)({
370
+ fileUri: options.fileUri,
371
+ targetSampleRate: options.targetSampleRate ?? 16000,
372
+ targetChannels: options.channels ?? 1,
373
+ normalizeAudio: options.normalizeAudio ?? true,
374
+ startTimeMs: options.startTimeMs,
375
+ endTimeMs: options.endTimeMs,
376
+ });
377
+ const sampleRate = processed.sampleRate;
378
+ const channels = processed.channels;
379
+ const durationMs = processed.durationMs;
380
+ const chunkDurationMs = options.chunkDurationMs ?? 1000;
381
+ let samplesPerChunk = Math.max(1, Math.floor((chunkDurationMs / 1000) * sampleRate) * channels);
382
+ if (options.maxChunkBytes) {
383
+ const maxSamples = Math.floor(options.maxChunkBytes / 4);
384
+ samplesPerChunk = Math.min(samplesPerChunk, maxSamples);
385
+ }
386
+ const all = sanitizeFloat32(processed.channelData, options.normalizeAudio ?? true);
387
+ let chunkIndex = 0;
388
+ let emittedSamples = 0;
389
+ for (let off = 0; off < all.length; off += samplesPerChunk) {
390
+ if (cancelled()) {
391
+ return {
392
+ requestId,
393
+ durationMs,
394
+ sampleRate,
395
+ channels,
396
+ chunks: chunkIndex,
397
+ samples: emittedSamples,
398
+ cancelled: true,
399
+ };
400
+ }
401
+ const end = Math.min(off + samplesPerChunk, all.length);
402
+ const slice = all.slice(off, end);
403
+ const startSample = off / channels;
404
+ const endSample = end / channels;
405
+ const chunk = {
406
+ requestId,
407
+ chunkIndex,
408
+ startTimeMs: Math.round((startSample / sampleRate) * 1000),
409
+ endTimeMs: Math.round((endSample / sampleRate) * 1000),
410
+ durationMs: Math.round(((endSample - startSample) / sampleRate) * 1000),
411
+ startSample,
412
+ sampleCount: slice.length,
413
+ sampleRate,
414
+ channels,
415
+ samples: slice,
416
+ isFinal: end >= all.length,
417
+ };
418
+ await callbacks.onChunk(chunk);
419
+ callbacks.onProgress?.({
420
+ requestId,
421
+ processedMs: chunk.endTimeMs,
422
+ durationMs,
423
+ progress: durationMs > 0 ? chunk.endTimeMs / durationMs : 1,
424
+ emittedChunks: chunkIndex + 1,
425
+ });
426
+ chunkIndex += 1;
427
+ emittedSamples += slice.length;
428
+ }
429
+ return {
430
+ requestId,
431
+ durationMs,
432
+ sampleRate,
433
+ channels,
434
+ chunks: chunkIndex,
435
+ samples: emittedSamples,
436
+ cancelled: false,
437
+ };
438
+ }
439
+ catch (err) {
440
+ throw (0, AudioStreamError_1.mapStreamError)(err, options.fileUri, 'web');
441
+ }
442
+ }
443
+ function sanitizeFloat32(input, clamp) {
444
+ if (!clamp) {
445
+ // still need NaN/Inf sanitation
446
+ for (let i = 0; i < input.length; i++) {
447
+ const v = input[i];
448
+ if (!Number.isFinite(v))
449
+ input[i] = 0;
450
+ }
451
+ return input;
452
+ }
453
+ for (let i = 0; i < input.length; i++) {
454
+ const v = input[i];
455
+ if (!Number.isFinite(v)) {
456
+ input[i] = 0;
457
+ }
458
+ else if (v > 1) {
459
+ input[i] = 1;
460
+ }
461
+ else if (v < -1) {
462
+ input[i] = -1;
463
+ }
464
+ }
465
+ return input;
466
+ }
467
+ //# sourceMappingURL=streamAudioData.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamAudioData.js","sourceRoot":"","sources":["../../src/streamAudioData.ts"],"names":[],"mappings":"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","sourcesContent":["import { LegacyEventEmitter, type EventSubscription } from 'expo-modules-core'\n\nimport AudioStudioModule from './AudioStudioModule'\nimport { isWeb } from './constants'\nimport {\n AudioStreamError,\n AudioStreamErrorCode,\n mapStreamError,\n} from './errors/AudioStreamError'\nimport { processAudioBuffer } from './utils/audioProcessing'\n\n/**\n * High-level API: stream decoded audio from a stored file as bounded Float32\n * chunks without materializing the full PCM range in memory.\n *\n * See `docs/STREAM_AUDIO_DATA.md` for the full contract and rollout notes.\n */\nexport interface StreamAudioDataOptions {\n /** URI of the audio file to decode. */\n fileUri: string\n /** Start time in milliseconds (default: 0). */\n startTimeMs?: number\n /** End time in milliseconds (default: end-of-file). */\n endTimeMs?: number\n /**\n * Source sample rate hint. Ignored if `targetSampleRate` is set; native\n * decoders read the actual rate from the file.\n */\n sampleRate?: number\n /** Output sample rate. Native resamples when this differs from the file. */\n targetSampleRate?: number\n /** Output channel count (1 = mono downmix, 2 = stereo passthrough). */\n channels?: number\n /** Clamp samples to [-1, 1] and replace non-finite values with 0. */\n normalizeAudio?: boolean\n /** Target chunk duration in ms (default: 1000, min: 10, max: 60000). */\n chunkDurationMs?: number\n /** Soft cap on chunk size in bytes (Float32 = 4 bytes/sample). */\n maxChunkBytes?: number\n /** Max chunks queued in native before JS ack pauses decode (default: 4). */\n maxBufferedChunks?: number\n /** Output PCM format; only `'float32'` supported today. */\n streamFormat?: 'float32'\n /** Abort the in-flight request. Resolves promise with `cancelled: true`. */\n signal?: AbortSignal\n}\n\nexport interface StreamAudioDataChunk {\n /** Native request id; constant across all chunks of one call. */\n requestId: string\n /** Zero-based monotonic chunk index. */\n chunkIndex: number\n /** Start time in output-rate ms (rounded to nearest sample). */\n startTimeMs: number\n /** End time in output-rate ms. */\n endTimeMs: number\n /** Duration in ms (`endTimeMs - startTimeMs`). */\n durationMs: number\n /** First sample index in the output timeline. */\n startSample: number\n /** Sample count in `samples` (interleaved if channels > 1). */\n sampleCount: number\n /** Output sample rate. */\n sampleRate: number\n /** Output channel count. */\n channels: number\n /** Interleaved Float32 samples in [-1, 1]. */\n samples: Float32Array\n /** True for the last chunk of a non-cancelled run. */\n isFinal: boolean\n}\n\nexport interface StreamAudioDataProgress {\n requestId: string\n processedMs: number\n durationMs: number\n progress: number\n emittedChunks: number\n bufferedChunks?: number\n}\n\nexport interface StreamAudioDataResult {\n requestId: string\n durationMs: number\n sampleRate: number\n channels: number\n chunks: number\n samples: number\n cancelled: boolean\n}\n\nexport interface StreamAudioDataCallbacks {\n /**\n * Called with each decoded chunk. If this returns a Promise, native decode\n * pauses until it resolves (backpressure). Throwing aborts the stream with\n * `ERR_AUDIO_STREAM_DECODE_FAILED`.\n */\n onChunk: (chunk: StreamAudioDataChunk) => void | Promise<void>\n /** Called whenever native reports progress. */\n onProgress?: (progress: StreamAudioDataProgress) => void\n}\n\nexport interface AudioDecodeCapabilities {\n platform: 'ios' | 'android' | 'web'\n supportedInputFormats: string[]\n supportedOutputFormats: Array<'float32'>\n supportsCancellation: boolean\n supportsBackpressure: boolean\n supportsTimeRange: boolean\n supportsTargetSampleRate: boolean\n supportsChannelMixing: boolean\n knownLimitations?: string[]\n}\n\nconst CHUNK_EVENT = 'AudioDataStreamChunk'\nconst PROGRESS_EVENT = 'AudioDataStreamProgress'\nconst COMPLETE_EVENT = 'AudioDataStreamComplete'\nconst ERROR_EVENT = 'AudioDataStreamError'\n\nlet cachedEmitter: LegacyEventEmitter | null = null\nfunction getEmitter(): LegacyEventEmitter {\n if (!cachedEmitter) {\n cachedEmitter = new LegacyEventEmitter(AudioStudioModule)\n }\n return cachedEmitter\n}\n\nfunction generateRequestId(): string {\n const g = globalThis as { crypto?: { randomUUID?: () => string } }\n if (typeof g.crypto?.randomUUID === 'function') {\n try {\n return g.crypto.randomUUID()\n } catch {\n // fall through\n }\n }\n return `asd_${Date.now().toString(36)}_${Math.random()\n .toString(36)\n .slice(2, 10)}`\n}\n\nfunction toFloat32(samples: unknown): Float32Array {\n if (samples instanceof Float32Array) return samples\n if (Array.isArray(samples)) {\n const out = new Float32Array(samples.length)\n for (let i = 0; i < samples.length; i++) {\n const v = Number(samples[i])\n out[i] = Number.isFinite(v) ? v : 0\n }\n return out\n }\n if (typeof samples === 'string') {\n const bytes = base64ToBytes(samples)\n const aligned =\n bytes.byteOffset % 4 === 0\n ? new Float32Array(\n bytes.buffer,\n bytes.byteOffset,\n bytes.byteLength / 4\n )\n : new Float32Array(bytes.buffer.slice(bytes.byteOffset))\n return new Float32Array(aligned)\n }\n if (samples && typeof samples === 'object' && 'length' in samples) {\n // ArrayLike fallback\n const arr = samples as ArrayLike<number>\n const out = new Float32Array(arr.length)\n for (let i = 0; i < arr.length; i++) {\n const v = Number(arr[i])\n out[i] = Number.isFinite(v) ? v : 0\n }\n return out\n }\n return new Float32Array(0)\n}\n\nfunction base64ToBytes(input: string): Uint8Array {\n const g = globalThis as { atob?: (s: string) => string }\n if (typeof g.atob !== 'function') {\n // Buffer path for environments without atob; React Native has atob.\n const Buf = (globalThis as { Buffer?: { from: Function } }).Buffer\n if (Buf) return new Uint8Array(Buf.from(input, 'base64'))\n return new Uint8Array(0)\n }\n const bin = g.atob(input)\n const out = new Uint8Array(bin.length)\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i)\n return out\n}\n\nfunction validateOptions(options: StreamAudioDataOptions): void {\n if (!options.fileUri) {\n throw new AudioStreamError({\n code: 'ERR_AUDIO_STREAM_INVALID_RANGE',\n message: 'fileUri is required',\n recoverable: false,\n })\n }\n if (\n options.startTimeMs !== undefined &&\n options.endTimeMs !== undefined &&\n options.startTimeMs >= options.endTimeMs\n ) {\n throw new AudioStreamError({\n code: 'ERR_AUDIO_STREAM_INVALID_RANGE',\n message: 'startTimeMs must be < endTimeMs',\n recoverable: false,\n })\n }\n if (\n options.chunkDurationMs !== undefined &&\n (options.chunkDurationMs < 10 || options.chunkDurationMs > 60000)\n ) {\n throw new AudioStreamError({\n code: 'ERR_AUDIO_STREAM_INVALID_RANGE',\n message: 'chunkDurationMs must be in [10, 60000]',\n recoverable: false,\n })\n }\n if (\n options.streamFormat !== undefined &&\n options.streamFormat !== 'float32'\n ) {\n throw new AudioStreamError({\n code: 'ERR_AUDIO_STREAM_UNSUPPORTED_FORMAT',\n message: `Unsupported streamFormat: ${options.streamFormat}`,\n recoverable: false,\n })\n }\n}\n\n/**\n * Stream decoded audio from a stored file as bounded Float32 PCM chunks.\n *\n * Memory bound:\n * `chunkDurationMs * sampleRate * channels * 4 * maxBufferedChunks` +\n * native decoder buffers.\n *\n * Cancellation: pass `options.signal` and call `abort()`. The returned promise\n * resolves with `cancelled: true` (it does not reject) when cancellation wins.\n *\n * Backpressure: if `onChunk` returns a Promise, native decode is paused until\n * it resolves; if it throws, the stream is aborted with a `decode_failed` error.\n */\nexport async function streamAudioData(\n options: StreamAudioDataOptions,\n callbacks: StreamAudioDataCallbacks\n): Promise<StreamAudioDataResult> {\n validateOptions(options)\n\n if (isWeb) {\n return streamAudioDataWeb(options, callbacks)\n }\n\n return streamAudioDataNative(options, callbacks)\n}\n\n/** Discover what the running platform supports. */\nexport async function getAudioDecodeCapabilities(): Promise<AudioDecodeCapabilities> {\n if (isWeb) {\n return {\n platform: 'web',\n supportedInputFormats: [\n 'audio/wav',\n 'audio/mpeg',\n 'audio/mp4',\n 'audio/aac',\n 'audio/ogg',\n 'audio/webm',\n ],\n supportedOutputFormats: ['float32'],\n supportsCancellation: true,\n supportsBackpressure: true,\n supportsTimeRange: true,\n supportsTargetSampleRate: true,\n supportsChannelMixing: true,\n knownLimitations: [\n 'Web decodes the entire file via AudioContext.decodeAudioData before chunking; very long files may exceed browser memory.',\n ],\n }\n }\n if (typeof AudioStudioModule.getAudioDecodeCapabilities === 'function') {\n try {\n const caps = await AudioStudioModule.getAudioDecodeCapabilities()\n return caps as AudioDecodeCapabilities\n } catch (err) {\n throw mapStreamError(err, undefined, 'native')\n }\n }\n throw new AudioStreamError({\n code: 'ERR_AUDIO_STREAM_NATIVE_UNAVAILABLE',\n message: 'getAudioDecodeCapabilities is not available on this build',\n recoverable: false,\n })\n}\n\nasync function streamAudioDataNative(\n options: StreamAudioDataOptions,\n callbacks: StreamAudioDataCallbacks\n): Promise<StreamAudioDataResult> {\n if (typeof AudioStudioModule.streamAudioData !== 'function') {\n throw new AudioStreamError({\n code: 'ERR_AUDIO_STREAM_NATIVE_UNAVAILABLE',\n message:\n 'streamAudioData native method missing — rebuild the host app with the latest @siteed/audio-studio.',\n recoverable: false,\n })\n }\n\n const requestId = generateRequestId()\n const emitter = getEmitter()\n const subs: EventSubscription[] = []\n let chunkCount = 0\n let sampleCount = 0\n let processingChain: Promise<void> = Promise.resolve()\n let settled = false\n let abortListener: (() => void) | null = null\n\n const finalize = () => {\n for (const sub of subs) {\n try {\n sub.remove()\n } catch {\n /* noop */\n }\n }\n subs.length = 0\n if (abortListener && options.signal) {\n options.signal.removeEventListener('abort', abortListener)\n }\n }\n\n return new Promise<StreamAudioDataResult>((resolve, reject) => {\n const settle = (\n fn: () => void,\n mode: 'resolve' | 'reject',\n value: unknown\n ) => {\n if (settled) return\n settled = true\n finalize()\n fn()\n if (mode === 'resolve') {\n resolve(value as StreamAudioDataResult)\n } else {\n reject(value as Error)\n }\n }\n\n const handleError = (err: unknown) => {\n settle(\n () => {\n try {\n AudioStudioModule.cancelStreamAudioData?.(requestId)\n } catch {\n /* noop */\n }\n },\n 'reject',\n mapStreamError(err, options.fileUri, 'native')\n )\n }\n\n subs.push(\n emitter.addListener(CHUNK_EVENT, (raw: unknown) => {\n const evt = raw as {\n requestId: string\n chunkIndex: number\n startTimeMs: number\n endTimeMs: number\n startSample: number\n sampleCount: number\n sampleRate: number\n channels: number\n samples: Float32Array | number[] | string\n isFinal: boolean\n }\n if (evt.requestId !== requestId) return\n const chunk: StreamAudioDataChunk = {\n requestId: evt.requestId,\n chunkIndex: evt.chunkIndex,\n startTimeMs: evt.startTimeMs,\n endTimeMs: evt.endTimeMs,\n durationMs: evt.endTimeMs - evt.startTimeMs,\n startSample: evt.startSample,\n sampleCount: evt.sampleCount,\n sampleRate: evt.sampleRate,\n channels: evt.channels,\n samples: toFloat32(evt.samples),\n isFinal: evt.isFinal,\n }\n chunkCount += 1\n sampleCount += chunk.sampleCount\n\n processingChain = processingChain\n .then(async () => {\n await callbacks.onChunk(chunk)\n try {\n AudioStudioModule.acknowledgeStreamAudioChunk?.(\n requestId,\n chunk.chunkIndex\n )\n } catch {\n /* noop */\n }\n })\n .catch(handleError)\n })\n )\n\n if (callbacks.onProgress) {\n subs.push(\n emitter.addListener(PROGRESS_EVENT, (raw: unknown) => {\n const evt = raw as StreamAudioDataProgress\n if (evt.requestId !== requestId) return\n callbacks.onProgress!(evt)\n })\n )\n }\n\n subs.push(\n emitter.addListener(COMPLETE_EVENT, (raw: unknown) => {\n const evt = raw as {\n requestId: string\n durationMs: number\n sampleRate: number\n channels: number\n chunks?: number\n samples?: number\n cancelled: boolean\n }\n if (evt.requestId !== requestId) return\n // Wait for in-flight onChunk callbacks before resolving.\n processingChain\n .then(() => {\n settle(() => {}, 'resolve', {\n requestId,\n durationMs: evt.durationMs,\n sampleRate: evt.sampleRate,\n channels: evt.channels,\n chunks: evt.chunks ?? chunkCount,\n samples: evt.samples ?? sampleCount,\n cancelled: evt.cancelled,\n } satisfies StreamAudioDataResult)\n })\n .catch(handleError)\n })\n )\n\n subs.push(\n emitter.addListener(ERROR_EVENT, (raw: unknown) => {\n const evt = raw as {\n requestId: string\n code?: string\n message?: string\n nativeMessage?: string\n }\n if (evt.requestId !== requestId) return\n if (evt.code === 'ERR_AUDIO_STREAM_CANCELLED') {\n processingChain\n .catch(() => {})\n .then(() => {\n settle(() => {}, 'resolve', {\n requestId,\n durationMs: 0,\n sampleRate:\n options.targetSampleRate ??\n options.sampleRate ??\n 0,\n channels: options.channels ?? 1,\n chunks: chunkCount,\n samples: sampleCount,\n cancelled: true,\n } satisfies StreamAudioDataResult)\n })\n return\n }\n handleError(\n new AudioStreamError({\n code:\n (evt.code as AudioStreamErrorCode) ??\n 'ERR_AUDIO_STREAM_UNKNOWN',\n message: evt.message ?? 'native stream error',\n nativeMessage: evt.nativeMessage,\n fileUri: options.fileUri,\n platform: 'native',\n recoverable: false,\n })\n )\n })\n )\n\n if (options.signal) {\n if (options.signal.aborted) {\n settle(() => {}, 'resolve', {\n requestId,\n durationMs: 0,\n sampleRate:\n options.targetSampleRate ?? options.sampleRate ?? 0,\n channels: options.channels ?? 1,\n chunks: 0,\n samples: 0,\n cancelled: true,\n } satisfies StreamAudioDataResult)\n return\n }\n abortListener = () => {\n try {\n AudioStudioModule.cancelStreamAudioData?.(requestId)\n } catch {\n /* noop */\n }\n }\n options.signal.addEventListener('abort', abortListener)\n }\n\n const { signal: _signal, ...nativeOptions } = options\n AudioStudioModule.streamAudioData({\n ...nativeOptions,\n requestId,\n streamFormat: options.streamFormat ?? 'float32',\n chunkDurationMs: options.chunkDurationMs ?? 1000,\n maxBufferedChunks: options.maxBufferedChunks ?? 4,\n normalizeAudio: options.normalizeAudio ?? true,\n }).catch(handleError)\n })\n}\n\nasync function streamAudioDataWeb(\n options: StreamAudioDataOptions,\n callbacks: StreamAudioDataCallbacks\n): Promise<StreamAudioDataResult> {\n const requestId = generateRequestId()\n const cancelled = () => options.signal?.aborted === true\n if (cancelled()) {\n return {\n requestId,\n durationMs: 0,\n sampleRate: options.targetSampleRate ?? options.sampleRate ?? 0,\n channels: options.channels ?? 1,\n chunks: 0,\n samples: 0,\n cancelled: true,\n }\n }\n\n try {\n const processed = await processAudioBuffer({\n fileUri: options.fileUri,\n targetSampleRate: options.targetSampleRate ?? 16000,\n targetChannels: options.channels ?? 1,\n normalizeAudio: options.normalizeAudio ?? true,\n startTimeMs: options.startTimeMs,\n endTimeMs: options.endTimeMs,\n })\n\n const sampleRate = processed.sampleRate\n const channels = processed.channels\n const durationMs = processed.durationMs\n const chunkDurationMs = options.chunkDurationMs ?? 1000\n let samplesPerChunk = Math.max(\n 1,\n Math.floor((chunkDurationMs / 1000) * sampleRate) * channels\n )\n if (options.maxChunkBytes) {\n const maxSamples = Math.floor(options.maxChunkBytes / 4)\n samplesPerChunk = Math.min(samplesPerChunk, maxSamples)\n }\n\n const all = sanitizeFloat32(processed.channelData, options.normalizeAudio ?? true)\n\n let chunkIndex = 0\n let emittedSamples = 0\n for (let off = 0; off < all.length; off += samplesPerChunk) {\n if (cancelled()) {\n return {\n requestId,\n durationMs,\n sampleRate,\n channels,\n chunks: chunkIndex,\n samples: emittedSamples,\n cancelled: true,\n }\n }\n const end = Math.min(off + samplesPerChunk, all.length)\n const slice = all.slice(off, end)\n const startSample = off / channels\n const endSample = end / channels\n const chunk: StreamAudioDataChunk = {\n requestId,\n chunkIndex,\n startTimeMs: Math.round((startSample / sampleRate) * 1000),\n endTimeMs: Math.round((endSample / sampleRate) * 1000),\n durationMs: Math.round(\n ((endSample - startSample) / sampleRate) * 1000\n ),\n startSample,\n sampleCount: slice.length,\n sampleRate,\n channels,\n samples: slice,\n isFinal: end >= all.length,\n }\n await callbacks.onChunk(chunk)\n callbacks.onProgress?.({\n requestId,\n processedMs: chunk.endTimeMs,\n durationMs,\n progress: durationMs > 0 ? chunk.endTimeMs / durationMs : 1,\n emittedChunks: chunkIndex + 1,\n })\n chunkIndex += 1\n emittedSamples += slice.length\n }\n\n return {\n requestId,\n durationMs,\n sampleRate,\n channels,\n chunks: chunkIndex,\n samples: emittedSamples,\n cancelled: false,\n }\n } catch (err) {\n throw mapStreamError(err, options.fileUri, 'web')\n }\n}\n\nfunction sanitizeFloat32(\n input: Float32Array,\n clamp: boolean\n): Float32Array {\n if (!clamp) {\n // still need NaN/Inf sanitation\n for (let i = 0; i < input.length; i++) {\n const v = input[i]\n if (!Number.isFinite(v)) input[i] = 0\n }\n return input\n }\n for (let i = 0; i < input.length; i++) {\n const v = input[i]\n if (!Number.isFinite(v)) {\n input[i] = 0\n } else if (v > 1) {\n input[i] = 1\n } else if (v < -1) {\n input[i] = -1\n }\n }\n return input\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"AudioAnalysis.types.js","sourceRoot":"","sources":["../../../src/AudioAnalysis/AudioAnalysis.types.ts"],"names":[],"mappings":"AAAA,iEAAiE","sourcesContent":["// packages/audio-studio/src/AudioAnalysis/AudioAnalysis.types.ts\n\nimport { BitDepth, ConsoleLike } from '../AudioStudio.types'\n\n/**\n * Represents the configuration for decoding audio data.\n */\nexport interface DecodingConfig {\n /** Target sample rate for decoded audio (Android and Web) */\n targetSampleRate?: number\n /** Target number of channels (Android and Web) */\n targetChannels?: number\n /** Target bit depth (Android and Web) */\n targetBitDepth?: BitDepth\n /** Whether to normalize audio levels (Android and Web) */\n normalizeAudio?: boolean\n}\n\n/**\n * Represents speech-related features extracted from audio.\n */\nexport interface SpeechFeatures {\n isActive: boolean // Whether speech is detected in this segment\n speakerId?: number // Optional speaker identification\n // Could add more speech-related features here like:\n // confidence: number\n // language?: string\n // sentiment?: number\n // etc.\n}\n\n/**\n * Represents various audio features extracted from an audio signal.\n */\nexport interface AudioFeatures {\n energy?: number // The infinite integral of the squared signal, representing the overall energy of the audio.\n mfcc?: number[] // Mel-frequency cepstral coefficients, describing the short-term power spectrum of a sound.\n rms?: number // Root mean square value, indicating the amplitude of the audio signal.\n minAmplitude?: number // Minimum amplitude value in the audio signal.\n maxAmplitude?: number // Maximum amplitude value in the audio signal.\n zcr?: number // Zero-crossing rate, indicating the rate at which the signal changes sign.\n spectralCentroid?: number // The center of mass of the spectrum, indicating the brightness of the sound.\n spectralFlatness?: number // Measure of the flatness of the spectrum, indicating how noise-like the signal is.\n spectralRolloff?: number // The frequency below which a specified percentage (usually 85%) of the total spectral energy lies.\n spectralBandwidth?: number // The width of the spectrum, indicating the range of frequencies present.\n chromagram?: number[] // Chromagram, representing the 12 different pitch classes of the audio.\n tempo?: number // Estimated tempo of the audio signal, measured in beats per minute (BPM).\n hnr?: number // Harmonics-to-noise ratio, indicating the proportion of harmonics to noise in the audio signal.\n melSpectrogram?: number[] // Mel-scaled spectrogram representation of the audio.\n spectralContrast?: number[] // Spectral contrast features representing the difference between peaks and valleys.\n tonnetz?: number[] // Tonal network features representing harmonic relationships.\n pitch?: number // Pitch of the audio signal, measured in Hertz (Hz).\n crc32?: number // crc32 checksum of the audio signal, used to verify the integrity of the audio.\n}\n\n/**\n * Options to specify which audio features to extract.\n * Note: Advanced features (spectral features, chromagram, pitch, etc.) are experimental,\n * especially during live recording, due to high processing requirements.\n */\nexport interface AudioFeaturesOptions {\n // Basic features - well optimized\n energy?: boolean\n rms?: boolean\n zcr?: boolean\n\n // Advanced features - experimental, may impact performance in live recording\n mfcc?: boolean\n spectralCentroid?: boolean\n spectralFlatness?: boolean\n spectralRolloff?: boolean\n spectralBandwidth?: boolean\n chromagram?: boolean\n tempo?: boolean\n hnr?: boolean\n melSpectrogram?: boolean\n spectralContrast?: boolean\n tonnetz?: boolean\n pitch?: boolean\n\n // Utility\n crc32?: boolean\n}\n\n/**\n * Represents a single data point in the audio analysis.\n */\nexport interface DataPoint {\n id: number\n amplitude: number // Peak amplitude for the segment\n rms: number // Root mean square value\n dB: number // dBFS (decibels relative to full scale) computed from RMS value\n silent: boolean // Always computed\n features?: AudioFeatures\n speech?: SpeechFeatures\n startTime?: number\n endTime?: number\n // start / end position in bytes\n startPosition?: number\n endPosition?: number\n // number of audio samples for this point (samples size depends on bit depth)\n samples?: number\n}\n\n/**\n * Represents the complete data from the audio analysis.\n */\nexport interface AudioAnalysis {\n segmentDurationMs: number // Duration of each segment in milliseconds\n durationMs: number // Duration of the audio in milliseconds\n /**\n * Bit depth used for audio analysis processing.\n *\n * **Important**: This represents the internal processing bit depth, which may differ\n * from the recording bit depth. Audio is typically converted to 32-bit float for\n * analysis to ensure precision in calculations, regardless of the original recording format.\n *\n * Platform behavior:\n * - iOS: Always 32 (float processing)\n * - Android: Always 32 (float processing)\n * - Web: Always 32 (Web Audio API standard)\n *\n * The actual recorded file will maintain the requested bit depth (8, 16, or 32).\n */\n bitDepth: number\n samples: number // Size of the audio in bytes\n numberOfChannels: number // Number of audio channels\n sampleRate: number // Sample rate of the audio\n dataPoints: DataPoint[] // Array of data points from the analysis.\n amplitudeRange: {\n min: number\n max: number\n }\n rmsRange: {\n min: number\n max: number\n }\n extractionTimeMs: number // Time taken to extract/process the analysis in milliseconds\n // TODO: speaker changes into a broader speech analysis section\n speechAnalysis?: {\n speakerChanges: {\n timestamp: number\n speakerId: number\n }[]\n // Could add more speech analysis data here like:\n // dominantSpeaker?: number\n // totalSpeechDuration?: number\n // speakerStats?: { [speakerId: number]: { duration: number, segments: number } }\n }\n}\n\n/**\n * Options for specifying a time range within an audio file.\n */\nexport interface AudioRangeOptions {\n /** Start time in milliseconds */\n startTimeMs?: number\n /** End time in milliseconds */\n endTimeMs?: number\n}\n\n/**\n * Options for generating a quick preview of audio waveform.\n * This is optimized for UI rendering with a specified number of points.\n */\nexport interface PreviewOptions extends AudioRangeOptions {\n /** URI of the audio file to analyze */\n fileUri: string\n /**\n * Total number of points to generate for the preview.\n * @default 100\n */\n numberOfPoints?: number\n /**\n * Optional logger for debugging.\n */\n logger?: ConsoleLike\n /**\n * Optional configuration for decoding the audio file.\n * Defaults to:\n * - targetSampleRate: undefined (keep original)\n * - targetChannels: undefined (keep original)\n * - targetBitDepth: 16\n * - normalizeAudio: false\n */\n decodingOptions?: DecodingConfig\n}\n\n/**\n * Options for mel-spectrogram extraction\n *\n * @experimental This feature is experimental and currently only available on Android.\n * The API may change in future versions.\n */\nexport interface ExtractMelSpectrogramOptions {\n fileUri?: string // Path to audio file\n arrayBuffer?: ArrayBuffer // Raw audio buffer\n windowSizeMs: number // Window size in ms (e.g., 25)\n hopLengthMs: number // Hop length in ms (e.g., 10)\n nMels: number // Number of mel filters (e.g., 60)\n fMin?: number // Min frequency (default: 0)\n fMax?: number // Max frequency (default: sampleRate / 2)\n windowType?: 'hann' | 'hamming' // Window function (default: 'hann')\n normalize?: boolean // Mean normalization (default: false)\n logScale?: boolean // Log scaling of mel energies (default: true)\n decodingOptions?: DecodingConfig // Audio decoding settings\n /** Optional start time in ms. If neither startTimeMs nor endTimeMs is set, defaults to 0. */\n startTimeMs?: number\n /** Optional end time in ms. Clamped so that the range does not exceed MAX_DURATION_MS (30 s). */\n endTimeMs?: number\n logger?: ConsoleLike\n}\n\n/**\n * Result type for WASM-based audio feature extraction.\n */\nexport interface AudioFeaturesWasmResult {\n spectralCentroid: number\n spectralFlatness: number\n spectralRolloff: number\n spectralBandwidth: number\n mfcc: number[]\n chromagram: number[]\n}\n\n/**\n * Return type for mel spectrogram extraction\n *\n * @experimental This feature is experimental and currently only available on Android.\n * The API may change in future versions.\n */\nexport interface MelSpectrogram {\n spectrogram: number[][] // 2D array [time][mel]\n sampleRate: number // Audio sample rate\n nMels: number // Number of mel filters\n timeSteps: number // Number of time frames\n durationMs: number // Audio duration in ms\n}\n"]}
1
+ {"version":3,"file":"AudioAnalysis.types.js","sourceRoot":"","sources":["../../../src/AudioAnalysis/AudioAnalysis.types.ts"],"names":[],"mappings":"AAAA,iEAAiE","sourcesContent":["// packages/audio-studio/src/AudioAnalysis/AudioAnalysis.types.ts\n\nimport { BitDepth, ConsoleLike } from '../AudioStudio.types'\n\n/**\n * Represents the configuration for decoding audio data.\n */\nexport interface DecodingConfig {\n /** Target sample rate for decoded audio (Android and Web) */\n targetSampleRate?: number\n /** Target number of channels (Android and Web) */\n targetChannels?: number\n /** Target bit depth (Android and Web) */\n targetBitDepth?: BitDepth\n /** Whether to normalize audio levels (Android and Web) */\n normalizeAudio?: boolean\n /**\n * RMS threshold below which a segment is flagged silent.\n * Range 0..1. Default 0.01.\n * Currently applied as a JS post-process so the same behavior holds across iOS/Android/Web.\n */\n silenceRmsThreshold?: number\n}\n\n/**\n * Represents speech-related features extracted from audio.\n */\nexport interface SpeechFeatures {\n isActive: boolean // Whether speech is detected in this segment\n speakerId?: number // Optional speaker identification\n // Could add more speech-related features here like:\n // confidence: number\n // language?: string\n // sentiment?: number\n // etc.\n}\n\n/**\n * Represents various audio features extracted from an audio signal.\n */\nexport interface AudioFeatures {\n energy?: number // The infinite integral of the squared signal, representing the overall energy of the audio.\n mfcc?: number[] // Mel-frequency cepstral coefficients, describing the short-term power spectrum of a sound.\n rms?: number // Root mean square value, indicating the amplitude of the audio signal.\n minAmplitude?: number // Minimum amplitude value in the audio signal.\n maxAmplitude?: number // Maximum amplitude value in the audio signal.\n zcr?: number // Zero-crossing rate, indicating the rate at which the signal changes sign.\n spectralCentroid?: number // The center of mass of the spectrum, indicating the brightness of the sound.\n spectralFlatness?: number // Measure of the flatness of the spectrum, indicating how noise-like the signal is.\n spectralRolloff?: number // The frequency below which a specified percentage (usually 85%) of the total spectral energy lies.\n spectralBandwidth?: number // The width of the spectrum, indicating the range of frequencies present.\n chromagram?: number[] // Chromagram, representing the 12 different pitch classes of the audio.\n tempo?: number // Estimated tempo of the audio signal, measured in beats per minute (BPM).\n hnr?: number // Harmonics-to-noise ratio, indicating the proportion of harmonics to noise in the audio signal.\n melSpectrogram?: number[] // Mel-scaled spectrogram representation of the audio.\n spectralContrast?: number[] // Spectral contrast features representing the difference between peaks and valleys.\n tonnetz?: number[] // Tonal network features representing harmonic relationships.\n pitch?: number // Pitch of the audio signal, measured in Hertz (Hz).\n crc32?: number // crc32 checksum of the audio signal, used to verify the integrity of the audio.\n}\n\n/**\n * Options to specify which audio features to extract.\n * Note: Advanced features (spectral features, chromagram, pitch, etc.) are experimental,\n * especially during live recording, due to high processing requirements.\n */\nexport interface AudioFeaturesOptions {\n // Basic features - well optimized\n energy?: boolean\n rms?: boolean\n zcr?: boolean\n\n // Advanced features - experimental, may impact performance in live recording\n mfcc?: boolean\n spectralCentroid?: boolean\n spectralFlatness?: boolean\n spectralRolloff?: boolean\n spectralBandwidth?: boolean\n chromagram?: boolean\n tempo?: boolean\n hnr?: boolean\n melSpectrogram?: boolean\n spectralContrast?: boolean\n tonnetz?: boolean\n pitch?: boolean\n\n // Utility\n crc32?: boolean\n}\n\n/**\n * Represents a single data point in the audio analysis.\n */\nexport interface DataPoint {\n id: number\n amplitude: number // Peak amplitude for the segment\n rms: number // Root mean square value\n dB: number // dBFS (decibels relative to full scale) computed from RMS value\n silent: boolean // Always computed\n features?: AudioFeatures\n speech?: SpeechFeatures\n startTime?: number\n endTime?: number\n // start / end position in bytes\n startPosition?: number\n endPosition?: number\n // number of audio samples for this point (samples size depends on bit depth)\n samples?: number\n}\n\n/**\n * Represents the complete data from the audio analysis.\n */\nexport interface AudioAnalysis {\n segmentDurationMs: number // Duration of each segment in milliseconds\n durationMs: number // Duration of the audio in milliseconds\n /**\n * Bit depth used for audio analysis processing.\n *\n * **Important**: This represents the internal processing bit depth, which may differ\n * from the recording bit depth. Audio is typically converted to 32-bit float for\n * analysis to ensure precision in calculations, regardless of the original recording format.\n *\n * Platform behavior:\n * - iOS: Always 32 (float processing)\n * - Android: Always 32 (float processing)\n * - Web: Always 32 (Web Audio API standard)\n *\n * The actual recorded file will maintain the requested bit depth (8, 16, or 32).\n */\n bitDepth: number\n samples: number // Size of the audio in bytes\n numberOfChannels: number // Number of audio channels\n sampleRate: number // Sample rate of the audio\n dataPoints: DataPoint[] // Array of data points from the analysis.\n amplitudeRange: {\n min: number\n max: number\n }\n rmsRange: {\n min: number\n max: number\n }\n extractionTimeMs: number // Time taken to extract/process the analysis in milliseconds\n // TODO: speaker changes into a broader speech analysis section\n speechAnalysis?: {\n speakerChanges: {\n timestamp: number\n speakerId: number\n }[]\n // Could add more speech analysis data here like:\n // dominantSpeaker?: number\n // totalSpeechDuration?: number\n // speakerStats?: { [speakerId: number]: { duration: number, segments: number } }\n }\n}\n\n/**\n * Options for specifying a time range within an audio file.\n */\nexport interface AudioRangeOptions {\n /** Start time in milliseconds */\n startTimeMs?: number\n /** End time in milliseconds */\n endTimeMs?: number\n}\n\n/**\n * Options for generating a quick preview of audio waveform.\n * This is optimized for UI rendering with a specified number of points.\n */\nexport interface PreviewBar {\n /** Stable zero-based bar identifier. */\n id: number\n /** Peak amplitude for this bar, normalized to 0..1. */\n amplitude: number\n /** Root mean square amplitude for this bar, normalized to 0..1. */\n rms: number\n /** Whether this bar is below the configured silence RMS threshold. */\n silent: boolean\n /** Bar start time in milliseconds from the extracted range start. */\n startTimeMs: number\n /** Bar end time in milliseconds from the extracted range start. */\n endTimeMs: number\n}\n\n/**\n * Compact preview-bars result for UI waveform rendering.\n * Unlike `AudioAnalysis`, this intentionally omits full `DataPoint` feature data.\n */\nexport interface PreviewBarsResult {\n bars: PreviewBar[]\n durationMs: number\n sampleRate: number\n numberOfChannels: number\n bitDepth: number\n samples: number\n /** Requested bar count before native/platform clamping. */\n requestedNumberOfBars: number\n /** Approximate duration represented by each bar. */\n barDurationMs: number\n amplitudeRange: {\n min: number\n max: number\n }\n rmsRange: {\n min: number\n max: number\n }\n extractionTimeMs: number\n}\n\n/**\n * Options for extracting compact waveform preview bars for UI rendering.\n */\nexport interface PreviewBarsOptions extends AudioRangeOptions {\n /** URI of the audio file to analyze */\n fileUri: string\n /**\n * Total number of bars to generate for the preview.\n * @default 100\n */\n numberOfBars?: number\n /** Optional logger for debugging. */\n logger?: ConsoleLike\n /** Optional configuration for decoding the audio file. */\n decodingOptions?: DecodingConfig\n /**\n * Optional callback fired once per compact bar after extraction resolves.\n * Native progressive streaming is not implied by this callback.\n */\n onBarReady?: (bar: PreviewBar, index: number, total: number) => void\n}\n\nexport interface PreviewOptions extends AudioRangeOptions {\n /** URI of the audio file to analyze */\n fileUri: string\n /**\n * Total number of points to generate for the preview.\n * @default 100\n */\n numberOfPoints?: number\n /**\n * Optional logger for debugging.\n */\n logger?: ConsoleLike\n /**\n * Optional configuration for decoding the audio file.\n * Defaults to:\n * - targetSampleRate: undefined (keep original)\n * - targetChannels: undefined (keep original)\n * - targetBitDepth: 16\n * - normalizeAudio: false\n */\n decodingOptions?: DecodingConfig\n /**\n * Optional callback fired once per data point as the preview becomes available.\n * Today the native module returns the full analysis in one shot; the points are then\n * micro-batched on the JS side so consumers can render bars incrementally.\n * Native progressive streaming is a future enhancement.\n */\n onPointReady?: (point: DataPoint, index: number, total: number) => void\n /**\n * Optional cancellation signal for JS-side progressive point emission.\n * Aborting does not cancel native extraction after it has started, but it\n * stops any queued `onPointReady` callbacks from an older request.\n */\n signal?: AbortSignal\n}\n\n/**\n * Options for mel-spectrogram extraction\n *\n * @experimental This feature is experimental and currently only available on Android.\n * The API may change in future versions.\n */\nexport interface ExtractMelSpectrogramOptions {\n fileUri?: string // Path to audio file\n arrayBuffer?: ArrayBuffer // Raw audio buffer\n windowSizeMs: number // Window size in ms (e.g., 25)\n hopLengthMs: number // Hop length in ms (e.g., 10)\n nMels: number // Number of mel filters (e.g., 60)\n fMin?: number // Min frequency (default: 0)\n fMax?: number // Max frequency (default: sampleRate / 2)\n windowType?: 'hann' | 'hamming' // Window function (default: 'hann')\n normalize?: boolean // Mean normalization (default: false)\n logScale?: boolean // Log scaling of mel energies (default: true)\n decodingOptions?: DecodingConfig // Audio decoding settings\n /** Optional start time in ms. If neither startTimeMs nor endTimeMs is set, defaults to 0. */\n startTimeMs?: number\n /** Optional end time in ms. Clamped so that the range does not exceed MAX_DURATION_MS (30 s). */\n endTimeMs?: number\n logger?: ConsoleLike\n}\n\n/**\n * Result type for WASM-based audio feature extraction.\n */\nexport interface AudioFeaturesWasmResult {\n spectralCentroid: number\n spectralFlatness: number\n spectralRolloff: number\n spectralBandwidth: number\n mfcc: number[]\n chromagram: number[]\n}\n\n/**\n * Return type for mel spectrogram extraction\n *\n * @experimental This feature is experimental and currently only available on Android.\n * The API may change in future versions.\n */\nexport interface MelSpectrogram {\n spectrogram: number[][] // 2D array [time][mel]\n sampleRate: number // Audio sample rate\n nMels: number // Number of mel filters\n timeSteps: number // Number of time frames\n durationMs: number // Audio duration in ms\n}\n"]}