@siteed/audio-studio 3.0.2 → 3.0.3

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 (86) hide show
  1. package/CHANGELOG.md +9 -1
  2. package/android/src/main/java/net/siteed/audiostudio/AudioStudioModule.kt +7 -1
  3. package/build/cjs/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
  4. package/build/cjs/AudioAnalysis/audioFeaturesWasm.js +10 -7
  5. package/build/cjs/AudioAnalysis/audioFeaturesWasm.js.map +1 -1
  6. package/build/cjs/AudioAnalysis/audioFeaturesWasm.web.js +78 -97
  7. package/build/cjs/AudioAnalysis/audioFeaturesWasm.web.js.map +1 -1
  8. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js +15 -12
  9. package/build/cjs/AudioAnalysis/extractAudioAnalysis.js.map +1 -1
  10. package/build/cjs/AudioAnalysis/extractAudioData.js +144 -2
  11. package/build/cjs/AudioAnalysis/extractAudioData.js.map +1 -1
  12. package/build/cjs/AudioAnalysis/melSpectrogramWasm.web.js +9 -56
  13. package/build/cjs/AudioAnalysis/melSpectrogramWasm.web.js.map +1 -1
  14. package/build/cjs/AudioAnalysis/wasmConfig.js +4 -4
  15. package/build/cjs/AudioAnalysis/wasmConfig.js.map +1 -1
  16. package/build/cjs/AudioAnalysis/wasmLoader.web.js +78 -0
  17. package/build/cjs/AudioAnalysis/wasmLoader.web.js.map +1 -0
  18. package/build/cjs/AudioStudioModule.js +4 -599
  19. package/build/cjs/AudioStudioModule.js.map +1 -1
  20. package/build/cjs/trimAudio.js +227 -0
  21. package/build/cjs/trimAudio.js.map +1 -1
  22. package/build/cjs/utils/encodeCompressedAudio.web.js +65 -0
  23. package/build/cjs/utils/encodeCompressedAudio.web.js.map +1 -0
  24. package/build/cjs/utils/resampleAudioBuffer.web.js +25 -0
  25. package/build/cjs/utils/resampleAudioBuffer.web.js.map +1 -0
  26. package/build/esm/AudioAnalysis/AudioAnalysis.types.js.map +1 -1
  27. package/build/esm/AudioAnalysis/audioFeaturesWasm.js +8 -5
  28. package/build/esm/AudioAnalysis/audioFeaturesWasm.js.map +1 -1
  29. package/build/esm/AudioAnalysis/audioFeaturesWasm.web.js +76 -62
  30. package/build/esm/AudioAnalysis/audioFeaturesWasm.web.js.map +1 -1
  31. package/build/esm/AudioAnalysis/extractAudioAnalysis.js +15 -12
  32. package/build/esm/AudioAnalysis/extractAudioAnalysis.js.map +1 -1
  33. package/build/esm/AudioAnalysis/extractAudioData.js +144 -2
  34. package/build/esm/AudioAnalysis/extractAudioData.js.map +1 -1
  35. package/build/esm/AudioAnalysis/melSpectrogramWasm.web.js +9 -23
  36. package/build/esm/AudioAnalysis/melSpectrogramWasm.web.js.map +1 -1
  37. package/build/esm/AudioAnalysis/wasmConfig.js +4 -4
  38. package/build/esm/AudioAnalysis/wasmConfig.js.map +1 -1
  39. package/build/esm/AudioAnalysis/wasmLoader.web.js +42 -0
  40. package/build/esm/AudioAnalysis/wasmLoader.web.js.map +1 -0
  41. package/build/esm/AudioStudioModule.js +4 -596
  42. package/build/esm/AudioStudioModule.js.map +1 -1
  43. package/build/esm/trimAudio.js +227 -0
  44. package/build/esm/trimAudio.js.map +1 -1
  45. package/build/esm/utils/encodeCompressedAudio.web.js +62 -0
  46. package/build/esm/utils/encodeCompressedAudio.web.js.map +1 -0
  47. package/build/esm/utils/resampleAudioBuffer.web.js +22 -0
  48. package/build/esm/utils/resampleAudioBuffer.web.js.map +1 -0
  49. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts +11 -0
  50. package/build/types/AudioAnalysis/AudioAnalysis.types.d.ts.map +1 -1
  51. package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts +5 -9
  52. package/build/types/AudioAnalysis/audioFeaturesWasm.d.ts.map +1 -1
  53. package/build/types/AudioAnalysis/audioFeaturesWasm.web.d.ts +35 -16
  54. package/build/types/AudioAnalysis/audioFeaturesWasm.web.d.ts.map +1 -1
  55. package/build/types/AudioAnalysis/extractAudioAnalysis.d.ts.map +1 -1
  56. package/build/types/AudioAnalysis/extractAudioData.d.ts +2 -2
  57. package/build/types/AudioAnalysis/extractAudioData.d.ts.map +1 -1
  58. package/build/types/AudioAnalysis/melSpectrogramWasm.web.d.ts.map +1 -1
  59. package/build/types/AudioAnalysis/wasmLoader.web.d.ts +3 -0
  60. package/build/types/AudioAnalysis/wasmLoader.web.d.ts.map +1 -0
  61. package/build/types/AudioStudioModule.d.ts.map +1 -1
  62. package/build/types/trimAudio.d.ts.map +1 -1
  63. package/build/types/utils/encodeCompressedAudio.web.d.ts +10 -0
  64. package/build/types/utils/encodeCompressedAudio.web.d.ts.map +1 -0
  65. package/build/types/utils/resampleAudioBuffer.web.d.ts +2 -0
  66. package/build/types/utils/resampleAudioBuffer.web.d.ts.map +1 -0
  67. package/package.json +1 -1
  68. package/src/AudioAnalysis/AudioAnalysis.types.ts +12 -0
  69. package/src/AudioAnalysis/audioFeaturesWasm.ts +17 -22
  70. package/src/AudioAnalysis/audioFeaturesWasm.web.ts +102 -94
  71. package/src/AudioAnalysis/extractAudioAnalysis.ts +23 -20
  72. package/src/AudioAnalysis/extractAudioData.ts +186 -4
  73. package/src/AudioAnalysis/melSpectrogramWasm.web.ts +10 -27
  74. package/src/AudioAnalysis/wasmConfig.ts +4 -4
  75. package/src/AudioAnalysis/wasmLoader.web.ts +48 -0
  76. package/src/AudioStudioModule.ts +6 -854
  77. package/src/trimAudio.ts +337 -0
  78. package/src/utils/encodeCompressedAudio.web.ts +78 -0
  79. package/src/utils/resampleAudioBuffer.web.ts +39 -0
  80. package/build/cjs/AudioAnalysis/extractWaveform.js +0 -18
  81. package/build/cjs/AudioAnalysis/extractWaveform.js.map +0 -1
  82. package/build/esm/AudioAnalysis/extractWaveform.js +0 -11
  83. package/build/esm/AudioAnalysis/extractWaveform.js.map +0 -1
  84. package/build/types/AudioAnalysis/extractWaveform.d.ts +0 -8
  85. package/build/types/AudioAnalysis/extractWaveform.d.ts.map +0 -1
  86. package/src/AudioAnalysis/extractWaveform.ts +0 -22
