@siteed/expo-audio-stream 2.1.0 → 2.2.1-beta.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/README.md +23 -260
- package/build/index.d.ts +11 -15
- package/build/index.js +54 -14
- package/build/src/index.d.ts +11 -0
- package/build/src/index.js +54 -0
- package/package.json +49 -110
- package/src/index.ts +18 -32
- package/CHANGELOG.md +0 -206
- package/android/build.gradle +0 -105
- package/android/src/main/AndroidManifest.xml +0 -27
- package/android/src/main/java/net/siteed/audiostream/AudioAnalysisData.kt +0 -166
- package/android/src/main/java/net/siteed/audiostream/AudioDataEncoder.kt +0 -9
- package/android/src/main/java/net/siteed/audiostream/AudioFileHandler.kt +0 -131
- package/android/src/main/java/net/siteed/audiostream/AudioFormatUtils.kt +0 -103
- package/android/src/main/java/net/siteed/audiostream/AudioNotificationsManager.kt +0 -435
- package/android/src/main/java/net/siteed/audiostream/AudioProcessor.kt +0 -2235
- package/android/src/main/java/net/siteed/audiostream/AudioRecorderManager.kt +0 -1437
- package/android/src/main/java/net/siteed/audiostream/AudioRecordingService.kt +0 -152
- package/android/src/main/java/net/siteed/audiostream/AudioTrimmer.kt +0 -1099
- package/android/src/main/java/net/siteed/audiostream/Constants.kt +0 -21
- package/android/src/main/java/net/siteed/audiostream/EventSender.kt +0 -7
- package/android/src/main/java/net/siteed/audiostream/ExpoAudioStreamModule.kt +0 -739
- package/android/src/main/java/net/siteed/audiostream/FFT.kt +0 -99
- package/android/src/main/java/net/siteed/audiostream/Features.kt +0 -98
- package/android/src/main/java/net/siteed/audiostream/NotificationConfig.kt +0 -70
- package/android/src/main/java/net/siteed/audiostream/PermissionUtils.kt +0 -59
- package/android/src/main/java/net/siteed/audiostream/RecordingActionReceiver.kt +0 -59
- package/android/src/main/java/net/siteed/audiostream/RecordingConfig.kt +0 -205
- package/android/src/main/java/net/siteed/audiostream/WaveformConfig.kt +0 -19
- package/android/src/main/java/net/siteed/audiostream/WaveformRenderer.kt +0 -159
- package/android/src/main/res/drawable/ic_default_action_icon.xml +0 -16
- package/android/src/main/res/drawable/ic_microphone.xml +0 -13
- package/android/src/main/res/drawable/ic_pause.xml +0 -10
- package/android/src/main/res/drawable/ic_play.xml +0 -10
- package/android/src/main/res/drawable/ic_stop.xml +0 -10
- package/android/src/main/res/layout/notification_recording.xml +0 -37
- package/android/src/main/test/java/net/siteed/audiostream/AudioProcessorTest.kt +0 -56
- package/app.plugin.js +0 -1
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts +0 -179
- package/build/AudioAnalysis/AudioAnalysis.types.d.ts.map +0 -1
- package/build/AudioAnalysis/AudioAnalysis.types.js +0 -3
- package/build/AudioAnalysis/AudioAnalysis.types.js.map +0 -1
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts +0 -68
- package/build/AudioAnalysis/extractAudioAnalysis.d.ts.map +0 -1
- package/build/AudioAnalysis/extractAudioAnalysis.js +0 -203
- package/build/AudioAnalysis/extractAudioAnalysis.js.map +0 -1
- package/build/AudioAnalysis/extractAudioData.d.ts +0 -3
- package/build/AudioAnalysis/extractAudioData.d.ts.map +0 -1
- package/build/AudioAnalysis/extractAudioData.js +0 -5
- package/build/AudioAnalysis/extractAudioData.js.map +0 -1
- package/build/AudioAnalysis/extractMelSpectrogram.d.ts +0 -14
- package/build/AudioAnalysis/extractMelSpectrogram.d.ts.map +0 -1
- package/build/AudioAnalysis/extractMelSpectrogram.js +0 -85
- package/build/AudioAnalysis/extractMelSpectrogram.js.map +0 -1
- package/build/AudioAnalysis/extractPreview.d.ts +0 -11
- package/build/AudioAnalysis/extractPreview.d.ts.map +0 -1
- package/build/AudioAnalysis/extractPreview.js +0 -25
- package/build/AudioAnalysis/extractPreview.js.map +0 -1
- package/build/AudioAnalysis/extractWaveform.d.ts +0 -8
- package/build/AudioAnalysis/extractWaveform.d.ts.map +0 -1
- package/build/AudioAnalysis/extractWaveform.js +0 -11
- package/build/AudioAnalysis/extractWaveform.js.map +0 -1
- package/build/AudioRecorder.provider.d.ts +0 -11
- package/build/AudioRecorder.provider.d.ts.map +0 -1
- package/build/AudioRecorder.provider.js +0 -37
- package/build/AudioRecorder.provider.js.map +0 -1
- package/build/ExpoAudioStream.native.d.ts +0 -3
- package/build/ExpoAudioStream.native.d.ts.map +0 -1
- package/build/ExpoAudioStream.native.js +0 -6
- package/build/ExpoAudioStream.native.js.map +0 -1
- package/build/ExpoAudioStream.types.d.ts +0 -532
- package/build/ExpoAudioStream.types.d.ts.map +0 -1
- package/build/ExpoAudioStream.types.js +0 -2
- package/build/ExpoAudioStream.types.js.map +0 -1
- package/build/ExpoAudioStream.web.d.ts +0 -59
- package/build/ExpoAudioStream.web.d.ts.map +0 -1
- package/build/ExpoAudioStream.web.js +0 -285
- package/build/ExpoAudioStream.web.js.map +0 -1
- package/build/ExpoAudioStreamModule.d.ts +0 -3
- package/build/ExpoAudioStreamModule.d.ts.map +0 -1
- package/build/ExpoAudioStreamModule.js +0 -693
- package/build/ExpoAudioStreamModule.js.map +0 -1
- package/build/WebRecorder.web.d.ts +0 -119
- package/build/WebRecorder.web.d.ts.map +0 -1
- package/build/WebRecorder.web.js +0 -436
- package/build/WebRecorder.web.js.map +0 -1
- package/build/constants.d.ts +0 -11
- package/build/constants.d.ts.map +0 -1
- package/build/constants.js +0 -14
- package/build/constants.js.map +0 -1
- package/build/events.d.ts +0 -26
- package/build/events.d.ts.map +0 -1
- package/build/events.js +0 -21
- package/build/events.js.map +0 -1
- package/build/index.d.ts.map +0 -1
- package/build/index.js.map +0 -1
- package/build/trimAudio.d.ts +0 -25
- package/build/trimAudio.d.ts.map +0 -1
- package/build/trimAudio.js +0 -67
- package/build/trimAudio.js.map +0 -1
- package/build/useAudioRecorder.d.ts +0 -21
- package/build/useAudioRecorder.d.ts.map +0 -1
- package/build/useAudioRecorder.js +0 -427
- package/build/useAudioRecorder.js.map +0 -1
- package/build/utils/BlobFix.d.ts +0 -9
- package/build/utils/BlobFix.d.ts.map +0 -1
- package/build/utils/BlobFix.js +0 -498
- package/build/utils/BlobFix.js.map +0 -1
- package/build/utils/audioProcessing.d.ts +0 -24
- package/build/utils/audioProcessing.d.ts.map +0 -1
- package/build/utils/audioProcessing.js +0 -133
- package/build/utils/audioProcessing.js.map +0 -1
- package/build/utils/concatenateBuffers.d.ts +0 -8
- package/build/utils/concatenateBuffers.d.ts.map +0 -1
- package/build/utils/concatenateBuffers.js +0 -21
- package/build/utils/concatenateBuffers.js.map +0 -1
- package/build/utils/convertPCMToFloat32.d.ts +0 -13
- package/build/utils/convertPCMToFloat32.d.ts.map +0 -1
- package/build/utils/convertPCMToFloat32.js +0 -120
- package/build/utils/convertPCMToFloat32.js.map +0 -1
- package/build/utils/encodingToBitDepth.d.ts +0 -5
- package/build/utils/encodingToBitDepth.d.ts.map +0 -1
- package/build/utils/encodingToBitDepth.js +0 -13
- package/build/utils/encodingToBitDepth.js.map +0 -1
- package/build/utils/getWavFileInfo.d.ts +0 -26
- package/build/utils/getWavFileInfo.d.ts.map +0 -1
- package/build/utils/getWavFileInfo.js +0 -92
- package/build/utils/getWavFileInfo.js.map +0 -1
- package/build/utils/writeWavHeader.d.ts +0 -49
- package/build/utils/writeWavHeader.d.ts.map +0 -1
- package/build/utils/writeWavHeader.js +0 -91
- package/build/utils/writeWavHeader.js.map +0 -1
- package/build/workers/InlineFeaturesExtractor.web.d.ts +0 -2
- package/build/workers/InlineFeaturesExtractor.web.d.ts.map +0 -1
- package/build/workers/InlineFeaturesExtractor.web.js +0 -828
- package/build/workers/InlineFeaturesExtractor.web.js.map +0 -1
- package/build/workers/inlineAudioWebWorker.web.d.ts +0 -2
- package/build/workers/inlineAudioWebWorker.web.d.ts.map +0 -1
- package/build/workers/inlineAudioWebWorker.web.js +0 -157
- package/build/workers/inlineAudioWebWorker.web.js.map +0 -1
- package/expo-module.config.json +0 -9
- package/ios/AudioAnalysisData.swift +0 -74
- package/ios/AudioNotificationManager.swift +0 -135
- package/ios/AudioProcessingHelpers.swift +0 -743
- package/ios/AudioProcessor.swift +0 -1313
- package/ios/AudioStreamError.swift +0 -7
- package/ios/AudioStreamManager.swift +0 -1708
- package/ios/AudioStreamManagerDelegate.swift +0 -16
- package/ios/DataPoint.swift +0 -54
- package/ios/DecodingConfig.swift +0 -47
- package/ios/ExpoAudioStream.podspec +0 -27
- package/ios/ExpoAudioStreamModule.swift +0 -805
- package/ios/FFT.swift +0 -62
- package/ios/Features.swift +0 -95
- package/ios/Logger.swift +0 -7
- package/ios/NotificationExtension.swift +0 -15
- package/ios/RecordingResult.swift +0 -22
- package/ios/RecordingSettings.swift +0 -265
- package/ios/WaveformExtractor.swift +0 -105
- package/plugin/build/index.d.ts +0 -21
- package/plugin/build/index.js +0 -191
- package/plugin/src/index.ts +0 -278
- package/plugin/tsconfig.json +0 -10
- package/plugin/tsconfig.tsbuildinfo +0 -1
- package/src/AudioAnalysis/AudioAnalysis.types.ts +0 -202
- package/src/AudioAnalysis/extractAudioAnalysis.ts +0 -333
- package/src/AudioAnalysis/extractAudioData.ts +0 -6
- package/src/AudioAnalysis/extractMelSpectrogram.ts +0 -144
- package/src/AudioAnalysis/extractPreview.ts +0 -34
- package/src/AudioAnalysis/extractWaveform.ts +0 -22
- package/src/AudioRecorder.provider.tsx +0 -54
- package/src/ExpoAudioStream.native.ts +0 -6
- package/src/ExpoAudioStream.types.ts +0 -641
- package/src/ExpoAudioStream.web.ts +0 -359
- package/src/ExpoAudioStreamModule.ts +0 -967
- package/src/WebRecorder.web.ts +0 -580
- package/src/constants.ts +0 -18
- package/src/events.ts +0 -60
- package/src/trimAudio.ts +0 -90
- package/src/useAudioRecorder.tsx +0 -620
- package/src/utils/BlobFix.ts +0 -559
- package/src/utils/audioProcessing.ts +0 -205
- package/src/utils/concatenateBuffers.ts +0 -24
- package/src/utils/convertPCMToFloat32.ts +0 -170
- package/src/utils/encodingToBitDepth.ts +0 -18
- package/src/utils/getWavFileInfo.ts +0 -132
- package/src/utils/writeWavHeader.ts +0 -114
- package/src/workers/InlineFeaturesExtractor.web.tsx +0 -827
- package/src/workers/inlineAudioWebWorker.web.tsx +0 -156
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"InlineFeaturesExtractor.web.js","sourceRoot":"","sources":["../../src/workers/InlineFeaturesExtractor.web.tsx"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyzBtC,CAAA","sourcesContent":["// packages/expo-audio-stream/src/workers/InlineFeaturesExtractor.web.tsx\nexport const InlineFeaturesExtractor = `\n// Constants\nconst N_FFT = 1024; // Default FFT size\nconst MAX_FFT_SIZE = 8192; // Maximum FFT size to prevent memory issues\nconst N_CHROMA = 12;\n\n// FFT Implementation with normalized Hann window\nfunction FFT(n) {\n this.n = n;\n this.cosTable = new Float32Array(n / 2);\n this.sinTable = new Float32Array(n / 2);\n this.hannWindow = new Float32Array(n);\n \n // Match Android implementation with precomputed tables\n const normalizationFactor = Math.sqrt(2.0 / n);\n for (var i = 0; i < n / 2; i++) {\n this.cosTable[i] = Math.cos(2.0 * Math.PI * i / n);\n this.sinTable[i] = Math.sin(2.0 * Math.PI * i / n);\n }\n \n // Precompute normalized Hann window to match Android\n for (var i = 0; i < n; i++) {\n this.hannWindow[i] = normalizationFactor * 0.5 * (1 - Math.cos(2.0 * Math.PI * i / (n - 1)));\n }\n}\n\nFFT.prototype.transform = function(data) {\n const n = data.length;\n \n // Validate input length is power of 2\n if ((n & (n - 1)) !== 0) {\n throw new Error('FFT length must be power of 2');\n }\n\n // Use iterative bit reversal instead of recursive\n const bitReversedIndices = new Uint32Array(n);\n for (let i = 0; i < n; i++) {\n let reversed = 0;\n let j = i;\n let bits = Math.log2(n);\n while (bits--) {\n reversed = (reversed << 1) | (j & 1);\n j >>= 1;\n }\n bitReversedIndices[i] = reversed;\n }\n\n // Apply bit reversal\n for (let i = 0; i < n; i++) {\n const j = bitReversedIndices[i];\n if (i < j) {\n const temp = data[i];\n data[i] = data[j];\n data[j] = temp;\n }\n }\n\n // Iterative FFT computation with optimized memory usage\n for (let step = 1; step < n; step <<= 1) {\n const jump = step << 1;\n const angleStep = Math.PI / step;\n\n for (let group = 0; group < n; group += jump) {\n for (let pair = group; pair < group + step; pair++) {\n const match = pair + step;\n const angle = angleStep * (pair - group);\n \n const currentCos = Math.cos(angle);\n const currentSin = Math.sin(angle);\n\n const real = currentCos * data[match] - currentSin * data[match + 1];\n const imag = currentCos * data[match + 1] + currentSin * data[match];\n\n data[match] = data[pair] - real;\n data[match + 1] = data[pair + 1] - imag;\n data[pair] += real;\n data[pair + 1] += imag;\n }\n }\n }\n};\n\n// Add realInverse method\nFFT.prototype.realInverse = function(powerSpectrum, output) {\n const n = powerSpectrum.length;\n const complexData = new Float32Array(n * 2);\n \n // Copy power spectrum to complex format\n for (let i = 0; i < n/2 + 1; i++) {\n complexData[2 * i] = powerSpectrum[i];\n if (2 * i + 1 < complexData.length) {\n complexData[2 * i + 1] = 0;\n }\n }\n \n // Conjugate for inverse FFT\n for (let i = 0; i < n; i++) {\n if (2 * i + 1 < complexData.length) {\n complexData[2 * i + 1] = -complexData[2 * i + 1];\n }\n }\n \n this.transform(complexData);\n \n // Copy real part to output and scale\n for (let i = 0; i < n; i++) {\n output[i] = complexData[2 * i] / n;\n }\n};\n\n// Add helper functions to match Android\nfunction nextPowerOfTwo(n) {\n let value = 1;\n while (value < n) {\n value *= 2;\n }\n return value;\n}\n\nfunction applyHannWindow(samples) {\n const output = new Float32Array(samples.length);\n for (let i = 0; i < samples.length; i++) {\n const multiplier = 0.5 * (1 - Math.cos(2 * Math.PI * i / (samples.length - 1)));\n output[i] = samples[i] * multiplier;\n }\n return output;\n}\n\n// Update spectral feature computation to match Android\nfunction computeSpectralFeatures(segment, sampleRate, featureOptions = {}) {\n try {\n // Early return if no spectral features are requested\n if (!featureOptions.spectralCentroid && \n !featureOptions.spectralFlatness && \n !featureOptions.spectralRollOff && \n !featureOptions.spectralBandwidth &&\n !featureOptions.magnitudeSpectrum) {\n return {\n centroid: 0,\n flatness: 0,\n rollOff: 0,\n bandwidth: 0,\n magnitudeSpectrum: []\n };\n }\n\n // Ensure we have valid data\n if (!segment || segment.length === 0) {\n throw new Error('Invalid segment data');\n }\n\n // Process in fixed-size chunks\n const chunkSize = N_FFT;\n const numChunks = Math.ceil(segment.length / chunkSize);\n \n let results = {\n centroid: 0,\n flatness: 0,\n rollOff: 0,\n bandwidth: 0,\n magnitudeSpectrum: new Float32Array(N_FFT / 2 + 1).fill(0)\n };\n \n let validChunks = 0;\n \n // Iterate through chunks\n for (let i = 0; i < numChunks; i++) {\n const start = i * chunkSize;\n const end = Math.min(start + chunkSize, segment.length);\n const chunk = segment.slice(start, end);\n \n if (chunk.length < N_FFT / 4) continue; // Skip very small chunks\n\n // Process the chunk\n const paddedChunk = new Float32Array(N_FFT);\n paddedChunk.set(applyHannWindow(chunk));\n\n const fft = new FFT(N_FFT);\n fft.transform(paddedChunk);\n\n // Calculate magnitude spectrum\n const chunkMagnitudeSpectrum = new Float32Array(N_FFT / 2 + 1);\n let hasSignal = false;\n \n for (let j = 0; j < N_FFT / 2; j++) {\n const re = paddedChunk[2 * j];\n const im = paddedChunk[2 * j + 1];\n const magnitude = Math.sqrt(re * re + im * im);\n chunkMagnitudeSpectrum[j] = magnitude;\n if (magnitude > Number.EPSILON) hasSignal = true;\n }\n \n if (!hasSignal) continue;\n validChunks++;\n\n // Accumulate results\n if (featureOptions.spectralCentroid) {\n const centroid = computeSpectralCentroid(chunkMagnitudeSpectrum, sampleRate);\n if (!isNaN(centroid)) results.centroid += centroid;\n }\n \n if (featureOptions.spectralFlatness) {\n const flatness = computeSpectralFlatness(chunkMagnitudeSpectrum);\n if (!isNaN(flatness)) results.flatness += flatness;\n }\n \n if (featureOptions.spectralRollOff) {\n const rolloff = computeSpectralRollOff(chunkMagnitudeSpectrum, sampleRate);\n if (!isNaN(rolloff)) results.rollOff += rolloff;\n }\n \n if (featureOptions.spectralBandwidth && !isNaN(results.centroid)) {\n const bandwidth = computeSpectralBandwidth(chunkMagnitudeSpectrum, sampleRate, results.centroid);\n if (!isNaN(bandwidth)) results.bandwidth += bandwidth;\n }\n \n if (featureOptions.magnitudeSpectrum) {\n for (let j = 0; j < results.magnitudeSpectrum.length; j++) {\n results.magnitudeSpectrum[j] += chunkMagnitudeSpectrum[j];\n }\n }\n }\n\n // Average the accumulated results\n if (validChunks > 0) {\n results.centroid /= validChunks;\n results.flatness /= validChunks;\n results.rollOff /= validChunks;\n results.bandwidth /= validChunks;\n \n if (featureOptions.magnitudeSpectrum) {\n for (let i = 0; i < results.magnitudeSpectrum.length; i++) {\n results.magnitudeSpectrum[i] /= validChunks;\n }\n }\n }\n\n return results;\n } catch (error) {\n console.error('[Worker] Spectral feature computation error:', error);\n return {\n centroid: 0,\n flatness: 0,\n rollOff: 0,\n bandwidth: 0,\n magnitudeSpectrum: []\n };\n }\n}\n\nfunction computeSpectralCentroid(magnitudeSpectrum, sampleRate) {\n const sum = magnitudeSpectrum.reduce((a, b) => a + (b || 0), 0);\n if (sum <= Number.EPSILON) return 0;\n \n const weightedSum = magnitudeSpectrum.reduce((acc, value, index) => \n acc + (index * (sampleRate / N_FFT) * (value || 0)), 0);\n \n return weightedSum / sum;\n}\n\nfunction computeSpectralFlatness(powerSpectrum) {\n // Add small epsilon to avoid log(0)\n const epsilon = Number.EPSILON;\n const validSpectrum = powerSpectrum.map(v => Math.max(v, epsilon));\n \n const geometricMean = Math.exp(\n validSpectrum\n .map(v => Math.log(v))\n .reduce((a, b) => a + b) / validSpectrum.length\n );\n \n const arithmeticMean =\n validSpectrum.reduce((a, b) => a + b) / validSpectrum.length;\n \n return geometricMean / arithmeticMean;\n}\n\nfunction computeSpectralRollOff(magnitudeSpectrum, sampleRate) {\n const totalEnergy = magnitudeSpectrum.reduce((a, b) => a + b, 0);\n const rollOffThreshold = totalEnergy * 0.85;\n let cumulativeEnergy = 0;\n\n for (let i = 0; i < magnitudeSpectrum.length; i++) {\n cumulativeEnergy += magnitudeSpectrum[i];\n if (cumulativeEnergy >= rollOffThreshold) {\n return (i / magnitudeSpectrum.length) * (sampleRate / 2);\n }\n }\n\n return 0;\n}\n\nfunction computeSpectralBandwidth(magnitudeSpectrum, sampleRate, centroid) {\n const sum = magnitudeSpectrum.reduce((a, b) => a + (b || 0), 0);\n if (sum <= Number.EPSILON) return 0;\n\n const weightedSum = magnitudeSpectrum.reduce(\n (acc, value, index) => {\n const freq = index * sampleRate / (2 * magnitudeSpectrum.length);\n return acc + (value || 0) * Math.pow(freq - centroid, 2);\n }, 0\n );\n\n return Math.sqrt(weightedSum / sum);\n}\n\nfunction computeChroma(segmentData, sampleRate) {\n // Ensure we have valid input data\n if (!segmentData || segmentData.length === 0) {\n return new Array(N_CHROMA).fill(0);\n }\n\n const fftLength = nextPowerOfTwo(Math.max(segmentData.length, N_FFT));\n const windowed = applyHannWindow(segmentData);\n const padded = new Float32Array(fftLength);\n padded.set(windowed.slice(0, Math.min(windowed.length, fftLength)));\n\n const fft = new FFT(fftLength);\n try {\n fft.transform(padded);\n } catch (e) {\n console.error('[Worker] FFT transform failed in chromagram:', e);\n return new Array(N_CHROMA).fill(0);\n }\n\n const chroma = new Float32Array(N_CHROMA).fill(0);\n const freqsPerBin = sampleRate / fftLength;\n let totalEnergy = 0;\n\n // First pass: compute magnitudes and total energy\n for (let i = 0; i < fftLength / 2; i++) {\n const freq = i * freqsPerBin;\n if (freq > 20) { // Only consider frequencies above 20 Hz\n const re = padded[2 * i];\n const im = padded[2 * i + 1] || 0;\n const magnitude = Math.sqrt(re * re + im * im);\n \n if (magnitude > Number.EPSILON) {\n // Use a more stable pitch class calculation\n const midiNote = 69 + 12 * Math.log2(freq / 440.0);\n const pitchClass = Math.round(midiNote) % 12;\n \n if (pitchClass >= 0 && pitchClass < 12) {\n chroma[pitchClass] += magnitude;\n totalEnergy += magnitude;\n }\n }\n }\n }\n\n // Normalize chroma values only if we have energy\n if (totalEnergy > Number.EPSILON) {\n for (let i = 0; i < N_CHROMA; i++) {\n chroma[i] = chroma[i] / totalEnergy;\n }\n }\n\n // Convert to regular array and ensure no NaN values\n return Array.from(chroma, v => isNaN(v) ? 0 : v);\n}\n\nfunction extractHNR(segmentData) {\n const frameSize = segmentData.length;\n const autocorrelation = new Float32Array(frameSize);\n\n // Compute the autocorrelation iteratively\n for (let i = 0; i < frameSize; i++) {\n let sum = 0;\n for (let j = 0; j < frameSize - i; j++) {\n sum += segmentData[j] * segmentData[j + i];\n }\n autocorrelation[i] = sum;\n }\n\n // Find the maximum autocorrelation value iteratively\n let maxAutocorrelation = -Infinity;\n for (let i = 1; i < autocorrelation.length; i++) {\n if (autocorrelation[i] > maxAutocorrelation) {\n maxAutocorrelation = autocorrelation[i];\n }\n }\n\n // Compute the HNR\n return autocorrelation[0] !== 0\n ? 10 * Math.log10(maxAutocorrelation / (autocorrelation[0] - maxAutocorrelation))\n : 0;\n}\n\nfunction estimatePitch(segment, sampleRate) {\n // Early validation\n if (!segment || segment.length < 2 || !sampleRate) return 0;\n\n try {\n // Apply Hann window\n const windowed = applyHannWindow(segment);\n\n // Pad for FFT\n const fftLength = nextPowerOfTwo(segment.length * 2);\n const padded = new Float32Array(fftLength);\n padded.set(windowed);\n\n // Perform FFT\n const fft = new FFT(fftLength);\n fft.transform(padded);\n\n // Compute power spectrum\n const powerSpectrum = new Float32Array(fftLength / 2 + 1);\n for (let i = 0; i <= fftLength / 2; i++) {\n const re = padded[2 * i];\n const im = padded[2 * i + 1] || 0;\n powerSpectrum[i] = re * re + im * im;\n }\n\n // Find peak frequency\n let maxPower = 0;\n let peakIndex = 0;\n const minFreq = 50; // Minimum frequency to consider (Hz)\n const maxFreq = 1000; // Maximum frequency to consider (Hz)\n const minBin = Math.floor(minFreq * fftLength / sampleRate);\n const maxBin = Math.ceil(maxFreq * fftLength / sampleRate);\n\n for (let i = minBin; i <= maxBin; i++) {\n if (powerSpectrum[i] > maxPower) {\n maxPower = powerSpectrum[i];\n peakIndex = i;\n }\n }\n\n // Convert peak index to frequency\n const fundamentalFreq = peakIndex * sampleRate / fftLength;\n\n // Return 0 if the detected frequency is outside reasonable bounds\n return (fundamentalFreq >= minFreq && fundamentalFreq <= maxFreq) ? \n fundamentalFreq : 0;\n\n } catch (error) {\n console.error('[Worker] Pitch estimation error:', error);\n return 0;\n }\n}\n\n// Unique ID counter\nlet uniqueIdCounter = 0\nlet accumulatedDataPoints = []\nlet lastEmitTime = Date.now()\n\nself.onmessage = function (event) {\n // Check if this is a reset command\n if (event.data.command === 'resetCounter') {\n uniqueIdCounter = event.data.startCounterFrom || 0;\n console.log('[Worker] Reset counter to', uniqueIdCounter);\n return; // Exit early, don't process audio\n }\n\n // Regular audio processing\n const {\n channelData,\n sampleRate,\n segmentDurationMs,\n algorithm,\n bitDepth,\n fullAudioDurationMs,\n numberOfChannels,\n features: _features,\n intervalAnalysis = 500,\n enableLogging,\n resetCounter,\n startCounterFrom,\n } = event.data\n\n // Also handle reset as part of regular message\n if (resetCounter) {\n uniqueIdCounter = startCounterFrom || 0;\n }\n\n const subChunkStartTime = fullAudioDurationMs / 1000\n\n \n // Create a simple logger that only logs when enabled\n const logger = enableLogging ? {\n debug: (...args) => console.debug('[Worker]', ...args),\n log: (...args) => console.log('[Worker]', ...args),\n error: (...args) => console.error('[Worker]', ...args)\n } : {\n debug: () => {},\n log: () => {},\n error: () => {}\n }\n console.log('[Worker] START Feature Extractor - hasData: ' + (event.data ? true : false) + ', channelData: ' + (event.data.channelData ? event.data.channelData.length : 0) + ', fullAudioDurationMs: ' + (event.data.fullAudioDurationMs || 0) + ', sampleRate: ' + (event.data.sampleRate || 0) + ', segmentDurationMs: ' + (event.data.segmentDurationMs || 0) + ', algorithm: ' + (event.data.algorithm || 'none') + ', bitDepth: ' + (event.data.bitDepth || 0) + ', numberOfChannels: ' + (event.data.numberOfChannels || 0) + ', features: ' + (event.data.features ? Object.keys(event.data.features).length : 0) + ', intervalAnalysis: ' + (event.data.intervalAnalysis || 0) + ', dataKeys: ' + (event.data ? Object.keys(event.data).join(',') : ''));\n \n const features = _features || {}\n\n const SILENCE_THRESHOLD = 0.01\n const MIN_SILENCE_DURATION = 1.5 * sampleRate // 1.5 seconds of silence\n const SPEECH_INERTIA_DURATION = 0.1 * sampleRate // Speech inertia duration in samples\n const RMS_THRESHOLD = 0.01\n const ZCR_THRESHOLD = 0.1\n\n // Placeholder functions for feature extraction\n const extractMFCC = (segmentData, sampleRate) => {\n // Implement MFCC extraction logic here\n return []\n }\n\n const extractSpectralCentroid = (segmentData, sampleRate) => {\n const magnitudeSpectrum = segmentData.map((v) => v * v)\n const sum = magnitudeSpectrum.reduce((a, b) => a + b, 0)\n if (sum === 0) return 0\n\n const weightedSum = magnitudeSpectrum.reduce(\n (acc, value, index) => acc + index * value,\n 0\n )\n return (\n ((weightedSum / sum) * (sampleRate / 2)) / magnitudeSpectrum.length\n )\n }\n\n const extractSpectralFlatness = (segmentData) => {\n const magnitudeSpectrum = segmentData.map((v) => Math.abs(v))\n const geometricMean = Math.exp(\n magnitudeSpectrum\n .map((v) => Math.log(v + Number.MIN_VALUE))\n .reduce((a, b) => a + b) / magnitudeSpectrum.length\n )\n const arithmeticMean =\n magnitudeSpectrum.reduce((a, b) => a + b) / magnitudeSpectrum.length\n return arithmeticMean === 0 ? 0 : geometricMean / arithmeticMean\n }\n\n const extractSpectralRollOff = (segmentData, sampleRate) => {\n const magnitudeSpectrum = segmentData.map((v) => Math.abs(v))\n const totalEnergy = magnitudeSpectrum.reduce((a, b) => a + b, 0)\n const rollOffThreshold = totalEnergy * 0.85\n let cumulativeEnergy = 0\n\n for (let i = 0; i < magnitudeSpectrum.length; i++) {\n cumulativeEnergy += magnitudeSpectrum[i]\n if (cumulativeEnergy >= rollOffThreshold) {\n return (i / magnitudeSpectrum.length) * (sampleRate / 2)\n }\n }\n\n return 0\n }\n\n const extractSpectralBandwidth = (segmentData, sampleRate) => {\n const centroid = extractSpectralCentroid(segmentData, sampleRate)\n const magnitudeSpectrum = segmentData.map((v) => Math.abs(v))\n const sum = magnitudeSpectrum.reduce((a, b) => a + b, 0)\n if (sum === 0) return 0\n\n const weightedSum = magnitudeSpectrum.reduce(\n (acc, value, index) => acc + value * Math.pow(index - centroid, 2),\n 0\n )\n return Math.sqrt(weightedSum / sum)\n }\n\n const extractChromagram = (segmentData, sampleRate) => {\n return [] // TODO implement\n }\n\n /**\n * Creates a features object based on requested features\n */\n function createFeaturesObject(\n features,\n maxAmp,\n rms,\n sumSquares,\n zeroCrossings,\n remainingSamples,\n spectralFeatures,\n channelData,\n startIdx,\n endIdx,\n sampleRate\n ) {\n // If no features are requested, return undefined\n if (!Object.values(features).some(function(v) { return v; })) {\n return undefined;\n }\n\n const result = {};\n \n if (features.energy) {\n result.energy = sumSquares;\n }\n if (features.rms) {\n result.rms = rms;\n }\n // Always include min/max amplitude if any features are requested\n result.minAmplitude = -maxAmp;\n result.maxAmplitude = maxAmp;\n \n if (features.zcr) {\n result.zcr = zeroCrossings / remainingSamples;\n }\n if (features.spectralCentroid) {\n result.spectralCentroid = spectralFeatures.centroid;\n }\n if (features.spectralFlatness) {\n result.spectralFlatness = spectralFeatures.flatness;\n }\n if (features.spectralRolloff) {\n result.spectralRolloff = spectralFeatures.rollOff;\n }\n if (features.spectralBandwidth) {\n result.spectralBandwidth = spectralFeatures.bandwidth;\n }\n if (features.chromagram) {\n result.chromagram = computeChroma(channelData.slice(startIdx, endIdx), sampleRate);\n }\n if (features.hnr) {\n result.hnr = extractHNR(channelData.slice(startIdx, endIdx));\n }\n if (features.pitch) {\n result.pitch = estimatePitch(channelData.slice(startIdx, endIdx), sampleRate);\n }\n \n return result;\n }\n\n function extractWaveform(\n channelData,\n sampleRate,\n segmentDurationMs\n ) {\n const logger = enableLogging ? {\n debug: (...args) => console.debug('[Worker]', ...args),\n log: (...args) => console.log('[Worker]', ...args),\n error: (...args) => console.error('[Worker]', ...args)\n } : {\n debug: () => {},\n log: () => {},\n error: () => {}\n }\n\n // Calculate amplitude range\n let min = Infinity\n let max = -Infinity\n for (let i = 0; i < channelData.length; i++) {\n min = Math.min(min, channelData[i])\n max = Math.max(max, channelData[i])\n }\n\n const totalSamples = channelData.length\n const durationMs = (totalSamples / sampleRate) * 1000\n \n // Calculate fixed segment sizes\n const samplesPerSegment = Math.floor(sampleRate * (segmentDurationMs / 1000));\n const numPoints = Math.floor(totalSamples / samplesPerSegment);\n const remainingSamples = totalSamples % samplesPerSegment;\n\n const dataPoints = []\n\n // Process full segments\n for (let i = 0; i < numPoints; i++) {\n const startIdx = i * samplesPerSegment\n const endIdx = startIdx + samplesPerSegment\n \n let sumSquares = 0\n let maxAmp = 0\n let zeroCrossings = 0\n\n // Calculate segment features\n for (let j = startIdx; j < endIdx; j++) {\n const value = channelData[j]\n sumSquares += value * value\n maxAmp = Math.max(maxAmp, Math.abs(value))\n if (j > 0 && value * channelData[j - 1] < 0) {\n zeroCrossings++\n }\n }\n\n const rms = Math.sqrt(sumSquares / samplesPerSegment)\n const startTime = subChunkStartTime + (startIdx / sampleRate)\n const endTime = subChunkStartTime + (endIdx / sampleRate)\n\n var spectralFeatures = computeSpectralFeatures(channelData.slice(startIdx, endIdx), sampleRate, features);\n\n const dataPoint = {\n id: uniqueIdCounter++,\n amplitude: maxAmp,\n rms,\n startTime,\n endTime,\n dB: 20 * Math.log10(rms + 1e-6),\n silent: rms < 0.01,\n startPosition: startIdx * 2,\n endPosition: endIdx * 2,\n samples: samplesPerSegment,\n }\n\n // Extract features if any are requested\n const extractedFeatures = createFeaturesObject(\n features,\n maxAmp,\n rms,\n sumSquares,\n zeroCrossings,\n samplesPerSegment,\n spectralFeatures,\n channelData,\n startIdx,\n endIdx,\n sampleRate\n );\n \n if (extractedFeatures) {\n dataPoint.features = extractedFeatures;\n }\n\n dataPoints.push(dataPoint)\n }\n\n // Handle remaining samples if they exist and are enough to process\n if (remainingSamples > samplesPerSegment / 4) { // Only process if we have at least 1/4 of a segment\n const startIdx = numPoints * samplesPerSegment\n const endIdx = totalSamples\n \n let sumSquares = 0\n let maxAmp = 0\n let zeroCrossings = 0\n\n for (let j = startIdx; j < endIdx; j++) {\n const value = channelData[j]\n sumSquares += value * value\n maxAmp = Math.max(maxAmp, Math.abs(value))\n if (j > 0 && value * channelData[j - 1] < 0) {\n zeroCrossings++\n }\n }\n\n const rms = Math.sqrt(sumSquares / remainingSamples)\n const startTime = startIdx / sampleRate;\n const endTime = endIdx / sampleRate;\n\n var spectralFeatures = computeSpectralFeatures(channelData.slice(startIdx, endIdx), sampleRate, features);\n\n const dataPoint = {\n id: uniqueIdCounter++,\n amplitude: maxAmp,\n rms,\n startTime,\n endTime,\n dB: 20 * Math.log10(rms + 1e-6),\n silent: rms < 0.01,\n startPosition: startIdx * 2,\n endPosition: endIdx * 2,\n samples: remainingSamples,\n }\n\n // Extract features if any are requested\n const extractedFeatures = createFeaturesObject(\n features,\n maxAmp,\n rms,\n sumSquares,\n zeroCrossings,\n remainingSamples,\n spectralFeatures,\n channelData,\n startIdx,\n endIdx,\n sampleRate\n );\n \n if (extractedFeatures) {\n dataPoint.features = extractedFeatures;\n }\n\n dataPoints.push(dataPoint)\n }\n\n return {\n durationMs,\n dataPoints,\n amplitudeRange: { min, max },\n rmsRange: {\n min: 0,\n max: Math.max(Math.abs(min), Math.abs(max))\n },\n extractionTimeMs: Date.now() - lastEmitTime\n }\n }\n\n try {\n const result = extractWaveform(\n channelData,\n sampleRate,\n segmentDurationMs\n )\n\n // Send complete result immediately\n self.postMessage({\n command: 'features',\n result: {\n bitDepth,\n samples: channelData.length,\n numberOfChannels,\n sampleRate,\n segmentDurationMs,\n durationMs: result.durationMs,\n dataPoints: result.dataPoints,\n amplitudeRange: result.amplitudeRange,\n rmsRange: result.rmsRange,\n }\n })\n } catch (error) {\n console.error('[Worker] Error', {\n message: error.message,\n stack: error.stack\n });\n \n self.postMessage({ \n error: {\n message: error.message,\n stack: error.stack,\n name: error.name\n }\n });\n }\n}\n`\n"]}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
export declare const InlineAudioWebWorker = "\nconst DEFAULT_BIT_DEPTH = 32\nconst DEFAULT_SAMPLE_RATE = 44100\n\nclass RecorderProcessor extends AudioWorkletProcessor {\n constructor() {\n super()\n this.currentChunk = [] // Float32Array\n this.samplesSinceLastExport = 0\n this.recordSampleRate = DEFAULT_SAMPLE_RATE\n this.exportSampleRate = DEFAULT_SAMPLE_RATE\n this.recordBitDepth = DEFAULT_BIT_DEPTH\n this.exportBitDepth = DEFAULT_BIT_DEPTH\n this.numberOfChannels = 1\n this.isRecording = true\n this.port.onmessage = this.handleMessage.bind(this)\n this.enableLogging = false\n this.exportIntervalSamples = 0\n }\n\n handleMessage(event) {\n switch (event.data.command) {\n case 'init':\n this.enableLogging = event.data.enableLogging || false\n this.recordSampleRate = event.data.recordSampleRate\n this.exportSampleRate =\n event.data.exportSampleRate || event.data.recordSampleRate\n this.exportIntervalSamples =\n this.recordSampleRate * (event.data.interval / 1000)\n if (event.data.numberOfChannels) {\n this.numberOfChannels = event.data.numberOfChannels\n }\n if (event.data.recordBitDepth) {\n this.recordBitDepth = event.data.recordBitDepth\n }\n this.exportBitDepth =\n event.data.exportBitDepth || this.recordBitDepth\n break\n\n case 'stop':\n this.isRecording = false\n if (this.currentChunk.length > 0) {\n this.processChunk()\n }\n break\n }\n }\n\n process(inputs, _outputs, _parameters) {\n if (!this.isRecording) return true\n const input = inputs[0]\n if (input.length > 0) {\n const newBuffer = new Float32Array(input[0])\n this.currentChunk.push(newBuffer)\n this.samplesSinceLastExport += newBuffer.length\n\n if (this.samplesSinceLastExport >= this.exportIntervalSamples) {\n this.processChunk()\n this.samplesSinceLastExport = 0\n }\n }\n return true\n }\n\n mergeBuffers(bufferArray, recLength) {\n const result = new Float32Array(recLength)\n let offset = 0\n for (let i = 0; i < bufferArray.length; i++) {\n result.set(bufferArray[i], offset)\n offset += bufferArray[i].length\n }\n return result\n }\n\n // Keep basic resampling for sample rate conversion\n resample(samples, targetSampleRate) {\n if (this.recordSampleRate === targetSampleRate) {\n return samples\n }\n const resampledBuffer = new Float32Array(\n Math.ceil(\n (samples.length * targetSampleRate) / this.recordSampleRate\n )\n )\n const ratio = this.recordSampleRate / targetSampleRate\n let offset = 0\n for (let i = 0; i < resampledBuffer.length; i++) {\n const nextOffset = Math.floor((i + 1) * ratio)\n let accum = 0\n let count = 0\n for (let j = offset; j < nextOffset && j < samples.length; j++) {\n accum += samples[j]\n count++\n }\n resampledBuffer[i] = count > 0 ? accum / count : 0\n offset = nextOffset\n }\n return resampledBuffer\n }\n\n // Keep bit depth conversion if needed\n convertBitDepth(input, targetBitDepth) {\n if (targetBitDepth === 32) {\n const output = new Int32Array(input.length)\n for (let i = 0; i < input.length; i++) {\n const s = Math.max(-1, Math.min(1, input[i]))\n output[i] = s < 0 ? s * 0x80000000 : s * 0x7fffffff\n }\n return output\n } else if (targetBitDepth === 16) {\n const output = new Int16Array(input.length)\n for (let i = 0; i < input.length; i++) {\n const s = Math.max(-1, Math.min(1, input[i]))\n output[i] = s < 0 ? s * 0x8000 : s * 0x7fff\n }\n return output\n }\n return input\n }\n\n processChunk() {\n if (this.currentChunk.length === 0) return\n\n // Merge buffers\n const chunkLength = this.currentChunk.reduce(\n (acc, buf) => acc + buf.length,\n 0\n )\n const mergedChunk = this.mergeBuffers(this.currentChunk, chunkLength)\n\n // Resample if needed\n const resampledChunk = this.resample(mergedChunk, this.exportSampleRate)\n\n // Convert bit depth if needed\n const finalBuffer =\n this.recordBitDepth !== this.exportBitDepth\n ? this.convertBitDepth(resampledChunk, this.exportBitDepth)\n : resampledChunk\n\n // Send processed chunk\n this.port.postMessage({\n command: 'newData',\n recordedData: finalBuffer,\n sampleRate: this.exportSampleRate,\n bitDepth: this.exportBitDepth,\n numberOfChannels: this.numberOfChannels,\n })\n\n // Clear the current chunk\n this.currentChunk = []\n }\n}\n\nregisterProcessor('recorder-processor', RecorderProcessor)\n";
|
|
2
|
-
//# sourceMappingURL=inlineAudioWebWorker.web.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"inlineAudioWebWorker.web.d.ts","sourceRoot":"","sources":["../../src/workers/inlineAudioWebWorker.web.tsx"],"names":[],"mappings":"AACA,eAAO,MAAM,oBAAoB,61KA0JhC,CAAA"}
|
|
@@ -1,157 +0,0 @@
|
|
|
1
|
-
// packages/expo-audio-stream/src/workers/inlineAudioWebWorker.web.tsx
|
|
2
|
-
export const InlineAudioWebWorker = `
|
|
3
|
-
const DEFAULT_BIT_DEPTH = 32
|
|
4
|
-
const DEFAULT_SAMPLE_RATE = 44100
|
|
5
|
-
|
|
6
|
-
class RecorderProcessor extends AudioWorkletProcessor {
|
|
7
|
-
constructor() {
|
|
8
|
-
super()
|
|
9
|
-
this.currentChunk = [] // Float32Array
|
|
10
|
-
this.samplesSinceLastExport = 0
|
|
11
|
-
this.recordSampleRate = DEFAULT_SAMPLE_RATE
|
|
12
|
-
this.exportSampleRate = DEFAULT_SAMPLE_RATE
|
|
13
|
-
this.recordBitDepth = DEFAULT_BIT_DEPTH
|
|
14
|
-
this.exportBitDepth = DEFAULT_BIT_DEPTH
|
|
15
|
-
this.numberOfChannels = 1
|
|
16
|
-
this.isRecording = true
|
|
17
|
-
this.port.onmessage = this.handleMessage.bind(this)
|
|
18
|
-
this.enableLogging = false
|
|
19
|
-
this.exportIntervalSamples = 0
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
handleMessage(event) {
|
|
23
|
-
switch (event.data.command) {
|
|
24
|
-
case 'init':
|
|
25
|
-
this.enableLogging = event.data.enableLogging || false
|
|
26
|
-
this.recordSampleRate = event.data.recordSampleRate
|
|
27
|
-
this.exportSampleRate =
|
|
28
|
-
event.data.exportSampleRate || event.data.recordSampleRate
|
|
29
|
-
this.exportIntervalSamples =
|
|
30
|
-
this.recordSampleRate * (event.data.interval / 1000)
|
|
31
|
-
if (event.data.numberOfChannels) {
|
|
32
|
-
this.numberOfChannels = event.data.numberOfChannels
|
|
33
|
-
}
|
|
34
|
-
if (event.data.recordBitDepth) {
|
|
35
|
-
this.recordBitDepth = event.data.recordBitDepth
|
|
36
|
-
}
|
|
37
|
-
this.exportBitDepth =
|
|
38
|
-
event.data.exportBitDepth || this.recordBitDepth
|
|
39
|
-
break
|
|
40
|
-
|
|
41
|
-
case 'stop':
|
|
42
|
-
this.isRecording = false
|
|
43
|
-
if (this.currentChunk.length > 0) {
|
|
44
|
-
this.processChunk()
|
|
45
|
-
}
|
|
46
|
-
break
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
process(inputs, _outputs, _parameters) {
|
|
51
|
-
if (!this.isRecording) return true
|
|
52
|
-
const input = inputs[0]
|
|
53
|
-
if (input.length > 0) {
|
|
54
|
-
const newBuffer = new Float32Array(input[0])
|
|
55
|
-
this.currentChunk.push(newBuffer)
|
|
56
|
-
this.samplesSinceLastExport += newBuffer.length
|
|
57
|
-
|
|
58
|
-
if (this.samplesSinceLastExport >= this.exportIntervalSamples) {
|
|
59
|
-
this.processChunk()
|
|
60
|
-
this.samplesSinceLastExport = 0
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
return true
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
mergeBuffers(bufferArray, recLength) {
|
|
67
|
-
const result = new Float32Array(recLength)
|
|
68
|
-
let offset = 0
|
|
69
|
-
for (let i = 0; i < bufferArray.length; i++) {
|
|
70
|
-
result.set(bufferArray[i], offset)
|
|
71
|
-
offset += bufferArray[i].length
|
|
72
|
-
}
|
|
73
|
-
return result
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Keep basic resampling for sample rate conversion
|
|
77
|
-
resample(samples, targetSampleRate) {
|
|
78
|
-
if (this.recordSampleRate === targetSampleRate) {
|
|
79
|
-
return samples
|
|
80
|
-
}
|
|
81
|
-
const resampledBuffer = new Float32Array(
|
|
82
|
-
Math.ceil(
|
|
83
|
-
(samples.length * targetSampleRate) / this.recordSampleRate
|
|
84
|
-
)
|
|
85
|
-
)
|
|
86
|
-
const ratio = this.recordSampleRate / targetSampleRate
|
|
87
|
-
let offset = 0
|
|
88
|
-
for (let i = 0; i < resampledBuffer.length; i++) {
|
|
89
|
-
const nextOffset = Math.floor((i + 1) * ratio)
|
|
90
|
-
let accum = 0
|
|
91
|
-
let count = 0
|
|
92
|
-
for (let j = offset; j < nextOffset && j < samples.length; j++) {
|
|
93
|
-
accum += samples[j]
|
|
94
|
-
count++
|
|
95
|
-
}
|
|
96
|
-
resampledBuffer[i] = count > 0 ? accum / count : 0
|
|
97
|
-
offset = nextOffset
|
|
98
|
-
}
|
|
99
|
-
return resampledBuffer
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Keep bit depth conversion if needed
|
|
103
|
-
convertBitDepth(input, targetBitDepth) {
|
|
104
|
-
if (targetBitDepth === 32) {
|
|
105
|
-
const output = new Int32Array(input.length)
|
|
106
|
-
for (let i = 0; i < input.length; i++) {
|
|
107
|
-
const s = Math.max(-1, Math.min(1, input[i]))
|
|
108
|
-
output[i] = s < 0 ? s * 0x80000000 : s * 0x7fffffff
|
|
109
|
-
}
|
|
110
|
-
return output
|
|
111
|
-
} else if (targetBitDepth === 16) {
|
|
112
|
-
const output = new Int16Array(input.length)
|
|
113
|
-
for (let i = 0; i < input.length; i++) {
|
|
114
|
-
const s = Math.max(-1, Math.min(1, input[i]))
|
|
115
|
-
output[i] = s < 0 ? s * 0x8000 : s * 0x7fff
|
|
116
|
-
}
|
|
117
|
-
return output
|
|
118
|
-
}
|
|
119
|
-
return input
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
processChunk() {
|
|
123
|
-
if (this.currentChunk.length === 0) return
|
|
124
|
-
|
|
125
|
-
// Merge buffers
|
|
126
|
-
const chunkLength = this.currentChunk.reduce(
|
|
127
|
-
(acc, buf) => acc + buf.length,
|
|
128
|
-
0
|
|
129
|
-
)
|
|
130
|
-
const mergedChunk = this.mergeBuffers(this.currentChunk, chunkLength)
|
|
131
|
-
|
|
132
|
-
// Resample if needed
|
|
133
|
-
const resampledChunk = this.resample(mergedChunk, this.exportSampleRate)
|
|
134
|
-
|
|
135
|
-
// Convert bit depth if needed
|
|
136
|
-
const finalBuffer =
|
|
137
|
-
this.recordBitDepth !== this.exportBitDepth
|
|
138
|
-
? this.convertBitDepth(resampledChunk, this.exportBitDepth)
|
|
139
|
-
: resampledChunk
|
|
140
|
-
|
|
141
|
-
// Send processed chunk
|
|
142
|
-
this.port.postMessage({
|
|
143
|
-
command: 'newData',
|
|
144
|
-
recordedData: finalBuffer,
|
|
145
|
-
sampleRate: this.exportSampleRate,
|
|
146
|
-
bitDepth: this.exportBitDepth,
|
|
147
|
-
numberOfChannels: this.numberOfChannels,
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
// Clear the current chunk
|
|
151
|
-
this.currentChunk = []
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
registerProcessor('recorder-processor', RecorderProcessor)
|
|
156
|
-
`;
|
|
157
|
-
//# sourceMappingURL=inlineAudioWebWorker.web.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"inlineAudioWebWorker.web.js","sourceRoot":"","sources":["../../src/workers/inlineAudioWebWorker.web.tsx"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0JnC,CAAA","sourcesContent":["// packages/expo-audio-stream/src/workers/inlineAudioWebWorker.web.tsx\nexport const InlineAudioWebWorker = `\nconst DEFAULT_BIT_DEPTH = 32\nconst DEFAULT_SAMPLE_RATE = 44100\n\nclass RecorderProcessor extends AudioWorkletProcessor {\n constructor() {\n super()\n this.currentChunk = [] // Float32Array\n this.samplesSinceLastExport = 0\n this.recordSampleRate = DEFAULT_SAMPLE_RATE\n this.exportSampleRate = DEFAULT_SAMPLE_RATE\n this.recordBitDepth = DEFAULT_BIT_DEPTH\n this.exportBitDepth = DEFAULT_BIT_DEPTH\n this.numberOfChannels = 1\n this.isRecording = true\n this.port.onmessage = this.handleMessage.bind(this)\n this.enableLogging = false\n this.exportIntervalSamples = 0\n }\n\n handleMessage(event) {\n switch (event.data.command) {\n case 'init':\n this.enableLogging = event.data.enableLogging || false\n this.recordSampleRate = event.data.recordSampleRate\n this.exportSampleRate =\n event.data.exportSampleRate || event.data.recordSampleRate\n this.exportIntervalSamples =\n this.recordSampleRate * (event.data.interval / 1000)\n if (event.data.numberOfChannels) {\n this.numberOfChannels = event.data.numberOfChannels\n }\n if (event.data.recordBitDepth) {\n this.recordBitDepth = event.data.recordBitDepth\n }\n this.exportBitDepth =\n event.data.exportBitDepth || this.recordBitDepth\n break\n\n case 'stop':\n this.isRecording = false\n if (this.currentChunk.length > 0) {\n this.processChunk()\n }\n break\n }\n }\n\n process(inputs, _outputs, _parameters) {\n if (!this.isRecording) return true\n const input = inputs[0]\n if (input.length > 0) {\n const newBuffer = new Float32Array(input[0])\n this.currentChunk.push(newBuffer)\n this.samplesSinceLastExport += newBuffer.length\n\n if (this.samplesSinceLastExport >= this.exportIntervalSamples) {\n this.processChunk()\n this.samplesSinceLastExport = 0\n }\n }\n return true\n }\n\n mergeBuffers(bufferArray, recLength) {\n const result = new Float32Array(recLength)\n let offset = 0\n for (let i = 0; i < bufferArray.length; i++) {\n result.set(bufferArray[i], offset)\n offset += bufferArray[i].length\n }\n return result\n }\n\n // Keep basic resampling for sample rate conversion\n resample(samples, targetSampleRate) {\n if (this.recordSampleRate === targetSampleRate) {\n return samples\n }\n const resampledBuffer = new Float32Array(\n Math.ceil(\n (samples.length * targetSampleRate) / this.recordSampleRate\n )\n )\n const ratio = this.recordSampleRate / targetSampleRate\n let offset = 0\n for (let i = 0; i < resampledBuffer.length; i++) {\n const nextOffset = Math.floor((i + 1) * ratio)\n let accum = 0\n let count = 0\n for (let j = offset; j < nextOffset && j < samples.length; j++) {\n accum += samples[j]\n count++\n }\n resampledBuffer[i] = count > 0 ? accum / count : 0\n offset = nextOffset\n }\n return resampledBuffer\n }\n\n // Keep bit depth conversion if needed\n convertBitDepth(input, targetBitDepth) {\n if (targetBitDepth === 32) {\n const output = new Int32Array(input.length)\n for (let i = 0; i < input.length; i++) {\n const s = Math.max(-1, Math.min(1, input[i]))\n output[i] = s < 0 ? s * 0x80000000 : s * 0x7fffffff\n }\n return output\n } else if (targetBitDepth === 16) {\n const output = new Int16Array(input.length)\n for (let i = 0; i < input.length; i++) {\n const s = Math.max(-1, Math.min(1, input[i]))\n output[i] = s < 0 ? s * 0x8000 : s * 0x7fff\n }\n return output\n }\n return input\n }\n\n processChunk() {\n if (this.currentChunk.length === 0) return\n\n // Merge buffers\n const chunkLength = this.currentChunk.reduce(\n (acc, buf) => acc + buf.length,\n 0\n )\n const mergedChunk = this.mergeBuffers(this.currentChunk, chunkLength)\n\n // Resample if needed\n const resampledChunk = this.resample(mergedChunk, this.exportSampleRate)\n\n // Convert bit depth if needed\n const finalBuffer =\n this.recordBitDepth !== this.exportBitDepth\n ? this.convertBitDepth(resampledChunk, this.exportBitDepth)\n : resampledChunk\n\n // Send processed chunk\n this.port.postMessage({\n command: 'newData',\n recordedData: finalBuffer,\n sampleRate: this.exportSampleRate,\n bitDepth: this.exportBitDepth,\n numberOfChannels: this.numberOfChannels,\n })\n\n // Clear the current chunk\n this.currentChunk = []\n }\n}\n\nregisterProcessor('recorder-processor', RecorderProcessor)\n`\n"]}
|
package/expo-module.config.json
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// AudioAnalysisData.swift
|
|
3
|
-
// ExpoAudioStream
|
|
4
|
-
//
|
|
5
|
-
// Created by Arthur Breton on 23/6/2024.
|
|
6
|
-
//
|
|
7
|
-
|
|
8
|
-
import Foundation
|
|
9
|
-
|
|
10
|
-
public struct AudioAnalysisData {
|
|
11
|
-
public let segmentDurationMs: Int
|
|
12
|
-
public let durationMs: Int
|
|
13
|
-
public let bitDepth: Int
|
|
14
|
-
public let numberOfChannels: Int
|
|
15
|
-
public let sampleRate: Int
|
|
16
|
-
public let samples: Int
|
|
17
|
-
public let dataPoints: [DataPoint]
|
|
18
|
-
public let amplitudeRange: AmplitudeRange
|
|
19
|
-
public let rmsRange: AmplitudeRange
|
|
20
|
-
public let speechAnalysis: SpeechAnalysis?
|
|
21
|
-
public let extractionTimeMs: Float
|
|
22
|
-
|
|
23
|
-
public struct AmplitudeRange {
|
|
24
|
-
public let min: Float
|
|
25
|
-
public let max: Float
|
|
26
|
-
|
|
27
|
-
func toDictionary() -> [String: Float] {
|
|
28
|
-
return [
|
|
29
|
-
"min": min,
|
|
30
|
-
"max": max
|
|
31
|
-
]
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
public struct SpeechAnalysis {
|
|
36
|
-
public let speakerChanges: [SpeakerChange]
|
|
37
|
-
|
|
38
|
-
func toDictionary() -> [String: Any] {
|
|
39
|
-
return [
|
|
40
|
-
"speakerChanges": speakerChanges.map { $0.toDictionary() }
|
|
41
|
-
]
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
public struct SpeakerChange {
|
|
46
|
-
public let timestamp: Int64
|
|
47
|
-
public let speakerId: Int
|
|
48
|
-
|
|
49
|
-
func toDictionary() -> [String: Any] {
|
|
50
|
-
return [
|
|
51
|
-
"timestamp": timestamp,
|
|
52
|
-
"speakerId": speakerId
|
|
53
|
-
]
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
extension AudioAnalysisData {
|
|
59
|
-
func toDictionary() -> [String: Any?] {
|
|
60
|
-
return [
|
|
61
|
-
"segmentDurationMs": segmentDurationMs,
|
|
62
|
-
"durationMs": durationMs,
|
|
63
|
-
"bitDepth": bitDepth,
|
|
64
|
-
"numberOfChannels": numberOfChannels,
|
|
65
|
-
"sampleRate": sampleRate,
|
|
66
|
-
"samples": samples,
|
|
67
|
-
"dataPoints": dataPoints.map { $0.toDictionary() },
|
|
68
|
-
"amplitudeRange": amplitudeRange.toDictionary(),
|
|
69
|
-
"rmsRange": rmsRange.toDictionary(),
|
|
70
|
-
"speechAnalysis": speechAnalysis?.toDictionary(),
|
|
71
|
-
"extractionTimeMs": extractionTimeMs
|
|
72
|
-
]
|
|
73
|
-
}
|
|
74
|
-
}
|
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
class AudioNotificationManager {
|
|
2
|
-
private let notificationCenter = UNUserNotificationCenter.current()
|
|
3
|
-
private let notificationId = "audio_recording_notification"
|
|
4
|
-
private var updateTimer: Timer?
|
|
5
|
-
private var config: NotificationConfig?
|
|
6
|
-
private var currentDuration: TimeInterval = 0
|
|
7
|
-
private var lastUpdateTime: Date = Date()
|
|
8
|
-
private var minUpdateInterval: TimeInterval = 1.0 // Minimum time between updates
|
|
9
|
-
|
|
10
|
-
func initialize(with config: NotificationConfig?) {
|
|
11
|
-
self.config = config
|
|
12
|
-
setupNotificationCategories()
|
|
13
|
-
showInitialNotification()
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
private func setupNotificationCategories() {
|
|
17
|
-
let pauseAction = UNNotificationAction(
|
|
18
|
-
identifier: "PAUSE_RECORDING",
|
|
19
|
-
title: "Pause",
|
|
20
|
-
options: [.foreground]
|
|
21
|
-
)
|
|
22
|
-
|
|
23
|
-
let resumeAction = UNNotificationAction(
|
|
24
|
-
identifier: "RESUME_RECORDING",
|
|
25
|
-
title: "Resume",
|
|
26
|
-
options: [.foreground]
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
let category = UNNotificationCategory(
|
|
30
|
-
identifier: config?.ios?.categoryIdentifier ?? "recording_category",
|
|
31
|
-
actions: [pauseAction, resumeAction],
|
|
32
|
-
intentIdentifiers: [],
|
|
33
|
-
options: [.customDismissAction]
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
notificationCenter.setNotificationCategories([category])
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
func showInitialNotification() {
|
|
40
|
-
updateNotification()
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
func startUpdates(startTime: Date) {
|
|
44
|
-
DispatchQueue.main.async {
|
|
45
|
-
self.updateTimer?.invalidate()
|
|
46
|
-
|
|
47
|
-
self.updateTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
|
|
48
|
-
guard let self = self else { return }
|
|
49
|
-
|
|
50
|
-
let now = Date()
|
|
51
|
-
if now.timeIntervalSince(self.lastUpdateTime) >= self.minUpdateInterval {
|
|
52
|
-
self.updateNotification()
|
|
53
|
-
self.lastUpdateTime = now
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
RunLoop.main.add(self.updateTimer!, forMode: .common)
|
|
57
|
-
|
|
58
|
-
self.showInitialNotification()
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
func stopUpdates() {
|
|
63
|
-
DispatchQueue.main.async {
|
|
64
|
-
self.updateTimer?.invalidate()
|
|
65
|
-
self.updateTimer = nil
|
|
66
|
-
|
|
67
|
-
self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [self.notificationId])
|
|
68
|
-
self.notificationCenter.removePendingNotificationRequests(withIdentifiers: [self.notificationId])
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
func updateState(isPaused: Bool) {
|
|
73
|
-
let now = Date()
|
|
74
|
-
if now.timeIntervalSince(lastUpdateTime) >= minUpdateInterval {
|
|
75
|
-
updateNotification(forcePauseState: isPaused)
|
|
76
|
-
lastUpdateTime = now
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
func updateDuration(_ duration: TimeInterval) {
|
|
81
|
-
currentDuration = max(0, duration)
|
|
82
|
-
let now = Date()
|
|
83
|
-
if now.timeIntervalSince(lastUpdateTime) >= minUpdateInterval {
|
|
84
|
-
updateNotification()
|
|
85
|
-
lastUpdateTime = now
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
private func updateNotification(forcePauseState: Bool? = nil) {
|
|
90
|
-
// First, check if we already have a notification
|
|
91
|
-
notificationCenter.getDeliveredNotifications { [weak self] notifications in
|
|
92
|
-
guard let self = self else { return }
|
|
93
|
-
|
|
94
|
-
// If we have a notification and it was recently updated, skip
|
|
95
|
-
if let existing = notifications.first(where: { $0.request.identifier == self.notificationId }),
|
|
96
|
-
Date().timeIntervalSince(self.lastUpdateTime) < self.minUpdateInterval {
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Create notification content
|
|
101
|
-
let content = UNMutableNotificationContent()
|
|
102
|
-
content.title = forcePauseState == true ?
|
|
103
|
-
"Recording Paused" :
|
|
104
|
-
(self.config?.title ?? "Recording in Progress")
|
|
105
|
-
|
|
106
|
-
let durationText = self.formatDuration(self.currentDuration)
|
|
107
|
-
let configText = self.config?.text ?? ""
|
|
108
|
-
content.body = configText.isEmpty ? durationText : "\(configText) - \(durationText)"
|
|
109
|
-
|
|
110
|
-
content.categoryIdentifier = self.config?.ios?.categoryIdentifier ?? "recording_category"
|
|
111
|
-
content.sound = nil
|
|
112
|
-
|
|
113
|
-
// Create request
|
|
114
|
-
let request = UNNotificationRequest(
|
|
115
|
-
identifier: self.notificationId,
|
|
116
|
-
content: content,
|
|
117
|
-
trigger: nil
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
// Replace the existing notification
|
|
121
|
-
self.notificationCenter.removeDeliveredNotifications(withIdentifiers: [self.notificationId])
|
|
122
|
-
self.notificationCenter.add(request) { error in
|
|
123
|
-
if let error = error {
|
|
124
|
-
print("Failed to update notification: \(error)")
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
private func formatDuration(_ duration: TimeInterval) -> String {
|
|
131
|
-
let minutes = Int(duration) / 60
|
|
132
|
-
let seconds = Int(duration) % 60
|
|
133
|
-
return String(format: "%02d:%02d", minutes, seconds)
|
|
134
|
-
}
|
|
135
|
-
}
|