@@ -1,22 +1,14 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  const expo_modules_core_1 = require("expo-modules-core");
7
4
  const react_native_1 = require("react-native");
8
5
  const AudioStudio_web_1 = require("./AudioStudio.web");
9
- const audioProcessing_1 = require("./utils/audioProcessing");
10
- const crc32_1 = __importDefault(require("./utils/crc32"));
11
- const writeWavHeader_1 = require("./utils/writeWavHeader");
12
6
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
7
  let AudioStudioModule;
14
8
  if (react_native_1.Platform.OS === 'web') {
15
9
  let instance = null;
16
10
  AudioStudioModule = (webProps) => {
17
- if (!instance) {
18
- instance = new AudioStudio_web_1.AudioStudioWeb(webProps);
19
- }
11
+ instance ??= new AudioStudio_web_1.AudioStudioWeb(webProps);
20
12
  return instance;
21
13
  };
22
14
  AudioStudioModule.requestPermissionsAsync = async () => {
@@ -73,487 +65,12 @@ if (react_native_1.Platform.OS === 'web') {
73
65
  return await AudioStudioModule.requestPermissionsAsync();
74
66
  }
75
67
  };
76
- AudioStudioModule.extractAudioData = async (options) => {
77
- try {
78
- const { fileUri, position, length, startTimeMs, endTimeMs, decodingOptions, includeNormalizedData, includeBase64Data, includeWavHeader = false, logger, } = options;
79
- logger?.debug('EXTRACT AUDIO - Step 1: Initial request', {
80
- fileUri,
81
- extractionParams: {
82
- position,
83
- length,
84
- startTimeMs,
85
- endTimeMs,
86
- },
87
- decodingOptions: {
88
- targetSampleRate: decodingOptions?.targetSampleRate ?? 16000,
89
- targetChannels: decodingOptions?.targetChannels ?? 1,
90
- targetBitDepth: decodingOptions?.targetBitDepth ?? 16,
91
- normalizeAudio: decodingOptions?.normalizeAudio ?? false,
92
- },
93
- outputOptions: {
94
- includeNormalizedData,
95
- includeBase64Data,
96
- includeWavHeader,
97
- },
98
- });
99
- // Process the audio using shared helper function
100
- const processedBuffer = await (0, audioProcessing_1.processAudioBuffer)({
101
- fileUri,
102
- targetSampleRate: decodingOptions?.targetSampleRate ?? 16000,
103
- targetChannels: decodingOptions?.targetChannels ?? 1,
104
- normalizeAudio: decodingOptions?.normalizeAudio ?? false,
105
- position,
106
- length,
107
- startTimeMs,
108
- endTimeMs,
109
- logger,
110
- });
111
- logger?.debug('EXTRACT AUDIO - Step 2: Audio processing complete', {
112
- processedData: {
113
- samples: processedBuffer.samples,
114
- sampleRate: processedBuffer.sampleRate,
115
- channels: processedBuffer.channels,
116
- durationMs: processedBuffer.durationMs,
117
- },
118
- });
119
- const channelData = processedBuffer.channelData;
120
- const bitDepth = (decodingOptions?.targetBitDepth ?? 16);
121
- const bytesPerSample = bitDepth / 8;
122
- const numSamples = processedBuffer.samples;
123
- logger?.debug('EXTRACT AUDIO - Step 3: PCM conversion setup', {
124
- channelData: {
125
- length: channelData.length,
126
- first: channelData[0],
127
- last: channelData[channelData.length - 1],
128
- },
129
- calculation: {
130
- bitDepth,
131
- bytesPerSample,
132
- numSamples,
133
- expectedBytes: numSamples * bytesPerSample,
134
- },
135
- });
136
- // Create PCM data with correct length based on original byte length
137
- const pcmData = new Uint8Array(numSamples * bytesPerSample);
138
- let offset = 0;
139
- // Convert Float32 samples to PCM format
140
- for (let i = 0; i < numSamples; i++) {
141
- const sample = channelData[i];
142
- const value = Math.max(-1, Math.min(1, sample));
143
- // Convert to 16-bit signed integer
144
- let intValue = Math.round(value * 32767);
145
- // Handle negative values correctly
146
- if (intValue < 0) {
147
- intValue = 65536 + intValue;
148
- }
149
- // Write as little-endian
150
- pcmData[offset++] = intValue & 255; // Low byte
151
- pcmData[offset++] = (intValue >> 8) & 255; // High byte
152
- }
153
- const durationMs = Math.round((numSamples / processedBuffer.sampleRate) * 1000);
154
- logger?.debug('EXTRACT AUDIO - Step 4: Final output', {
155
- pcmData: {
156
- length: pcmData.length,
157
- first: pcmData[0],
158
- last: pcmData[pcmData.length - 1],
159
- },
160
- timing: {
161
- numSamples,
162
- sampleRate: processedBuffer.sampleRate,
163
- durationMs,
164
- shouldBe3000ms: endTimeMs
165
- ? endTimeMs - (startTimeMs ?? 0) === 3000
166
- : undefined,
167
- },
168
- });
169
- const result = {
170
- pcmData: new Uint8Array(pcmData.buffer),
171
- sampleRate: processedBuffer.sampleRate,
172
- channels: processedBuffer.channels,
173
- bitDepth,
174
- durationMs,
175
- format: `pcm_${bitDepth}bit`,
176
- samples: numSamples,
177
- };
178
- // Add WAV header if requested
179
- if (includeWavHeader) {
180
- logger?.debug('EXTRACT AUDIO - Step 4: Adding WAV header', {
181
- originalLength: pcmData.length,
182
- newLength: result.pcmData.length,
183
- firstBytes: Array.from(result.pcmData.slice(0, 44)), // WAV header is 44 bytes
184
- });
185
- const wavBuffer = (0, writeWavHeader_1.writeWavHeader)({
186
- buffer: pcmData.buffer.slice(0, pcmData.length),
187
- sampleRate: processedBuffer.sampleRate,
188
- numChannels: processedBuffer.channels,
189
- bitDepth,
190
- });
191
- result.pcmData = new Uint8Array(wavBuffer);
192
- result.hasWavHeader = true;
193
- }
194
- if (includeNormalizedData) {
195
- // // Simple approach: Create normalized data directly from the PCM data
196
- // // Just convert to -1 to 1 range without any amplification
197
- // const normalizedData = new Float32Array(numSamples)
198
- // // Convert the PCM data to float values
199
- // for (let i = 0; i < numSamples; i++) {
200
- // // Get the 16-bit PCM value (little endian)
201
- // const lowByte = pcmData[i * 2]
202
- // const highByte = pcmData[i * 2 + 1]
203
- // const pcmValue = (highByte << 8) | lowByte
204
- // // Convert to signed 16-bit value
205
- // const signedValue =
206
- // pcmValue > 32767 ? pcmValue - 65536 : pcmValue
207
- // // Normalize to float between -1 and 1
208
- // normalizedData[i] = signedValue / 32768.0
209
- // }
210
- // Store the normalized data in the result
211
- result.normalizedData = channelData;
212
- }
213
- if (includeBase64Data) {
214
- // Convert the PCM data to a base64 string
215
- const binary = Array.from(new Uint8Array(pcmData.buffer))
216
- .map((b) => String.fromCharCode(b))
217
- .join('');
218
- result.base64Data = btoa(binary);
219
- }
220
- if (options.computeChecksum) {
221
- result.checksum = crc32_1.default.buf(pcmData);
222
- }
223
- logger?.debug('EXTRACT AUDIO - Step 3: PCM conversion complete', {
224
- pcmStats: {
225
- length: pcmData.length,
226
- bytesPerSample,
227
- totalSamples: numSamples,
228
- firstBytes: Array.from(pcmData.slice(0, 16)),
229
- lastBytes: Array.from(pcmData.slice(-16)),
230
- },
231
- });
232
- return result;
233
- }
234
- catch (error) {
235
- options.logger?.error('EXTRACT AUDIO - Error:', error);
236
- throw error;
237
- }
238
- };
239
- AudioStudioModule.trimAudio = async (options) => {
240
- try {
241
- const startTime = performance.now();
242
- const { fileUri, mode = 'single', startTimeMs, endTimeMs, ranges, outputFileName, outputFormat, } = options;
243
- // Validate inputs
244
- if (!fileUri) {
245
- throw new Error('fileUri is required');
246
- }
247
- if (mode === 'single' &&
248
- startTimeMs === undefined &&
249
- endTimeMs === undefined) {
250
- throw new Error('At least one of startTimeMs or endTimeMs must be provided in single mode');
251
- }
252
- if ((mode === 'keep' || mode === 'remove') &&
253
- (!ranges || ranges.length === 0)) {
254
- throw new Error('ranges must be provided and non-empty for keep or remove modes');
255
- }
256
- // Create AudioContext
257
- const audioContext = new (window.AudioContext ||
258
- window.webkitAudioContext)();
259
- // First, load the entire audio file to get its properties
260
- const response = await fetch(fileUri);
261
- const arrayBuffer = await response.arrayBuffer();
262
- const originalAudioBuffer = await audioContext.decodeAudioData(arrayBuffer);
263
- // Get original audio properties
264
- const originalSampleRate = originalAudioBuffer.sampleRate;
265
- const originalChannels = originalAudioBuffer.numberOfChannels;
266
- // Add more detailed logging
267
- console.log(`Original audio details:`, {
268
- sampleRate: originalSampleRate,
269
- channels: originalChannels,
270
- duration: originalAudioBuffer.duration,
271
- length: originalAudioBuffer.length,
272
- // Log a few samples to verify content
273
- firstSamples: Array.from(originalAudioBuffer.getChannelData(0).slice(0, 5)),
274
- });
275
- // Determine output format - use original values as defaults if not specified
276
- let format = outputFormat?.format || 'wav';
277
- const targetSampleRate = outputFormat?.sampleRate || originalSampleRate;
278
- const targetChannels = outputFormat?.channels || originalChannels;
279
- const targetBitDepth = outputFormat?.bitDepth || 16;
280
- // Get file info from the URL
281
- const filename = outputFileName ||
282
- fileUri.split('/').pop() ||
283
- 'trimmed-audio.wav';
284
- // Process based on mode
285
- let resultBuffer;
286
- // Report initial progress
287
- AudioStudioModule.sendEvent('TrimProgress', {
288
- progress: 10,
289
- });
290
- if (mode === 'single') {
291
- // Single mode: extract a single range
292
- // Use original sample rate and channels for extraction to preserve quality
293
- const { buffer } = await (0, audioProcessing_1.processAudioBuffer)({
294
- fileUri,
295
- targetSampleRate, // Use the requested sample rate
296
- targetChannels,
297
- normalizeAudio: false,
298
- startTimeMs,
299
- endTimeMs,
300
- audioContext,
301
- });
302
- console.log(`Processed buffer details:`, {
303
- sampleRate: buffer.sampleRate,
304
- channels: buffer.numberOfChannels,
305
- duration: buffer.duration,
306
- length: buffer.length,
307
- // Log a few samples to verify content
308
- firstSamples: Array.from(buffer.getChannelData(0).slice(0, 5)),
309
- });
310
- resultBuffer = buffer;
311
- // If we need to change sample rate or channels, do it after extraction
312
- if (targetSampleRate !== originalSampleRate ||
313
- targetChannels !== originalChannels) {
314
- console.log(`Resampling from ${originalSampleRate}Hz to ${targetSampleRate}Hz`);
315
- resultBuffer = await resampleAudioBuffer(audioContext, buffer, targetSampleRate, targetChannels);
316
- }
317
- }
318
- else {
319
- // For keep or remove modes
320
- const fullDuration = originalAudioBuffer.duration * 1000; // in ms
321
- let segmentsToProcess = [];
322
- if (mode === 'keep') {
323
- // For keep mode, use the ranges directly
324
- segmentsToProcess = ranges;
325
- }
326
- else {
327
- // mode === 'remove'
328
- // For remove mode, invert the ranges
329
- const sortedRanges = [...ranges].sort((a, b) => a.startTimeMs - b.startTimeMs);
330
- // Add segment from start to first range if needed
331
- if (sortedRanges.length > 0 &&
332
- sortedRanges[0].startTimeMs > 0) {
333
- segmentsToProcess.push({
334
- startTimeMs: 0,
335
- endTimeMs: sortedRanges[0].startTimeMs,
336
- });
337
- }
338
- // Add segments between ranges
339
- for (let i = 0; i < sortedRanges.length - 1; i++) {
340
- segmentsToProcess.push({
341
- startTimeMs: sortedRanges[i].endTimeMs,
342
- endTimeMs: sortedRanges[i + 1].startTimeMs,
343
- });
344
- }
345
- // Add segment from last range to end if needed
346
- if (sortedRanges.length > 0 &&
347
- sortedRanges[sortedRanges.length - 1].endTimeMs <
348
- fullDuration) {
349
- segmentsToProcess.push({
350
- startTimeMs: sortedRanges[sortedRanges.length - 1].endTimeMs,
351
- endTimeMs: fullDuration,
352
- });
353
- }
354
- }
355
- // Filter out empty or invalid segments
356
- segmentsToProcess = segmentsToProcess.filter((segment) => segment.startTimeMs < segment.endTimeMs &&
357
- segment.endTimeMs - segment.startTimeMs > 1); // 1ms minimum
358
- if (segmentsToProcess.length === 0) {
359
- throw new Error('No valid segments to process after filtering ranges');
360
- }
361
- // Process each segment using original sample rate and channels
362
- const segmentBuffers = [];
363
- for (let i = 0; i < segmentsToProcess.length; i++) {
364
- const segment = segmentsToProcess[i];
365
- // Report progress for each segment
366
- AudioStudioModule.sendEvent('TrimProgress', {
367
- progress: 10 +
368
- Math.round((i / segmentsToProcess.length) * 40),
369
- });
370
- // Use processAudioBuffer to extract this segment
371
- const { buffer: segmentBuffer } = await (0, audioProcessing_1.processAudioBuffer)({
372
- fileUri,
373
- targetSampleRate: originalSampleRate, // Use original sample rate
374
- targetChannels: originalChannels, // Use original channels
375
- normalizeAudio: false,
376
- startTimeMs: segment.startTimeMs,
377
- endTimeMs: segment.endTimeMs,
378
- audioContext,
379
- });
380
- segmentBuffers.push(segmentBuffer);
381
- }
382
- // Concatenate all segments
383
- const totalSamples = segmentBuffers.reduce((sum, buffer) => sum + buffer.length, 0);
384
- // Create buffer with original properties first
385
- const concatenatedBuffer = audioContext.createBuffer(originalChannels, totalSamples, originalSampleRate);
386
- let offset = 0;
387
- for (const segmentBuffer of segmentBuffers) {
388
- for (let channel = 0; channel < originalChannels; channel++) {
389
- const outputData = concatenatedBuffer.getChannelData(channel);
390
- const segmentData = segmentBuffer.getChannelData(channel);
391
- for (let i = 0; i < segmentBuffer.length; i++) {
392
- outputData[offset + i] = segmentData[i];
393
- }
394
- }
395
- offset += segmentBuffer.length;
396
- }
397
- resultBuffer = concatenatedBuffer;
398
- // If we need to change sample rate or channels, do it after concatenation
399
- if (targetSampleRate !== originalSampleRate ||
400
- targetChannels !== originalChannels) {
401
- console.log(`Resampling concatenated buffer from ${originalSampleRate}Hz to ${targetSampleRate}Hz`);
402
- resultBuffer = await resampleAudioBuffer(audioContext, concatenatedBuffer, targetSampleRate, targetChannels);
403
- }
404
- }
405
- // Report progress (50% - processing complete)
406
- AudioStudioModule.sendEvent('TrimProgress', {
407
- progress: 50,
408
- });
409
- // Encode the result based on the requested format
410
- let outputData;
411
- let outputMimeType;
412
- let compressionInfo = null;
413
- // Check if AAC was requested on web and show a warning
414
- if (format === 'aac' && react_native_1.Platform.OS === 'web') {
415
- console.warn('AAC format is not supported on web platforms. Falling back to OPUS format.');
416
- format = 'opus';
417
- }
418
- if (format === 'wav') {
419
- // Create a properly interleaved buffer for WAV format
420
- // For WAV, we need to convert Float32Array to Int16Array (for 16-bit audio)
421
- const numSamples = resultBuffer.length * resultBuffer.numberOfChannels;
422
- const interleavedData = new Int16Array(numSamples);
423
- // Log detailed information about the buffer before encoding
424
- console.log(`Creating WAV file:`, {
425
- bufferSampleRate: resultBuffer.sampleRate,
426
- bufferChannels: resultBuffer.numberOfChannels,
427
- bufferLength: resultBuffer.length,
428
- targetSampleRate,
429
- targetChannels,
430
- targetBitDepth,
431
- // Log a few samples to verify content
432
- firstSamples: Array.from(resultBuffer.getChannelData(0).slice(0, 5)),
433
- });
434
- // Interleave channels properly
435
- for (let i = 0; i < resultBuffer.length; i++) {
436
- for (let channel = 0; channel < resultBuffer.numberOfChannels; channel++) {
437
- // Convert float (-1.0 to 1.0) to int16 (-32768 to 32767)
438
- const floatSample = resultBuffer.getChannelData(channel)[i];
439
- // Clamp the value to -1.0 to 1.0
440
- const clampedSample = Math.max(-1.0, Math.min(1.0, floatSample));
441
- // Convert to int16
442
- const intSample = Math.round(clampedSample * 32767);
443
- // Store in interleaved buffer
444
- interleavedData[i * resultBuffer.numberOfChannels + channel] = intSample;
445
- }
446
- }
447
- // Convert Int16Array to ArrayBuffer for WAV header
448
- const rawBuffer = interleavedData.buffer;
449
- // IMPORTANT: Make sure we're using the ACTUAL sample rate of the buffer
450
- // not just what was requested in the options
451
- console.log(`Creating WAV with ${resultBuffer.numberOfChannels} channels at ${resultBuffer.sampleRate}Hz`);
452
- outputData = (0, writeWavHeader_1.writeWavHeader)({
453
- buffer: rawBuffer,
454
- sampleRate: resultBuffer.sampleRate, // Use the actual buffer's sample rate
455
- numChannels: resultBuffer.numberOfChannels,
456
- bitDepth: targetBitDepth,
457
- });
458
- outputMimeType = 'audio/wav';
459
- }
460
- else if (format === 'opus' || format === 'aac') {
461
- try {
462
- // Try to use MediaRecorder for compressed formats
463
- const { data, bitrate } = await encodeCompressedAudio(resultBuffer, format, outputFormat?.bitrate);
464
- outputData = data;
465
- outputMimeType =
466
- format === 'opus' ? 'audio/webm' : 'audio/aac';
467
- compressionInfo = {
468
- format,
469
- bitrate,
470
- size: data.byteLength,
471
- };
472
- }
473
- catch (error) {
474
- console.warn(`Failed to encode to ${format}, falling back to WAV: ${error}`);
475
- // Same WAV encoding as above
476
- const wavData = new Float32Array(resultBuffer.length * resultBuffer.numberOfChannels);
477
- for (let i = 0; i < resultBuffer.length; i++) {
478
- for (let channel = 0; channel < resultBuffer.numberOfChannels; channel++) {
479
- wavData[i * resultBuffer.numberOfChannels + channel] = resultBuffer.getChannelData(channel)[i];
480
- }
481
- }
482
- outputData = (0, writeWavHeader_1.writeWavHeader)({
483
- buffer: wavData.buffer,
484
- sampleRate: resultBuffer.sampleRate,
485
- numChannels: resultBuffer.numberOfChannels,
486
- bitDepth: targetBitDepth,
487
- });
488
- outputMimeType = 'audio/wav';
489
- }
490
- }
491
- else {
492
- // Default to WAV for unsupported formats
493
- console.warn(`Format ${format} not supported on web, using WAV instead`);
494
- // Same WAV encoding as above
495
- const wavData = new Float32Array(resultBuffer.length * resultBuffer.numberOfChannels);
496
- for (let i = 0; i < resultBuffer.length; i++) {
497
- for (let channel = 0; channel < resultBuffer.numberOfChannels; channel++) {
498
- wavData[i * resultBuffer.numberOfChannels + channel] =
499
- resultBuffer.getChannelData(channel)[i];
500
- }
501
- }
502
- outputData = (0, writeWavHeader_1.writeWavHeader)({
503
- buffer: wavData.buffer,
504
- sampleRate: resultBuffer.sampleRate,
505
- numChannels: resultBuffer.numberOfChannels,
506
- bitDepth: targetBitDepth,
507
- });
508
- outputMimeType = 'audio/wav';
509
- }
510
- // Report progress (90% - encoding complete)
511
- AudioStudioModule.sendEvent('TrimProgress', {
512
- progress: 90,
513
- });
514
- // Create a blob and URL for the result
515
- const blob = new Blob([outputData], { type: outputMimeType });
516
- const outputUri = URL.createObjectURL(blob);
517
- // Calculate processing time
518
- const processingTimeMs = performance.now() - startTime;
519
- // Report progress (100% - complete)
520
- AudioStudioModule.sendEvent('TrimProgress', {
521
- progress: 100,
522
- });
523
- // Create result object
524
- const result = {
525
- uri: outputUri,
526
- filename,
527
- durationMs: Math.round(resultBuffer.duration * 1000),
528
- size: outputData.byteLength,
529
- sampleRate: resultBuffer.sampleRate,
530
- channels: resultBuffer.numberOfChannels,
531
- bitDepth: targetBitDepth,
532
- mimeType: outputMimeType,
533
- processingInfo: {
534
- durationMs: processingTimeMs,
535
- },
536
- };
537
- // Add compression info if available
538
- if (compressionInfo) {
539
- result.compression = compressionInfo;
540
- }
541
- return result;
542
- }
543
- catch (error) {
544
- console.error('Error in trimAudio:', error);
545
- throw error;
546
- }
547
- };
548
68
  // Add a sendEvent method for web
549
69
  AudioStudioModule.sendEvent = (eventName, params) => {
550
70
  // This will be picked up by the LegacyEventEmitter in trimAudio.ts
551
- if (AudioStudioModule.listeners &&
552
- AudioStudioModule.listeners[eventName]) {
553
- AudioStudioModule.listeners[eventName].forEach((listener) => {
554
- listener(params);
555
- });
556
- }
71
+ AudioStudioModule.listeners?.[eventName]?.forEach((listener) => {
72
+ listener(params);
73
+ });
557
74
  };
558
75
  // Initialize listeners object
559
76
  AudioStudioModule.listeners = {};
@@ -599,118 +116,6 @@ if (react_native_1.Platform.OS === 'web') {
599
116
  }
600
117
  };
601
118
  }
602
- // Move the encodeCompressedAudio function outside the if block to fix the ESLint error
603
- async function encodeCompressedAudio(buffer, format, bitrate) {
604
- return new Promise((resolve, reject) => {
605
- try {
606
- // On web, always use opus if aac is requested
607
- const actualFormat = react_native_1.Platform.OS === 'web' && format === 'aac' ? 'opus' : format;
608
- // Check if MediaRecorder supports the requested format
609
- const mimeType = actualFormat === 'opus' ? 'audio/webm;codecs=opus' : 'audio/aac';
610
- if (!MediaRecorder.isTypeSupported(mimeType)) {
611
- throw new Error(`MediaRecorder does not support ${mimeType}`);
612
- }
613
- // Create a new AudioContext and source
614
- const ctx = new (window.AudioContext ||
615
- window.webkitAudioContext)();
616
- const source = ctx.createBufferSource();
617
- source.buffer = buffer;
618
- // Create a MediaStreamDestination to capture the audio
619
- const destination = ctx.createMediaStreamDestination();
620
- source.connect(destination);
621
- // Create a MediaRecorder with the requested format
622
- const recorder = new MediaRecorder(destination.stream, {
623
- mimeType,
624
- audioBitsPerSecond: bitrate || (actualFormat === 'opus' ? 32000 : 64000),
625
- });
626
- const chunks = [];
627
- recorder.ondataavailable = (e) => {
628
- if (e.data.size > 0) {
629
- chunks.push(e.data);
630
- }
631
- };
632
- recorder.onstop = async () => {
633
- try {
634
- const blob = new Blob(chunks, { type: mimeType });
635
- const arrayBuffer = await blob.arrayBuffer();
636
- // Get the actual bitrate used
637
- const actualBitrate = Math.round((arrayBuffer.byteLength * 8) / buffer.duration);
638
- resolve({
639
- data: arrayBuffer,
640
- bitrate: actualBitrate / 1000, // Convert to kbps
641
- });
642
- // Clean up
643
- ctx.close();
644
- }
645
- catch (error) {
646
- reject(error);
647
- }
648
- };
649
- // Start recording and playback
650
- recorder.start();
651
- source.start(0);
652
- // Stop recording when the buffer finishes playing
653
- setTimeout(() => {
654
- recorder.stop();
655
- source.stop();
656
- }, buffer.duration * 1000);
657
- }
658
- catch (error) {
659
- reject(error);
660
- }
661
- });
662
- }
663
- // Improved resampleAudioBuffer function
664
- async function resampleAudioBuffer(context, buffer, targetSampleRate, targetChannels) {
665
- // If no change needed, return the original buffer
666
- if (buffer.sampleRate === targetSampleRate &&
667
- buffer.numberOfChannels === targetChannels) {
668
- return buffer;
669
- }
670
- console.log(`Resampling: ${buffer.sampleRate}Hz → ${targetSampleRate}Hz, ${buffer.numberOfChannels} → ${targetChannels} channels`);
671
- // Calculate the new length based on the sample rate change
672
- const newLength = Math.round((buffer.length * targetSampleRate) / buffer.sampleRate);
673
- // Create an offline context for resampling
674
- const offlineContext = new OfflineAudioContext(targetChannels, newLength, targetSampleRate);
675
- // Create a source node
676
- const source = offlineContext.createBufferSource();
677
- source.buffer = buffer;
678
- // If we need to change channel count
679
- if (buffer.numberOfChannels !== targetChannels) {
680
- if (targetChannels === 1 && buffer.numberOfChannels > 1) {
681
- // Downmix to mono
682
- const merger = offlineContext.createChannelMerger(1);
683
- // Create a gain node to reduce volume when downmixing to prevent clipping
684
- const gainNode = offlineContext.createGain();
685
- gainNode.gain.value = 1.0 / buffer.numberOfChannels;
686
- source.connect(gainNode);
687
- gainNode.connect(merger);
688
- merger.connect(offlineContext.destination);
689
- }
690
- else if (targetChannels === 2 && buffer.numberOfChannels === 1) {
691
- // Upmix mono to stereo (duplicate the channel)
692
- const splitter = offlineContext.createChannelSplitter(1);
693
- const merger = offlineContext.createChannelMerger(2);
694
- source.connect(splitter);
695
- splitter.connect(merger, 0, 0);
696
- splitter.connect(merger, 0, 1);
697
- merger.connect(offlineContext.destination);
698
- }
699
- else {
700
- // For other cases, just connect and let the system handle it
701
- source.connect(offlineContext.destination);
702
- }
703
- }
704
- else {
705
- // No channel conversion needed
706
- source.connect(offlineContext.destination);
707
- }
708
- // Start rendering
709
- source.start(0);
710
- const resampledBuffer = await offlineContext.startRendering();
711
- console.log(`Resampling complete: ${resampledBuffer.length} samples at ${resampledBuffer.sampleRate}Hz`);
712
- return resampledBuffer;
713
- }
714
119
  if (react_native_1.Platform.OS !== 'web') {
715
120
  AudioStudioModule = (0, expo_modules_core_1.requireNativeModule)('AudioStudio');
716
121
  }