@siteed/expo-audio-stream 1.16.0 → 1.17.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.
@@ -1 +1 @@
1
- {"version":3,"file":"WebRecorder.web.js","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"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","sourcesContent":["// src/WebRecorder.ts\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { ConsoleLike, RecordingConfig } from './ExpoAudioStream.types'\nimport {\n EmitAudioAnalysisFunction,\n EmitAudioEventFunction,\n} from './ExpoAudioStream.web'\nimport { convertPCMToFloat32 } from './utils/convertPCMToFloat32'\nimport { encodingToBitDepth } from './utils/encodingToBitDepth'\nimport { writeWavHeader } from './utils/writeWavHeader'\nimport { InlineFeaturesExtractor } from './workers/InlineFeaturesExtractor.web'\nimport { InlineAudioWebWorker } from './workers/inlineAudioWebWorker.web'\n\ninterface AudioWorkletEvent {\n data: {\n command: string\n recordedData?: Float32Array\n sampleRate?: number\n }\n}\n\ninterface AudioFeaturesEvent {\n data: {\n command: string\n result: AudioAnalysis\n }\n}\n\nconst DEFAULT_WEB_BITDEPTH = 32\nconst DEFAULT_WEB_POINTS_PER_SECOND = 10\nconst DEFAULT_WEB_INTERVAL = 500\nconst DEFAULT_WEB_NUMBER_OF_CHANNELS = 1\nconst DEFAULT_ALGORITHM = 'rms'\n\nconst TAG = 'WebRecorder'\n\nconst STOP_PERFORMANCE_MARKS = {\n STOP_INITIATED: 'stopInitiated',\n COMPRESSED_RECORDING_STOP: 'compressedRecordingStop',\n AUDIO_WORKLET_STOP: 'audioWorkletStop',\n CLEANUP: 'cleanup',\n TOTAL_STOP_TIME: 'totalStopTime',\n} as const\n\nexport class WebRecorder {\n private audioContext: AudioContext\n private audioWorkletNode!: AudioWorkletNode\n private featureExtractorWorker?: Worker\n private source: MediaStreamAudioSourceNode\n private audioWorkletUrl: string\n private emitAudioEventCallback: EmitAudioEventFunction\n private emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n private config: RecordingConfig\n private position: number = 0\n private numberOfChannels: number // Number of audio channels\n private bitDepth: number // Bit depth of the audio\n private exportBitDepth: number // Bit depth of the audio\n private audioAnalysisData: AudioAnalysis // Keep updating the full audio analysis data with latest events\n private packetCount: number = 0\n private logger?: ConsoleLike\n private compressedMediaRecorder: MediaRecorder | null = null\n private compressedChunks: Blob[] = []\n private compressedSize: number = 0\n private pendingCompressedChunk: Blob | null = null\n private readonly wavMimeType = 'audio/wav'\n\n constructor({\n audioContext,\n source,\n recordingConfig,\n audioWorkletUrl,\n emitAudioEventCallback,\n emitAudioAnalysisCallback,\n logger,\n }: {\n audioContext: AudioContext\n source: MediaStreamAudioSourceNode\n recordingConfig: RecordingConfig\n audioWorkletUrl: string\n emitAudioEventCallback: EmitAudioEventFunction\n emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n logger?: ConsoleLike\n }) {\n this.audioContext = audioContext\n this.source = source\n this.audioWorkletUrl = audioWorkletUrl\n this.emitAudioEventCallback = emitAudioEventCallback\n this.emitAudioAnalysisCallback = emitAudioAnalysisCallback\n this.config = recordingConfig\n this.logger = logger\n\n const audioContextFormat = this.checkAudioContextFormat({\n sampleRate: this.audioContext.sampleRate,\n })\n this.logger?.debug('Initialized WebRecorder with config:', {\n sampleRate: audioContextFormat.sampleRate,\n bitDepth: audioContextFormat.bitDepth,\n numberOfChannels: audioContextFormat.numberOfChannels,\n })\n\n this.bitDepth = audioContextFormat.bitDepth\n this.numberOfChannels =\n audioContextFormat.numberOfChannels ||\n DEFAULT_WEB_NUMBER_OF_CHANNELS // Default to 1 if not available\n this.exportBitDepth =\n encodingToBitDepth({\n encoding: recordingConfig.encoding ?? 'pcm_32bit',\n }) ||\n audioContextFormat.bitDepth ||\n DEFAULT_WEB_BITDEPTH\n\n this.audioAnalysisData = {\n amplitudeRange: { min: 0, max: 0 },\n dataPoints: [],\n durationMs: 0,\n samples: 0,\n amplitudeAlgorithm: recordingConfig.algorithm || DEFAULT_ALGORITHM,\n bitDepth: this.bitDepth,\n numberOfChannels: this.numberOfChannels,\n sampleRate: this.config.sampleRate || this.audioContext.sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond || DEFAULT_WEB_POINTS_PER_SECOND,\n speakerChanges: [],\n }\n\n if (recordingConfig.enableProcessing) {\n this.initFeatureExtractorWorker()\n }\n\n // Initialize compressed recording if enabled\n if (recordingConfig.compression?.enabled) {\n this.initializeCompressedRecorder()\n }\n }\n\n async init() {\n try {\n if (!this.audioWorkletUrl) {\n const blob = new Blob([InlineAudioWebWorker], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n await this.audioContext.audioWorklet.addModule(url)\n } else {\n await this.audioContext.audioWorklet.addModule(\n this.audioWorkletUrl\n )\n }\n this.audioWorkletNode = new AudioWorkletNode(\n this.audioContext,\n 'recorder-processor'\n )\n\n this.audioWorkletNode.port.onmessage = async (\n event: AudioWorkletEvent\n ) => {\n const command = event.data.command\n if (command !== 'newData') return\n\n const pcmBufferFloat = event.data.recordedData\n if (!pcmBufferFloat) {\n this.logger?.warn('Received empty audio buffer', event)\n return\n }\n\n // Process data in smaller chunks and emit immediately\n const chunkSize = this.audioContext.sampleRate * 2 // Reduce to 2 seconds chunks\n const sampleRate =\n event.data.sampleRate ?? this.audioContext.sampleRate\n const duration = pcmBufferFloat.length / sampleRate\n\n // Emit chunks without storing them\n for (let i = 0; i < pcmBufferFloat.length; i += chunkSize) {\n const chunk = pcmBufferFloat.slice(i, i + chunkSize)\n const chunkPosition = this.position + i / sampleRate\n\n // Process features if enabled\n if (\n this.config.enableProcessing &&\n this.featureExtractorWorker\n ) {\n this.featureExtractorWorker.postMessage(\n {\n command: 'process',\n channelData: chunk,\n sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond ||\n DEFAULT_WEB_POINTS_PER_SECOND,\n algorithm: this.config.algorithm || 'rms',\n bitDepth: this.bitDepth,\n fullAudioDurationMs: this.position * 1000,\n numberOfChannels: this.numberOfChannels,\n features: this.config.features,\n },\n []\n )\n }\n\n // Emit chunk immediately\n this.emitAudioEventCallback({\n data: chunk,\n position: chunkPosition,\n compression: this.pendingCompressedChunk\n ? {\n data: this.pendingCompressedChunk,\n size: this.pendingCompressedChunk.size,\n totalSize: this.compressedSize,\n mimeType: 'audio/webm',\n format: 'opus',\n bitrate:\n this.config.compression?.bitrate ??\n 128000,\n }\n : undefined,\n })\n }\n\n this.position += duration\n this.pendingCompressedChunk = null\n }\n\n this.logger?.debug(\n `WebRecorder initialized -- recordSampleRate=${this.audioContext.sampleRate}`,\n this.config\n )\n this.audioWorkletNode.port.postMessage({\n command: 'init',\n recordSampleRate: this.audioContext.sampleRate,\n exportSampleRate:\n this.config.sampleRate ?? this.audioContext.sampleRate,\n bitDepth: this.bitDepth,\n exportBitDepth: this.exportBitDepth,\n channels: this.numberOfChannels,\n interval: this.config.interval ?? DEFAULT_WEB_INTERVAL,\n })\n\n // Connect the source to the AudioWorkletNode and start recording\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n } catch (error) {\n console.error(`[${TAG}] Failed to initialize WebRecorder`, error)\n }\n }\n\n initFeatureExtractorWorker(featuresExtratorUrl?: string) {\n try {\n if (featuresExtratorUrl) {\n // Initialize the feature extractor worker\n //TODO: create audio feature extractor from a Blob instead of url since we cannot include the url directly in the library\n // We keep the url during dev and use the blob in production.\n this.featureExtractorWorker = new Worker(\n new URL(featuresExtratorUrl, window.location.href)\n )\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror =\n this.handleWorkerError.bind(this)\n } else {\n // Fallback to the inline worker if the URL is not provided\n this.initFallbackWorker()\n }\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize feature extractor worker`,\n error\n )\n this.initFallbackWorker()\n }\n }\n\n initFallbackWorker() {\n try {\n const blob = new Blob([InlineFeaturesExtractor], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n this.featureExtractorWorker = new Worker(url)\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror = (error) => {\n console.error(`[${TAG}] Default Inline worker failed`, error)\n }\n this.logger?.log('Inline worker initialized successfully')\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize Inline Feature Extractor worker`,\n error\n )\n }\n }\n\n handleWorkerError(error: ErrorEvent) {\n console.error(`[${TAG}] Feature extractor worker error:`, error)\n }\n\n handleFeatureExtractorMessage(event: AudioFeaturesEvent) {\n if (event.data.command === 'features') {\n const segmentResult = event.data.result\n\n // Merge the segment result with the full audio analysis data\n this.audioAnalysisData.dataPoints.push(...segmentResult.dataPoints)\n this.audioAnalysisData.speakerChanges?.push(\n ...(segmentResult.speakerChanges ?? [])\n )\n this.audioAnalysisData.durationMs = segmentResult.durationMs\n if (segmentResult.amplitudeRange) {\n this.audioAnalysisData.amplitudeRange = {\n min: Math.min(\n this.audioAnalysisData.amplitudeRange.min,\n segmentResult.amplitudeRange.min\n ),\n max: Math.max(\n this.audioAnalysisData.amplitudeRange.max,\n segmentResult.amplitudeRange.max\n ),\n }\n }\n // Handle the extracted features (e.g., emit an event or log them)\n this.logger?.debug('features event segmentResult', segmentResult)\n this.logger?.debug(\n `features event audioAnalysisData duration=${this.audioAnalysisData.durationMs}`,\n this.audioAnalysisData\n )\n this.emitAudioAnalysisCallback(segmentResult)\n }\n }\n\n start() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.packetCount = 0\n\n if (this.compressedMediaRecorder) {\n this.compressedMediaRecorder.start(this.config.interval ?? 1000)\n }\n }\n\n async stop(): Promise<{ pcmData: Float32Array; compressedBlob?: Blob }> {\n try {\n if (this.compressedMediaRecorder) {\n this.compressedMediaRecorder.stop()\n return {\n pcmData: new Float32Array(), // Return empty array since we're streaming\n compressedBlob: new Blob(this.compressedChunks, {\n type: 'audio/webm;codecs=opus',\n }),\n }\n }\n return { pcmData: new Float32Array() }\n } finally {\n this.cleanup()\n // Reset the chunks array\n this.compressedChunks = []\n this.compressedSize = 0\n this.pendingCompressedChunk = null\n }\n }\n\n private cleanup() {\n if (this.audioContext) {\n this.audioContext.close()\n }\n if (this.audioWorkletNode) {\n this.audioWorkletNode.disconnect()\n }\n if (this.source) {\n this.source.disconnect()\n }\n this.stopMediaStreamTracks()\n }\n\n // Helper method to process recording stop\n private async processRecordingStop(): Promise<{\n pcmData: Float32Array\n compressedBlob?: Blob\n }> {\n const processStartTime = performance.now()\n this.logger?.debug('[Performance] Starting recording stop process')\n\n const [compressedData, workletData] = await Promise.all([\n this.stopCompressedRecording(),\n this.stopAudioWorklet(),\n ])\n\n this.logger?.debug(\n `[Performance] Recording stop process completed in ${performance.now() - processStartTime}ms`\n )\n return {\n pcmData:\n workletData ??\n new Float32Array(this.audioAnalysisData.dataPoints.length),\n compressedBlob: compressedData,\n }\n }\n\n // Helper method to stop compressed recording\n private stopCompressedRecording(): Promise<Blob | undefined> {\n const startTime = performance.now()\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.COMPRESSED_RECORDING_STOP}] Starting compressed recording stop`\n )\n\n if (!this.compressedMediaRecorder) {\n this.logger?.debug('[Performance] No compressed recorder to stop')\n return Promise.resolve(undefined)\n }\n\n return new Promise((resolve) => {\n this.compressedMediaRecorder!.onstop = () => {\n const blob = new Blob(this.compressedChunks, {\n type: 'audio/webm;codecs=opus',\n })\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.COMPRESSED_RECORDING_STOP}] Compressed recording stopped in ${performance.now() - startTime}ms, size: ${blob.size}`\n )\n resolve(blob)\n }\n this.compressedMediaRecorder!.stop()\n })\n }\n\n // Helper method to stop audio worklet\n private stopAudioWorklet(): Promise<Float32Array | undefined> {\n const startTime = performance.now()\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Starting audio worklet stop`\n )\n\n if (!this.audioWorkletNode) {\n this.logger?.debug('[Performance] No audio worklet to stop')\n return Promise.resolve(undefined)\n }\n\n return new Promise((resolve) => {\n const onMessage = (event: AudioWorkletEvent) => {\n if (event.data.command === 'recordedData') {\n this.audioWorkletNode?.port.removeEventListener(\n 'message',\n onMessage\n )\n const rawPCMDataFull = event.data.recordedData?.slice(0)\n\n if (!rawPCMDataFull) {\n this.logger?.debug('[Performance] No PCM data received')\n resolve(undefined)\n return\n }\n\n if (this.exportBitDepth !== this.bitDepth) {\n const conversionStart = performance.now()\n convertPCMToFloat32({\n buffer: rawPCMDataFull.buffer,\n bitDepth: this.exportBitDepth,\n skipWavHeader: true,\n logger: this.logger,\n }).then(({ pcmValues }) => {\n this.logger?.debug(\n `[Performance] PCM conversion completed in ${performance.now() - conversionStart}ms`\n )\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Audio worklet stopped in ${performance.now() - startTime}ms`\n )\n resolve(pcmValues)\n })\n } else {\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Audio worklet stopped in ${performance.now() - startTime}ms`\n )\n resolve(rawPCMDataFull)\n }\n }\n }\n\n this.audioWorkletNode.port.addEventListener('message', onMessage)\n this.audioWorkletNode.port.postMessage({ command: 'stop' })\n })\n }\n\n pause() {\n this.source.disconnect(this.audioWorkletNode) // Disconnect the source from the AudioWorkletNode\n this.audioWorkletNode.disconnect(this.audioContext.destination) // Disconnect the AudioWorkletNode from the destination\n this.audioWorkletNode.port.postMessage({ command: 'pause' })\n this.compressedMediaRecorder?.pause()\n }\n\n stopMediaStreamTracks() {\n // Stop all audio tracks to stop the recording icon\n const tracks = this.source.mediaStream.getTracks()\n tracks.forEach((track) => track.stop())\n }\n\n async playRecordedData({\n recordedData,\n }: {\n recordedData: ArrayBuffer\n mimeType?: string\n }) {\n try {\n // Create a WAV blob with proper headers\n const wavHeaderBuffer = writeWavHeader({\n buffer: recordedData,\n sampleRate: this.audioContext.sampleRate,\n numChannels: this.numberOfChannels,\n bitDepth: this.exportBitDepth,\n })\n\n const blob = new Blob([wavHeaderBuffer], { type: 'audio/wav' })\n const url = URL.createObjectURL(blob)\n const response = await fetch(url)\n const arrayBuffer = await response.arrayBuffer()\n\n // Decode the audio data\n const audioBuffer =\n await this.audioContext.decodeAudioData(arrayBuffer)\n\n // Create a buffer source node and play the audio\n const bufferSource = this.audioContext.createBufferSource()\n bufferSource.buffer = audioBuffer\n bufferSource.connect(this.audioContext.destination)\n bufferSource.start()\n this.logger?.debug('Playing recorded data', recordedData)\n\n // Clean up\n URL.revokeObjectURL(url)\n } catch (error) {\n console.error(`[${TAG}] Failed to play recorded data:`, error)\n }\n }\n\n private checkAudioContextFormat({ sampleRate }: { sampleRate: number }) {\n // Create a silent AudioBuffer\n const frameCount = sampleRate * 1.0 // 1 second buffer\n const audioBuffer = this.audioContext.createBuffer(\n 1,\n frameCount,\n sampleRate\n )\n\n // Check the format\n const channelData = audioBuffer.getChannelData(0)\n const bitDepth = channelData.BYTES_PER_ELEMENT * 8 // 4 bytes per element means 32-bit\n\n return {\n sampleRate: audioBuffer.sampleRate,\n bitDepth,\n numberOfChannels: audioBuffer.numberOfChannels,\n }\n }\n\n resume() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.audioWorkletNode.port.postMessage({ command: 'resume' })\n this.compressedMediaRecorder?.resume()\n }\n\n private initializeCompressedRecorder() {\n try {\n const mimeType = 'audio/webm;codecs=opus'\n if (!MediaRecorder.isTypeSupported(mimeType)) {\n this.logger?.warn(\n 'Opus compression not supported in this browser'\n )\n return\n }\n\n this.compressedMediaRecorder = new MediaRecorder(\n this.source.mediaStream,\n {\n mimeType,\n audioBitsPerSecond:\n this.config.compression?.bitrate ?? 128000,\n }\n )\n\n this.compressedMediaRecorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n this.compressedChunks.push(event.data)\n this.compressedSize += event.data.size\n this.pendingCompressedChunk = event.data\n }\n }\n } catch (error) {\n this.logger?.error(\n 'Failed to initialize compressed recorder:',\n error\n )\n }\n }\n}\n"]}
1
+ {"version":3,"file":"WebRecorder.web.js","sourceRoot":"","sources":["../src/WebRecorder.web.ts"],"names":[],"mappings":"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","sourcesContent":["// src/WebRecorder.ts\n\nimport { AudioAnalysis } from './AudioAnalysis/AudioAnalysis.types'\nimport { ConsoleLike, RecordingConfig } from './ExpoAudioStream.types'\nimport {\n EmitAudioAnalysisFunction,\n EmitAudioEventFunction,\n} from './ExpoAudioStream.web'\nimport { convertPCMToFloat32 } from './utils/convertPCMToFloat32'\nimport { encodingToBitDepth } from './utils/encodingToBitDepth'\nimport { writeWavHeader } from './utils/writeWavHeader'\nimport { InlineFeaturesExtractor } from './workers/InlineFeaturesExtractor.web'\nimport { InlineAudioWebWorker } from './workers/inlineAudioWebWorker.web'\n\ninterface AudioWorkletEvent {\n data: {\n command: string\n recordedData?: Float32Array\n sampleRate?: number\n }\n}\n\ninterface AudioFeaturesEvent {\n data: {\n command: string\n result: AudioAnalysis\n }\n}\n\nconst DEFAULT_WEB_BITDEPTH = 32\nconst DEFAULT_WEB_POINTS_PER_SECOND = 10\nconst DEFAULT_WEB_INTERVAL = 500\nconst DEFAULT_WEB_NUMBER_OF_CHANNELS = 1\nconst DEFAULT_ALGORITHM = 'rms'\n\nconst TAG = 'WebRecorder'\n\nconst STOP_PERFORMANCE_MARKS = {\n STOP_INITIATED: 'stopInitiated',\n COMPRESSED_RECORDING_STOP: 'compressedRecordingStop',\n AUDIO_WORKLET_STOP: 'audioWorkletStop',\n CLEANUP: 'cleanup',\n TOTAL_STOP_TIME: 'totalStopTime',\n} as const\n\nexport class WebRecorder {\n private audioContext: AudioContext\n private audioWorkletNode!: AudioWorkletNode\n private featureExtractorWorker?: Worker\n private source: MediaStreamAudioSourceNode\n private audioWorkletUrl: string\n private emitAudioEventCallback: EmitAudioEventFunction\n private emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n private config: RecordingConfig\n private position: number = 0\n private numberOfChannels: number // Number of audio channels\n private bitDepth: number // Bit depth of the audio\n private exportBitDepth: number // Bit depth of the audio\n private audioAnalysisData: AudioAnalysis // Keep updating the full audio analysis data with latest events\n private packetCount: number = 0\n private logger?: ConsoleLike\n private compressedMediaRecorder: MediaRecorder | null = null\n private compressedChunks: Blob[] = []\n private compressedSize: number = 0\n private pendingCompressedChunk: Blob | null = null\n private readonly wavMimeType = 'audio/wav'\n\n constructor({\n audioContext,\n source,\n recordingConfig,\n audioWorkletUrl,\n emitAudioEventCallback,\n emitAudioAnalysisCallback,\n logger,\n }: {\n audioContext: AudioContext\n source: MediaStreamAudioSourceNode\n recordingConfig: RecordingConfig\n audioWorkletUrl: string\n emitAudioEventCallback: EmitAudioEventFunction\n emitAudioAnalysisCallback: EmitAudioAnalysisFunction\n logger?: ConsoleLike\n }) {\n this.audioContext = audioContext\n this.source = source\n this.audioWorkletUrl = audioWorkletUrl\n this.emitAudioEventCallback = emitAudioEventCallback\n this.emitAudioAnalysisCallback = emitAudioAnalysisCallback\n this.config = recordingConfig\n this.logger = logger\n\n const audioContextFormat = this.checkAudioContextFormat({\n sampleRate: this.audioContext.sampleRate,\n })\n this.logger?.debug('Initialized WebRecorder with config:', {\n sampleRate: audioContextFormat.sampleRate,\n bitDepth: audioContextFormat.bitDepth,\n numberOfChannels: audioContextFormat.numberOfChannels,\n })\n\n this.bitDepth = audioContextFormat.bitDepth\n this.numberOfChannels =\n audioContextFormat.numberOfChannels ||\n DEFAULT_WEB_NUMBER_OF_CHANNELS // Default to 1 if not available\n this.exportBitDepth =\n encodingToBitDepth({\n encoding: recordingConfig.encoding ?? 'pcm_32bit',\n }) ||\n audioContextFormat.bitDepth ||\n DEFAULT_WEB_BITDEPTH\n\n this.audioAnalysisData = {\n amplitudeRange: { min: 0, max: 0 },\n dataPoints: [],\n durationMs: 0,\n samples: 0,\n amplitudeAlgorithm: recordingConfig.algorithm || DEFAULT_ALGORITHM,\n bitDepth: this.bitDepth,\n numberOfChannels: this.numberOfChannels,\n sampleRate: this.config.sampleRate || this.audioContext.sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond || DEFAULT_WEB_POINTS_PER_SECOND,\n speakerChanges: [],\n }\n\n if (recordingConfig.enableProcessing) {\n this.initFeatureExtractorWorker()\n }\n\n // Initialize compressed recording if enabled\n if (recordingConfig.compression?.enabled) {\n this.initializeCompressedRecorder()\n }\n }\n\n async init() {\n try {\n if (!this.audioWorkletUrl) {\n const blob = new Blob([InlineAudioWebWorker], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n await this.audioContext.audioWorklet.addModule(url)\n } else {\n await this.audioContext.audioWorklet.addModule(\n this.audioWorkletUrl\n )\n }\n this.audioWorkletNode = new AudioWorkletNode(\n this.audioContext,\n 'recorder-processor'\n )\n\n this.audioWorkletNode.port.onmessage = async (\n event: AudioWorkletEvent\n ) => {\n const command = event.data.command\n if (command !== 'newData') return\n\n const pcmBufferFloat = event.data.recordedData\n if (!pcmBufferFloat) {\n this.logger?.warn('Received empty audio buffer', event)\n return\n }\n\n // Process data in smaller chunks and emit immediately\n const chunkSize = this.audioContext.sampleRate * 2 // Reduce to 2 seconds chunks\n const sampleRate =\n event.data.sampleRate ?? this.audioContext.sampleRate\n const duration = pcmBufferFloat.length / sampleRate\n\n // Emit chunks without storing them\n for (let i = 0; i < pcmBufferFloat.length; i += chunkSize) {\n const chunk = pcmBufferFloat.slice(i, i + chunkSize)\n const chunkPosition = this.position + i / sampleRate\n\n // Process features if enabled\n if (\n this.config.enableProcessing &&\n this.featureExtractorWorker\n ) {\n this.featureExtractorWorker.postMessage(\n {\n command: 'process',\n channelData: chunk,\n sampleRate,\n pointsPerSecond:\n this.config.pointsPerSecond ||\n DEFAULT_WEB_POINTS_PER_SECOND,\n algorithm: this.config.algorithm || 'rms',\n bitDepth: this.bitDepth,\n fullAudioDurationMs: this.position * 1000,\n numberOfChannels: this.numberOfChannels,\n features: this.config.features,\n intervalAnalysis: this.config.intervalAnalysis,\n },\n []\n )\n }\n\n // Emit chunk immediately\n this.emitAudioEventCallback({\n data: chunk,\n position: chunkPosition,\n compression: this.pendingCompressedChunk\n ? {\n data: this.pendingCompressedChunk,\n size: this.pendingCompressedChunk.size,\n totalSize: this.compressedSize,\n mimeType: 'audio/webm',\n format: 'opus',\n bitrate:\n this.config.compression?.bitrate ??\n 128000,\n }\n : undefined,\n })\n }\n\n this.position += duration\n this.pendingCompressedChunk = null\n }\n\n this.logger?.debug(\n `WebRecorder initialized -- recordSampleRate=${this.audioContext.sampleRate}`,\n this.config\n )\n this.audioWorkletNode.port.postMessage({\n command: 'init',\n recordSampleRate: this.audioContext.sampleRate,\n exportSampleRate:\n this.config.sampleRate ?? this.audioContext.sampleRate,\n bitDepth: this.bitDepth,\n exportBitDepth: this.exportBitDepth,\n channels: this.numberOfChannels,\n interval: this.config.interval ?? DEFAULT_WEB_INTERVAL,\n })\n\n // Connect the source to the AudioWorkletNode and start recording\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n } catch (error) {\n console.error(`[${TAG}] Failed to initialize WebRecorder`, error)\n }\n }\n\n initFeatureExtractorWorker(featuresExtratorUrl?: string) {\n try {\n if (featuresExtratorUrl) {\n // Initialize the feature extractor worker\n //TODO: create audio feature extractor from a Blob instead of url since we cannot include the url directly in the library\n // We keep the url during dev and use the blob in production.\n this.featureExtractorWorker = new Worker(\n new URL(featuresExtratorUrl, window.location.href)\n )\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror =\n this.handleWorkerError.bind(this)\n } else {\n // Fallback to the inline worker if the URL is not provided\n this.initFallbackWorker()\n }\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize feature extractor worker`,\n error\n )\n this.initFallbackWorker()\n }\n }\n\n initFallbackWorker() {\n try {\n const blob = new Blob([InlineFeaturesExtractor], {\n type: 'application/javascript',\n })\n const url = URL.createObjectURL(blob)\n this.featureExtractorWorker = new Worker(url)\n this.featureExtractorWorker.onmessage =\n this.handleFeatureExtractorMessage.bind(this)\n this.featureExtractorWorker.onerror = (error) => {\n console.error(`[${TAG}] Default Inline worker failed`, error)\n }\n this.logger?.log('Inline worker initialized successfully')\n } catch (error) {\n console.error(\n `[${TAG}] Failed to initialize Inline Feature Extractor worker`,\n error\n )\n }\n }\n\n handleWorkerError(error: ErrorEvent) {\n console.error(`[${TAG}] Feature extractor worker error:`, error)\n }\n\n handleFeatureExtractorMessage(event: AudioFeaturesEvent) {\n if (event.data.command === 'features') {\n const segmentResult = event.data.result\n\n // Merge the segment result with the full audio analysis data\n this.audioAnalysisData.dataPoints.push(...segmentResult.dataPoints)\n this.audioAnalysisData.speakerChanges?.push(\n ...(segmentResult.speakerChanges ?? [])\n )\n this.audioAnalysisData.durationMs = segmentResult.durationMs\n if (segmentResult.amplitudeRange) {\n this.audioAnalysisData.amplitudeRange = {\n min: Math.min(\n this.audioAnalysisData.amplitudeRange.min,\n segmentResult.amplitudeRange.min\n ),\n max: Math.max(\n this.audioAnalysisData.amplitudeRange.max,\n segmentResult.amplitudeRange.max\n ),\n }\n }\n // Handle the extracted features (e.g., emit an event or log them)\n this.logger?.debug('features event segmentResult', segmentResult)\n this.logger?.debug(\n `features event audioAnalysisData duration=${this.audioAnalysisData.durationMs}`,\n this.audioAnalysisData\n )\n this.emitAudioAnalysisCallback(segmentResult)\n }\n }\n\n start() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.packetCount = 0\n\n if (this.compressedMediaRecorder) {\n this.compressedMediaRecorder.start(this.config.interval ?? 1000)\n }\n }\n\n async stop(): Promise<{ pcmData: Float32Array; compressedBlob?: Blob }> {\n try {\n if (this.compressedMediaRecorder) {\n this.compressedMediaRecorder.stop()\n return {\n pcmData: new Float32Array(), // Return empty array since we're streaming\n compressedBlob: new Blob(this.compressedChunks, {\n type: 'audio/webm;codecs=opus',\n }),\n }\n }\n return { pcmData: new Float32Array() }\n } finally {\n this.cleanup()\n // Reset the chunks array\n this.compressedChunks = []\n this.compressedSize = 0\n this.pendingCompressedChunk = null\n }\n }\n\n private cleanup() {\n if (this.audioContext) {\n this.audioContext.close()\n }\n if (this.audioWorkletNode) {\n this.audioWorkletNode.disconnect()\n }\n if (this.source) {\n this.source.disconnect()\n }\n this.stopMediaStreamTracks()\n }\n\n // Helper method to process recording stop\n private async processRecordingStop(): Promise<{\n pcmData: Float32Array\n compressedBlob?: Blob\n }> {\n const processStartTime = performance.now()\n this.logger?.debug('[Performance] Starting recording stop process')\n\n const [compressedData, workletData] = await Promise.all([\n this.stopCompressedRecording(),\n this.stopAudioWorklet(),\n ])\n\n this.logger?.debug(\n `[Performance] Recording stop process completed in ${performance.now() - processStartTime}ms`\n )\n return {\n pcmData:\n workletData ??\n new Float32Array(this.audioAnalysisData.dataPoints.length),\n compressedBlob: compressedData,\n }\n }\n\n // Helper method to stop compressed recording\n private stopCompressedRecording(): Promise<Blob | undefined> {\n const startTime = performance.now()\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.COMPRESSED_RECORDING_STOP}] Starting compressed recording stop`\n )\n\n if (!this.compressedMediaRecorder) {\n this.logger?.debug('[Performance] No compressed recorder to stop')\n return Promise.resolve(undefined)\n }\n\n return new Promise((resolve) => {\n this.compressedMediaRecorder!.onstop = () => {\n const blob = new Blob(this.compressedChunks, {\n type: 'audio/webm;codecs=opus',\n })\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.COMPRESSED_RECORDING_STOP}] Compressed recording stopped in ${performance.now() - startTime}ms, size: ${blob.size}`\n )\n resolve(blob)\n }\n this.compressedMediaRecorder!.stop()\n })\n }\n\n // Helper method to stop audio worklet\n private stopAudioWorklet(): Promise<Float32Array | undefined> {\n const startTime = performance.now()\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Starting audio worklet stop`\n )\n\n if (!this.audioWorkletNode) {\n this.logger?.debug('[Performance] No audio worklet to stop')\n return Promise.resolve(undefined)\n }\n\n return new Promise((resolve) => {\n const onMessage = (event: AudioWorkletEvent) => {\n if (event.data.command === 'recordedData') {\n this.audioWorkletNode?.port.removeEventListener(\n 'message',\n onMessage\n )\n const rawPCMDataFull = event.data.recordedData?.slice(0)\n\n if (!rawPCMDataFull) {\n this.logger?.debug('[Performance] No PCM data received')\n resolve(undefined)\n return\n }\n\n if (this.exportBitDepth !== this.bitDepth) {\n const conversionStart = performance.now()\n convertPCMToFloat32({\n buffer: rawPCMDataFull.buffer,\n bitDepth: this.exportBitDepth,\n skipWavHeader: true,\n logger: this.logger,\n }).then(({ pcmValues }) => {\n this.logger?.debug(\n `[Performance] PCM conversion completed in ${performance.now() - conversionStart}ms`\n )\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Audio worklet stopped in ${performance.now() - startTime}ms`\n )\n resolve(pcmValues)\n })\n } else {\n this.logger?.debug(\n `[Performance][${STOP_PERFORMANCE_MARKS.AUDIO_WORKLET_STOP}] Audio worklet stopped in ${performance.now() - startTime}ms`\n )\n resolve(rawPCMDataFull)\n }\n }\n }\n\n this.audioWorkletNode.port.addEventListener('message', onMessage)\n this.audioWorkletNode.port.postMessage({ command: 'stop' })\n })\n }\n\n pause() {\n this.source.disconnect(this.audioWorkletNode) // Disconnect the source from the AudioWorkletNode\n this.audioWorkletNode.disconnect(this.audioContext.destination) // Disconnect the AudioWorkletNode from the destination\n this.audioWorkletNode.port.postMessage({ command: 'pause' })\n this.compressedMediaRecorder?.pause()\n }\n\n stopMediaStreamTracks() {\n // Stop all audio tracks to stop the recording icon\n const tracks = this.source.mediaStream.getTracks()\n tracks.forEach((track) => track.stop())\n }\n\n async playRecordedData({\n recordedData,\n }: {\n recordedData: ArrayBuffer\n mimeType?: string\n }) {\n try {\n // Create a WAV blob with proper headers\n const wavHeaderBuffer = writeWavHeader({\n buffer: recordedData,\n sampleRate: this.audioContext.sampleRate,\n numChannels: this.numberOfChannels,\n bitDepth: this.exportBitDepth,\n })\n\n const blob = new Blob([wavHeaderBuffer], { type: 'audio/wav' })\n const url = URL.createObjectURL(blob)\n const response = await fetch(url)\n const arrayBuffer = await response.arrayBuffer()\n\n // Decode the audio data\n const audioBuffer =\n await this.audioContext.decodeAudioData(arrayBuffer)\n\n // Create a buffer source node and play the audio\n const bufferSource = this.audioContext.createBufferSource()\n bufferSource.buffer = audioBuffer\n bufferSource.connect(this.audioContext.destination)\n bufferSource.start()\n this.logger?.debug('Playing recorded data', recordedData)\n\n // Clean up\n URL.revokeObjectURL(url)\n } catch (error) {\n console.error(`[${TAG}] Failed to play recorded data:`, error)\n }\n }\n\n private checkAudioContextFormat({ sampleRate }: { sampleRate: number }) {\n // Create a silent AudioBuffer\n const frameCount = sampleRate * 1.0 // 1 second buffer\n const audioBuffer = this.audioContext.createBuffer(\n 1,\n frameCount,\n sampleRate\n )\n\n // Check the format\n const channelData = audioBuffer.getChannelData(0)\n const bitDepth = channelData.BYTES_PER_ELEMENT * 8 // 4 bytes per element means 32-bit\n\n return {\n sampleRate: audioBuffer.sampleRate,\n bitDepth,\n numberOfChannels: audioBuffer.numberOfChannels,\n }\n }\n\n resume() {\n this.source.connect(this.audioWorkletNode)\n this.audioWorkletNode.connect(this.audioContext.destination)\n this.audioWorkletNode.port.postMessage({ command: 'resume' })\n this.compressedMediaRecorder?.resume()\n }\n\n private initializeCompressedRecorder() {\n try {\n const mimeType = 'audio/webm;codecs=opus'\n if (!MediaRecorder.isTypeSupported(mimeType)) {\n this.logger?.warn(\n 'Opus compression not supported in this browser'\n )\n return\n }\n\n this.compressedMediaRecorder = new MediaRecorder(\n this.source.mediaStream,\n {\n mimeType,\n audioBitsPerSecond:\n this.config.compression?.bitrate ?? 128000,\n }\n )\n\n this.compressedMediaRecorder.ondataavailable = (event) => {\n if (event.data.size > 0) {\n this.compressedChunks.push(event.data)\n this.compressedSize += event.data.size\n this.pendingCompressedChunk = event.data\n }\n }\n } catch (error) {\n this.logger?.error(\n 'Failed to initialize compressed recorder:',\n error\n )\n }\n }\n}\n"]}
@@ -1,2 +1,2 @@
1
- export declare const InlineFeaturesExtractor = "\n// Unique ID counter\nlet uniqueIdCounter = 0\n\nself.onmessage = function (event) {\n const {\n channelData, // this is only the newly recorded data when live recording.\n sampleRate,\n pointsPerSecond,\n algorithm,\n bitDepth,\n fullAudioDurationMs,\n numberOfChannels,\n features: _features,\n } = event.data\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 const extractHNR = (segmentData) => {\n const frameSize = segmentData.length\n const autocorrelation = new Float32Array(frameSize)\n\n // Compute the autocorrelation of the segment data\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 (excluding the zero lag)\n const maxAutocorrelation = Math.max(...autocorrelation.subarray(1))\n\n // Compute the HNR\n return autocorrelation[0] !== 0\n ? 10 *\n Math.log10(\n maxAutocorrelation /\n (autocorrelation[0] - maxAutocorrelation)\n )\n : 0\n }\n\n const extractWaveform = (\n channelData, // Float32Array\n sampleRate, // number\n pointsPerSecond, // number\n algorithm // string\n ) => {\n const totalSamples = channelData.length\n const segmentDuration = totalSamples / sampleRate\n const totalPoints = Math.max(\n Math.ceil(segmentDuration * pointsPerSecond),\n 1\n )\n const pointInterval = Math.ceil(totalSamples / totalPoints)\n const dataPoints = []\n let minAmplitude = Infinity\n let maxAmplitude = -Infinity\n let silenceStart = null\n let lastSpeechEnd = -Infinity\n let isSpeech = false\n\n const expectedPoints = segmentDuration * pointsPerSecond\n const samplesPerPoint = Math.ceil(channelData.length / expectedPoints)\n\n for (let i = 0; i < expectedPoints; i++) {\n const start = i * samplesPerPoint\n const end = Math.min(start + samplesPerPoint, totalSamples)\n\n let sumSquares = 0\n let zeroCrossings = 0\n let prevValue = channelData[start]\n let localMinAmplitude = Infinity\n let localMaxAmplitude = -Infinity\n let hasNonZeroValue = false\n\n // compute values for the segment\n for (let j = start; j < end; j++) {\n const value = channelData[j]\n sumSquares += value * value\n if (j > start && value * prevValue < 0) {\n zeroCrossings++\n }\n prevValue = value\n\n // We need to keep absolute value otherwise we cannot visualize properly\n const absValue = Math.abs(value)\n localMinAmplitude = Math.min(localMinAmplitude, absValue)\n localMaxAmplitude = Math.max(localMaxAmplitude, absValue)\n\n if (value !== 0) {\n hasNonZeroValue = true\n }\n }\n\n // Post-processing checks\n if (!hasNonZeroValue) {\n // All values are zero\n localMinAmplitude = 0\n localMaxAmplitude = 0\n }\n\n const rms = Math.sqrt(sumSquares / (end - start))\n minAmplitude = Math.min(minAmplitude, localMinAmplitude)\n maxAmplitude = Math.max(maxAmplitude, localMaxAmplitude)\n\n const energy = sumSquares\n const zcr = zeroCrossings / (end - start)\n\n const silent = rms < SILENCE_THRESHOLD\n const dB = 20 * Math.log10(rms)\n\n if (silent) {\n if (silenceStart === null) {\n silenceStart = start\n } else if (start - silenceStart > MIN_SILENCE_DURATION) {\n // Silence detected for longer than the threshold, set amplitude to 0\n localMaxAmplitude = 0\n localMinAmplitude = 0\n isSpeech = false\n }\n } else {\n silenceStart = null\n if (\n !isSpeech &&\n start - lastSpeechEnd < SPEECH_INERTIA_DURATION\n ) {\n isSpeech = true\n }\n lastSpeechEnd = end\n }\n\n const activeSpeech =\n (rms > RMS_THRESHOLD && zcr > ZCR_THRESHOLD) ||\n (isSpeech && start - lastSpeechEnd < SPEECH_INERTIA_DURATION)\n\n if (activeSpeech) {\n isSpeech = true\n lastSpeechEnd = end\n } else {\n isSpeech = false\n }\n\n const bytesPerSample = bitDepth / 8\n const startPosition = start * bytesPerSample * numberOfChannels // Calculate start position in bytes\n const endPosition = end * bytesPerSample * numberOfChannels // Calculate end position in bytes\n\n // Compute features\n const segmentData = channelData.slice(start, end)\n const mfcc = features.mfcc\n ? extractMFCC(segmentData, sampleRate)\n : []\n const spectralCentroid = features.spectralCentroid\n ? extractSpectralCentroid(segmentData, sampleRate)\n : 0\n const spectralFlatness = features.spectralFlatness\n ? extractSpectralFlatness(segmentData)\n : 0\n const spectralRollOff = features.spectralRollOff\n ? extractSpectralRollOff(segmentData, sampleRate)\n : 0\n const spectralBandwidth = features.spectralBandwidth\n ? extractSpectralBandwidth(segmentData, sampleRate)\n : 0\n const chromagram = features.chromagram\n ? extractChromagram(segmentData, sampleRate)\n : []\n const hnr = features.hnr ? extractHNR(segmentData) : 0\n\n const peakAmp = Math.max(Math.abs(localMaxAmplitude), Math.abs(localMinAmplitude))\n const newData = {\n id: uniqueIdCounter++, // Assign unique ID and increment the counter\n amplitude: algorithm === 'peak' ? peakAmp : rms,\n activeSpeech,\n dB,\n silent,\n features: {\n energy,\n rms,\n minAmplitude: localMinAmplitude,\n maxAmplitude: localMaxAmplitude,\n zcr,\n mfcc: [], // Placeholder for MFCC features\n spectralCentroid, // Computed spectral centroid\n spectralFlatness, // Computed spectral flatness\n spectralRollOff, // Computed spectral roll-off\n spectralBandwidth, // Computed spectral bandwidth\n chromagram, // Computed chromagram\n hnr, // Computed HNR\n },\n startTime: start / sampleRate,\n endTime: end / sampleRate,\n startPosition,\n endPosition,\n samples: end - start,\n speaker: 0, // Assuming speaker detection is to be handled later\n }\n\n dataPoints.push(newData)\n }\n\n return {\n pointsPerSecond,\n amplitudeAlgorithm: algorithm,\n durationMs: fullAudioDurationMs,\n bitDepth,\n samples: totalSamples,\n numberOfChannels,\n sampleRate,\n dataPoints,\n amplitudeRange: {\n min: minAmplitude,\n max: maxAmplitude,\n },\n speakerChanges: [], // Placeholder for future speaker detection logic\n }\n }\n\n try {\n const result = extractWaveform(\n channelData,\n sampleRate,\n pointsPerSecond,\n algorithm\n )\n self.postMessage({\n command: 'features',\n result,\n })\n } catch (error) {\n console.error('[AudioFeaturesExtractor] Error in processing', error)\n self.postMessage({ error: error.message })\n } finally {\n // Do not close the worker so it can be re-used for subsequent messages\n // self.close();\n }\n}\n";
1
+ export declare const InlineFeaturesExtractor = "\n// Unique ID counter\nlet uniqueIdCounter = 0\nlet accumulatedDataPoints = [] // Move outside message handler\nlet lastEmitTime = Date.now() // Move outside message handler\n\nself.onmessage = function (event) {\n const {\n channelData, // this is only the newly recorded data when live recording.\n sampleRate,\n pointsPerSecond,\n algorithm,\n bitDepth,\n fullAudioDurationMs,\n numberOfChannels,\n features: _features,\n intervalAnalysis = 500, // Use intervalAnalysis instead of interval\n } = event.data\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 const extractHNR = (segmentData) => {\n const frameSize = segmentData.length\n const autocorrelation = new Float32Array(frameSize)\n\n // Compute the autocorrelation of the segment data\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 (excluding the zero lag)\n const maxAutocorrelation = Math.max(...autocorrelation.subarray(1))\n\n // Compute the HNR\n return autocorrelation[0] !== 0\n ? 10 *\n Math.log10(\n maxAutocorrelation /\n (autocorrelation[0] - maxAutocorrelation)\n )\n : 0\n }\n\n const extractWaveform = (\n channelData, // Float32Array\n sampleRate, // number\n pointsPerSecond, // number\n algorithm // string\n ) => {\n const totalSamples = channelData.length\n const segmentDuration = totalSamples / sampleRate\n const totalPoints = Math.max(\n Math.ceil(segmentDuration * pointsPerSecond),\n 1\n )\n const pointInterval = Math.ceil(totalSamples / totalPoints)\n const dataPoints = []\n let minAmplitude = Infinity\n let maxAmplitude = -Infinity\n let silenceStart = null\n let lastSpeechEnd = -Infinity\n let isSpeech = false\n\n const expectedPoints = segmentDuration * pointsPerSecond\n const samplesPerPoint = Math.ceil(channelData.length / expectedPoints)\n\n for (let i = 0; i < expectedPoints; i++) {\n const start = i * samplesPerPoint\n const end = Math.min(start + samplesPerPoint, totalSamples)\n\n let sumSquares = 0\n let zeroCrossings = 0\n let prevValue = channelData[start]\n let localMinAmplitude = Infinity\n let localMaxAmplitude = -Infinity\n let hasNonZeroValue = false\n\n // compute values for the segment\n for (let j = start; j < end; j++) {\n const value = channelData[j]\n sumSquares += value * value\n if (j > start && value * prevValue < 0) {\n zeroCrossings++\n }\n prevValue = value\n\n // We need to keep absolute value otherwise we cannot visualize properly\n const absValue = Math.abs(value)\n localMinAmplitude = Math.min(localMinAmplitude, absValue)\n localMaxAmplitude = Math.max(localMaxAmplitude, absValue)\n\n if (value !== 0) {\n hasNonZeroValue = true\n }\n }\n\n // Post-processing checks\n if (!hasNonZeroValue) {\n // All values are zero\n localMinAmplitude = 0\n localMaxAmplitude = 0\n }\n\n const rms = Math.sqrt(sumSquares / (end - start))\n minAmplitude = Math.min(minAmplitude, localMinAmplitude)\n maxAmplitude = Math.max(maxAmplitude, localMaxAmplitude)\n\n const energy = sumSquares\n const zcr = zeroCrossings / (end - start)\n\n const silent = rms < SILENCE_THRESHOLD\n const dB = 20 * Math.log10(rms)\n\n if (silent) {\n if (silenceStart === null) {\n silenceStart = start\n } else if (start - silenceStart > MIN_SILENCE_DURATION) {\n // Silence detected for longer than the threshold, set amplitude to 0\n localMaxAmplitude = 0\n localMinAmplitude = 0\n isSpeech = false\n }\n } else {\n silenceStart = null\n if (\n !isSpeech &&\n start - lastSpeechEnd < SPEECH_INERTIA_DURATION\n ) {\n isSpeech = true\n }\n lastSpeechEnd = end\n }\n\n const activeSpeech =\n (rms > RMS_THRESHOLD && zcr > ZCR_THRESHOLD) ||\n (isSpeech && start - lastSpeechEnd < SPEECH_INERTIA_DURATION)\n\n if (activeSpeech) {\n isSpeech = true\n lastSpeechEnd = end\n } else {\n isSpeech = false\n }\n\n const bytesPerSample = bitDepth / 8\n const startPosition = start * bytesPerSample * numberOfChannels // Calculate start position in bytes\n const endPosition = end * bytesPerSample * numberOfChannels // Calculate end position in bytes\n\n // Compute features\n const segmentData = channelData.slice(start, end)\n const mfcc = features.mfcc\n ? extractMFCC(segmentData, sampleRate)\n : []\n const spectralCentroid = features.spectralCentroid\n ? extractSpectralCentroid(segmentData, sampleRate)\n : 0\n const spectralFlatness = features.spectralFlatness\n ? extractSpectralFlatness(segmentData)\n : 0\n const spectralRollOff = features.spectralRollOff\n ? extractSpectralRollOff(segmentData, sampleRate)\n : 0\n const spectralBandwidth = features.spectralBandwidth\n ? extractSpectralBandwidth(segmentData, sampleRate)\n : 0\n const chromagram = features.chromagram\n ? extractChromagram(segmentData, sampleRate)\n : []\n const hnr = features.hnr ? extractHNR(segmentData) : 0\n\n const peakAmp = Math.max(Math.abs(localMaxAmplitude), Math.abs(localMinAmplitude))\n const newData = {\n id: uniqueIdCounter++, // Assign unique ID and increment the counter\n amplitude: algorithm === 'peak' ? peakAmp : rms,\n activeSpeech,\n dB,\n silent,\n features: {\n energy,\n rms,\n minAmplitude: localMinAmplitude,\n maxAmplitude: localMaxAmplitude,\n zcr,\n mfcc: [], // Placeholder for MFCC features\n spectralCentroid, // Computed spectral centroid\n spectralFlatness, // Computed spectral flatness\n spectralRollOff, // Computed spectral roll-off\n spectralBandwidth, // Computed spectral bandwidth\n chromagram, // Computed chromagram\n hnr, // Computed HNR\n },\n startTime: start / sampleRate,\n endTime: end / sampleRate,\n startPosition,\n endPosition,\n samples: end - start,\n speaker: 0, // Assuming speaker detection is to be handled later\n }\n\n dataPoints.push(newData)\n }\n\n return {\n pointsPerSecond,\n amplitudeAlgorithm: algorithm,\n durationMs: fullAudioDurationMs,\n bitDepth,\n samples: totalSamples,\n numberOfChannels,\n sampleRate,\n dataPoints,\n amplitudeRange: {\n min: minAmplitude,\n max: maxAmplitude,\n },\n speakerChanges: [], // Placeholder for future speaker detection logic\n }\n }\n\n try {\n const result = extractWaveform(\n channelData,\n sampleRate,\n pointsPerSecond,\n algorithm\n )\n\n // Accumulate data points\n accumulatedDataPoints = accumulatedDataPoints.concat(result.dataPoints)\n \n const currentTime = Date.now()\n const shouldEmitAccumulated = currentTime - lastEmitTime >= intervalAnalysis\n\n if (shouldEmitAccumulated) {\n self.postMessage({\n command: 'features',\n result: {\n ...result,\n dataPoints: accumulatedDataPoints\n }\n })\n accumulatedDataPoints = [] // Reset accumulator\n lastEmitTime = currentTime\n }\n } catch (error) {\n console.error('[AudioFeaturesExtractor] Error in processing', error)\n self.postMessage({ error: error.message })\n } finally {\n // Do not close the worker so it can be re-used for subsequent messages\n // self.close();\n }\n}\n";
2
2
  //# sourceMappingURL=InlineFeaturesExtractor.web.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"InlineFeaturesExtractor.web.d.ts","sourceRoot":"","sources":["../../src/workers/InlineFeaturesExtractor.web.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,ytWAqTnC,CAAA"}
1
+ {"version":3,"file":"InlineFeaturesExtractor.web.d.ts","sourceRoot":"","sources":["../../src/workers/InlineFeaturesExtractor.web.tsx"],"names":[],"mappings":"AAAA,eAAO,MAAM,uBAAuB,u7XAsUnC,CAAA"}
@@ -1,6 +1,8 @@
1
1
  export const InlineFeaturesExtractor = `
2
2
  // Unique ID counter
3
3
  let uniqueIdCounter = 0
4
+ let accumulatedDataPoints = [] // Move outside message handler
5
+ let lastEmitTime = Date.now() // Move outside message handler
4
6
 
5
7
  self.onmessage = function (event) {
6
8
  const {
@@ -12,6 +14,7 @@ self.onmessage = function (event) {
12
14
  fullAudioDurationMs,
13
15
  numberOfChannels,
14
16
  features: _features,
17
+ intervalAnalysis = 500, // Use intervalAnalysis instead of interval
15
18
  } = event.data
16
19
  const features = _features || {}
17
20
 
@@ -295,10 +298,24 @@ self.onmessage = function (event) {
295
298
  pointsPerSecond,
296
299
  algorithm
297
300
  )
298
- self.postMessage({
299
- command: 'features',
300
- result,
301
- })
301
+
302
+ // Accumulate data points
303
+ accumulatedDataPoints = accumulatedDataPoints.concat(result.dataPoints)
304
+
305
+ const currentTime = Date.now()
306
+ const shouldEmitAccumulated = currentTime - lastEmitTime >= intervalAnalysis
307
+
308
+ if (shouldEmitAccumulated) {
309
+ self.postMessage({
310
+ command: 'features',
311
+ result: {
312
+ ...result,
313
+ dataPoints: accumulatedDataPoints
314
+ }
315
+ })
316
+ accumulatedDataPoints = [] // Reset accumulator
317
+ lastEmitTime = currentTime
318
+ }
302
319
  } catch (error) {
303
320
  console.error('[AudioFeaturesExtractor] Error in processing', error)
304
321
  self.postMessage({ error: error.message })
@@ -1 +1 @@
1
- {"version":3,"file":"InlineFeaturesExtractor.web.js","sourceRoot":"","sources":["../../src/workers/InlineFeaturesExtractor.web.tsx"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAqTtC,CAAA","sourcesContent":["export const InlineFeaturesExtractor = `\n// Unique ID counter\nlet uniqueIdCounter = 0\n\nself.onmessage = function (event) {\n const {\n channelData, // this is only the newly recorded data when live recording.\n sampleRate,\n pointsPerSecond,\n algorithm,\n bitDepth,\n fullAudioDurationMs,\n numberOfChannels,\n features: _features,\n } = event.data\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 const extractHNR = (segmentData) => {\n const frameSize = segmentData.length\n const autocorrelation = new Float32Array(frameSize)\n\n // Compute the autocorrelation of the segment data\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 (excluding the zero lag)\n const maxAutocorrelation = Math.max(...autocorrelation.subarray(1))\n\n // Compute the HNR\n return autocorrelation[0] !== 0\n ? 10 *\n Math.log10(\n maxAutocorrelation /\n (autocorrelation[0] - maxAutocorrelation)\n )\n : 0\n }\n\n const extractWaveform = (\n channelData, // Float32Array\n sampleRate, // number\n pointsPerSecond, // number\n algorithm // string\n ) => {\n const totalSamples = channelData.length\n const segmentDuration = totalSamples / sampleRate\n const totalPoints = Math.max(\n Math.ceil(segmentDuration * pointsPerSecond),\n 1\n )\n const pointInterval = Math.ceil(totalSamples / totalPoints)\n const dataPoints = []\n let minAmplitude = Infinity\n let maxAmplitude = -Infinity\n let silenceStart = null\n let lastSpeechEnd = -Infinity\n let isSpeech = false\n\n const expectedPoints = segmentDuration * pointsPerSecond\n const samplesPerPoint = Math.ceil(channelData.length / expectedPoints)\n\n for (let i = 0; i < expectedPoints; i++) {\n const start = i * samplesPerPoint\n const end = Math.min(start + samplesPerPoint, totalSamples)\n\n let sumSquares = 0\n let zeroCrossings = 0\n let prevValue = channelData[start]\n let localMinAmplitude = Infinity\n let localMaxAmplitude = -Infinity\n let hasNonZeroValue = false\n\n // compute values for the segment\n for (let j = start; j < end; j++) {\n const value = channelData[j]\n sumSquares += value * value\n if (j > start && value * prevValue < 0) {\n zeroCrossings++\n }\n prevValue = value\n\n // We need to keep absolute value otherwise we cannot visualize properly\n const absValue = Math.abs(value)\n localMinAmplitude = Math.min(localMinAmplitude, absValue)\n localMaxAmplitude = Math.max(localMaxAmplitude, absValue)\n\n if (value !== 0) {\n hasNonZeroValue = true\n }\n }\n\n // Post-processing checks\n if (!hasNonZeroValue) {\n // All values are zero\n localMinAmplitude = 0\n localMaxAmplitude = 0\n }\n\n const rms = Math.sqrt(sumSquares / (end - start))\n minAmplitude = Math.min(minAmplitude, localMinAmplitude)\n maxAmplitude = Math.max(maxAmplitude, localMaxAmplitude)\n\n const energy = sumSquares\n const zcr = zeroCrossings / (end - start)\n\n const silent = rms < SILENCE_THRESHOLD\n const dB = 20 * Math.log10(rms)\n\n if (silent) {\n if (silenceStart === null) {\n silenceStart = start\n } else if (start - silenceStart > MIN_SILENCE_DURATION) {\n // Silence detected for longer than the threshold, set amplitude to 0\n localMaxAmplitude = 0\n localMinAmplitude = 0\n isSpeech = false\n }\n } else {\n silenceStart = null\n if (\n !isSpeech &&\n start - lastSpeechEnd < SPEECH_INERTIA_DURATION\n ) {\n isSpeech = true\n }\n lastSpeechEnd = end\n }\n\n const activeSpeech =\n (rms > RMS_THRESHOLD && zcr > ZCR_THRESHOLD) ||\n (isSpeech && start - lastSpeechEnd < SPEECH_INERTIA_DURATION)\n\n if (activeSpeech) {\n isSpeech = true\n lastSpeechEnd = end\n } else {\n isSpeech = false\n }\n\n const bytesPerSample = bitDepth / 8\n const startPosition = start * bytesPerSample * numberOfChannels // Calculate start position in bytes\n const endPosition = end * bytesPerSample * numberOfChannels // Calculate end position in bytes\n\n // Compute features\n const segmentData = channelData.slice(start, end)\n const mfcc = features.mfcc\n ? extractMFCC(segmentData, sampleRate)\n : []\n const spectralCentroid = features.spectralCentroid\n ? extractSpectralCentroid(segmentData, sampleRate)\n : 0\n const spectralFlatness = features.spectralFlatness\n ? extractSpectralFlatness(segmentData)\n : 0\n const spectralRollOff = features.spectralRollOff\n ? extractSpectralRollOff(segmentData, sampleRate)\n : 0\n const spectralBandwidth = features.spectralBandwidth\n ? extractSpectralBandwidth(segmentData, sampleRate)\n : 0\n const chromagram = features.chromagram\n ? extractChromagram(segmentData, sampleRate)\n : []\n const hnr = features.hnr ? extractHNR(segmentData) : 0\n\n const peakAmp = Math.max(Math.abs(localMaxAmplitude), Math.abs(localMinAmplitude))\n const newData = {\n id: uniqueIdCounter++, // Assign unique ID and increment the counter\n amplitude: algorithm === 'peak' ? peakAmp : rms,\n activeSpeech,\n dB,\n silent,\n features: {\n energy,\n rms,\n minAmplitude: localMinAmplitude,\n maxAmplitude: localMaxAmplitude,\n zcr,\n mfcc: [], // Placeholder for MFCC features\n spectralCentroid, // Computed spectral centroid\n spectralFlatness, // Computed spectral flatness\n spectralRollOff, // Computed spectral roll-off\n spectralBandwidth, // Computed spectral bandwidth\n chromagram, // Computed chromagram\n hnr, // Computed HNR\n },\n startTime: start / sampleRate,\n endTime: end / sampleRate,\n startPosition,\n endPosition,\n samples: end - start,\n speaker: 0, // Assuming speaker detection is to be handled later\n }\n\n dataPoints.push(newData)\n }\n\n return {\n pointsPerSecond,\n amplitudeAlgorithm: algorithm,\n durationMs: fullAudioDurationMs,\n bitDepth,\n samples: totalSamples,\n numberOfChannels,\n sampleRate,\n dataPoints,\n amplitudeRange: {\n min: minAmplitude,\n max: maxAmplitude,\n },\n speakerChanges: [], // Placeholder for future speaker detection logic\n }\n }\n\n try {\n const result = extractWaveform(\n channelData,\n sampleRate,\n pointsPerSecond,\n algorithm\n )\n self.postMessage({\n command: 'features',\n result,\n })\n } catch (error) {\n console.error('[AudioFeaturesExtractor] Error in processing', error)\n self.postMessage({ error: error.message })\n } finally {\n // Do not close the worker so it can be re-used for subsequent messages\n // self.close();\n }\n}\n`\n"]}
1
+ {"version":3,"file":"InlineFeaturesExtractor.web.js","sourceRoot":"","sources":["../../src/workers/InlineFeaturesExtractor.web.tsx"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,uBAAuB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAsUtC,CAAA","sourcesContent":["export const InlineFeaturesExtractor = `\n// Unique ID counter\nlet uniqueIdCounter = 0\nlet accumulatedDataPoints = [] // Move outside message handler\nlet lastEmitTime = Date.now() // Move outside message handler\n\nself.onmessage = function (event) {\n const {\n channelData, // this is only the newly recorded data when live recording.\n sampleRate,\n pointsPerSecond,\n algorithm,\n bitDepth,\n fullAudioDurationMs,\n numberOfChannels,\n features: _features,\n intervalAnalysis = 500, // Use intervalAnalysis instead of interval\n } = event.data\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 const extractHNR = (segmentData) => {\n const frameSize = segmentData.length\n const autocorrelation = new Float32Array(frameSize)\n\n // Compute the autocorrelation of the segment data\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 (excluding the zero lag)\n const maxAutocorrelation = Math.max(...autocorrelation.subarray(1))\n\n // Compute the HNR\n return autocorrelation[0] !== 0\n ? 10 *\n Math.log10(\n maxAutocorrelation /\n (autocorrelation[0] - maxAutocorrelation)\n )\n : 0\n }\n\n const extractWaveform = (\n channelData, // Float32Array\n sampleRate, // number\n pointsPerSecond, // number\n algorithm // string\n ) => {\n const totalSamples = channelData.length\n const segmentDuration = totalSamples / sampleRate\n const totalPoints = Math.max(\n Math.ceil(segmentDuration * pointsPerSecond),\n 1\n )\n const pointInterval = Math.ceil(totalSamples / totalPoints)\n const dataPoints = []\n let minAmplitude = Infinity\n let maxAmplitude = -Infinity\n let silenceStart = null\n let lastSpeechEnd = -Infinity\n let isSpeech = false\n\n const expectedPoints = segmentDuration * pointsPerSecond\n const samplesPerPoint = Math.ceil(channelData.length / expectedPoints)\n\n for (let i = 0; i < expectedPoints; i++) {\n const start = i * samplesPerPoint\n const end = Math.min(start + samplesPerPoint, totalSamples)\n\n let sumSquares = 0\n let zeroCrossings = 0\n let prevValue = channelData[start]\n let localMinAmplitude = Infinity\n let localMaxAmplitude = -Infinity\n let hasNonZeroValue = false\n\n // compute values for the segment\n for (let j = start; j < end; j++) {\n const value = channelData[j]\n sumSquares += value * value\n if (j > start && value * prevValue < 0) {\n zeroCrossings++\n }\n prevValue = value\n\n // We need to keep absolute value otherwise we cannot visualize properly\n const absValue = Math.abs(value)\n localMinAmplitude = Math.min(localMinAmplitude, absValue)\n localMaxAmplitude = Math.max(localMaxAmplitude, absValue)\n\n if (value !== 0) {\n hasNonZeroValue = true\n }\n }\n\n // Post-processing checks\n if (!hasNonZeroValue) {\n // All values are zero\n localMinAmplitude = 0\n localMaxAmplitude = 0\n }\n\n const rms = Math.sqrt(sumSquares / (end - start))\n minAmplitude = Math.min(minAmplitude, localMinAmplitude)\n maxAmplitude = Math.max(maxAmplitude, localMaxAmplitude)\n\n const energy = sumSquares\n const zcr = zeroCrossings / (end - start)\n\n const silent = rms < SILENCE_THRESHOLD\n const dB = 20 * Math.log10(rms)\n\n if (silent) {\n if (silenceStart === null) {\n silenceStart = start\n } else if (start - silenceStart > MIN_SILENCE_DURATION) {\n // Silence detected for longer than the threshold, set amplitude to 0\n localMaxAmplitude = 0\n localMinAmplitude = 0\n isSpeech = false\n }\n } else {\n silenceStart = null\n if (\n !isSpeech &&\n start - lastSpeechEnd < SPEECH_INERTIA_DURATION\n ) {\n isSpeech = true\n }\n lastSpeechEnd = end\n }\n\n const activeSpeech =\n (rms > RMS_THRESHOLD && zcr > ZCR_THRESHOLD) ||\n (isSpeech && start - lastSpeechEnd < SPEECH_INERTIA_DURATION)\n\n if (activeSpeech) {\n isSpeech = true\n lastSpeechEnd = end\n } else {\n isSpeech = false\n }\n\n const bytesPerSample = bitDepth / 8\n const startPosition = start * bytesPerSample * numberOfChannels // Calculate start position in bytes\n const endPosition = end * bytesPerSample * numberOfChannels // Calculate end position in bytes\n\n // Compute features\n const segmentData = channelData.slice(start, end)\n const mfcc = features.mfcc\n ? extractMFCC(segmentData, sampleRate)\n : []\n const spectralCentroid = features.spectralCentroid\n ? extractSpectralCentroid(segmentData, sampleRate)\n : 0\n const spectralFlatness = features.spectralFlatness\n ? extractSpectralFlatness(segmentData)\n : 0\n const spectralRollOff = features.spectralRollOff\n ? extractSpectralRollOff(segmentData, sampleRate)\n : 0\n const spectralBandwidth = features.spectralBandwidth\n ? extractSpectralBandwidth(segmentData, sampleRate)\n : 0\n const chromagram = features.chromagram\n ? extractChromagram(segmentData, sampleRate)\n : []\n const hnr = features.hnr ? extractHNR(segmentData) : 0\n\n const peakAmp = Math.max(Math.abs(localMaxAmplitude), Math.abs(localMinAmplitude))\n const newData = {\n id: uniqueIdCounter++, // Assign unique ID and increment the counter\n amplitude: algorithm === 'peak' ? peakAmp : rms,\n activeSpeech,\n dB,\n silent,\n features: {\n energy,\n rms,\n minAmplitude: localMinAmplitude,\n maxAmplitude: localMaxAmplitude,\n zcr,\n mfcc: [], // Placeholder for MFCC features\n spectralCentroid, // Computed spectral centroid\n spectralFlatness, // Computed spectral flatness\n spectralRollOff, // Computed spectral roll-off\n spectralBandwidth, // Computed spectral bandwidth\n chromagram, // Computed chromagram\n hnr, // Computed HNR\n },\n startTime: start / sampleRate,\n endTime: end / sampleRate,\n startPosition,\n endPosition,\n samples: end - start,\n speaker: 0, // Assuming speaker detection is to be handled later\n }\n\n dataPoints.push(newData)\n }\n\n return {\n pointsPerSecond,\n amplitudeAlgorithm: algorithm,\n durationMs: fullAudioDurationMs,\n bitDepth,\n samples: totalSamples,\n numberOfChannels,\n sampleRate,\n dataPoints,\n amplitudeRange: {\n min: minAmplitude,\n max: maxAmplitude,\n },\n speakerChanges: [], // Placeholder for future speaker detection logic\n }\n }\n\n try {\n const result = extractWaveform(\n channelData,\n sampleRate,\n pointsPerSecond,\n algorithm\n )\n\n // Accumulate data points\n accumulatedDataPoints = accumulatedDataPoints.concat(result.dataPoints)\n \n const currentTime = Date.now()\n const shouldEmitAccumulated = currentTime - lastEmitTime >= intervalAnalysis\n\n if (shouldEmitAccumulated) {\n self.postMessage({\n command: 'features',\n result: {\n ...result,\n dataPoints: accumulatedDataPoints\n }\n })\n accumulatedDataPoints = [] // Reset accumulator\n lastEmitTime = currentTime\n }\n } catch (error) {\n console.error('[AudioFeaturesExtractor] Error in processing', error)\n self.postMessage({ error: error.message })\n } finally {\n // Do not close the worker so it can be re-used for subsequent messages\n // self.close();\n }\n}\n`\n"]}
@@ -44,17 +44,29 @@ class AudioStreamManager: NSObject {
44
44
  private var wasIdleTimerDisabled: Bool = false // Track previous idle timer state
45
45
  private var isWakeLockEnabled: Bool = false // Track current wake lock state
46
46
 
47
+
48
+ // Data emission for onAudioStream
47
49
  internal var lastEmissionTime: Date?
48
50
  internal var lastEmittedSize: Int64 = 0
49
51
  internal var lastEmittedCompressedSize: Int64 = 0
50
- private var emissionInterval: TimeInterval = 1.0 // Default to 1 second
51
52
  private var totalDataSize: Int64 = 0
53
+ private var lastBufferTime: AVAudioTime?
54
+ private var accumulatedData = Data()
55
+
56
+ // Data emission for onAudioAnalysis
57
+ internal var lastEmissionTimeAnalysis: Date?
58
+ internal var lastEmittedSizeAnalysis: Int64 = 0
59
+ internal var lastEmittedCompressedSizeAnalysis: Int64 = 0
60
+ private var totalDataSizeAnalysis: Int64 = 0
61
+ private var lastBufferTimeAnalysis: AVAudioTime?
62
+ private var accumulatedAnalysisData = Data()
63
+
64
+
65
+
52
66
  private var fileManager = FileManager.default
53
67
  internal var recordingSettings: RecordingSettings?
54
68
  internal var recordingUUID: UUID?
55
69
  internal var mimeType: String = "audio/wav"
56
- private var lastBufferTime: AVAudioTime?
57
- private var accumulatedData = Data()
58
70
  private var recentData = [Float]() // This property stores the recent audio data
59
71
  private var notificationUpdateTimer: Timer?
60
72
 
@@ -77,6 +89,10 @@ class AudioStreamManager: NSObject {
77
89
  // Add property to track auto-resume preference
78
90
  private var autoResumeAfterInterruption: Bool = false
79
91
 
92
+ // Add these properties
93
+ private var emissionInterval: TimeInterval = 1.0 // Default 1 second
94
+ private var emissionIntervalAnalysis: TimeInterval = 0.5 // Default 0.5 seconds
95
+
80
96
  /// Initializes the AudioStreamManager
81
97
  override init() {
82
98
  super.init()
@@ -537,7 +553,8 @@ class AudioStreamManager: NSObject {
537
553
  "isPaused": isPaused,
538
554
  "mimeType": mimeType,
539
555
  "size": totalDataSize,
540
- "interval": emissionInterval
556
+ "interval": settings.interval,
557
+ "intervalAnalysis": settings.intervalAnalysis
541
558
  ]
542
559
 
543
560
  // Add compression info if enabled
@@ -575,12 +592,11 @@ class AudioStreamManager: NSObject {
575
592
  audioSession.currentRoute.outputs.contains { $0.portType == .builtInReceiver }
576
593
  }
577
594
 
578
- /// Starts a new audio recording with the specified settings and interval.
595
+ /// Starts a new audio recording with the specified settings.
579
596
  /// - Parameters:
580
597
  /// - settings: The recording settings to use.
581
- /// - intervalMilliseconds: The interval in milliseconds for emitting audio data.
582
598
  /// - Returns: A StartRecordingResult object if recording starts successfully, or nil otherwise.
583
- func startRecording(settings: RecordingSettings, intervalMilliseconds: Int) -> StartRecordingResult? {
599
+ func startRecording(settings: RecordingSettings) -> StartRecordingResult? {
584
600
  // Check for active call using the new method
585
601
  if isPhoneCallActive() {
586
602
  Logger.debug("Cannot start recording during an active phone call")
@@ -619,14 +635,19 @@ class AudioStreamManager: NSObject {
619
635
  let session = AVAudioSession.sharedInstance()
620
636
  var newSettings = settings
621
637
 
622
- // Add these initializations back
623
- emissionInterval = max(100.0, Double(intervalMilliseconds)) / 1000.0
638
+ emissionInterval = max(100.0, Double(settings.interval ?? 1000)) / 1000.0
639
+ emissionIntervalAnalysis = max(100.0, Double(settings.intervalAnalysis ?? 500)) / 1000.0
624
640
  lastEmissionTime = Date()
641
+ lastEmissionTimeAnalysis = Date()
625
642
  accumulatedData.removeAll()
643
+ accumulatedAnalysisData.removeAll()
626
644
  totalDataSize = 0
645
+ totalDataSizeAnalysis = 0
627
646
  totalPausedDuration = 0
628
647
  lastEmittedSize = 0
648
+ lastEmittedCompressedSizeAnalysis = 0
629
649
  isPaused = false
650
+
630
651
 
631
652
  // Create recording file first
632
653
  recordingFileURL = createRecordingFile()
@@ -677,6 +698,12 @@ class AudioStreamManager: NSObject {
677
698
  - mode: \(mode)
678
699
  - options: \(options)
679
700
  - keepAwake: \(settings.keepAwake)
701
+ - emission interval: \(emissionInterval * 1000)ms
702
+ - analysis interval: \(emissionIntervalAnalysis * 1000)ms
703
+ - sample rate: \(settings.sampleRate)Hz
704
+ - channels: \(settings.numberOfChannels)
705
+ - bit depth: \(settings.bitDepth)-bit
706
+ - compression enabled: \(settings.enableCompressedOutput)
680
707
  """)
681
708
 
682
709
  try session.setPreferredSampleRate(settings.sampleRate)
@@ -860,7 +887,7 @@ class AudioStreamManager: NSObject {
860
887
  }
861
888
 
862
889
  /// Pauses the current audio recording.
863
- func pauseRecording() {
890
+ public func pauseRecording() {
864
891
  guard isRecording && !isPaused else { return }
865
892
 
866
893
  // Store the current duration when pausing
@@ -929,8 +956,8 @@ class AudioStreamManager: NSObject {
929
956
  }
930
957
 
931
958
  /// Resumes the current audio recording.
932
- func resumeRecording() {
933
- // Check for active call using the new method
959
+ public func resumeRecording() {
960
+ // Check for active phone call
934
961
  if isPhoneCallActive() {
935
962
  Logger.debug("Cannot resume recording during an active phone call")
936
963
  delegate?.audioStreamManager(self, didFailWithError: "Cannot resume recording during an active phone call")
@@ -987,7 +1014,7 @@ class AudioStreamManager: NSObject {
987
1014
  return formatDescription
988
1015
  }
989
1016
 
990
- private func describeCommonFormat(_ format: AVAudioCommonFormat) -> String {
1017
+ func describeCommonFormat(_ format: AVAudioCommonFormat) -> String {
991
1018
  switch format {
992
1019
  case .pcmFormatFloat32:
993
1020
  return "32-bit float"
@@ -1149,9 +1176,12 @@ class AudioStreamManager: NSObject {
1149
1176
  totalPausedDuration = 0
1150
1177
  currentPauseStart = nil
1151
1178
  lastEmissionTime = nil
1179
+ lastEmissionTimeAnalysis = nil
1152
1180
  lastEmittedSize = 0
1181
+ lastEmittedSizeAnalysis = 0
1153
1182
  lastEmittedCompressedSize = 0
1154
1183
  accumulatedData.removeAll()
1184
+ accumulatedAnalysisData.removeAll()
1155
1185
  recordingUUID = nil
1156
1186
 
1157
1187
  return result
@@ -1459,6 +1489,7 @@ class AudioStreamManager: NSObject {
1459
1489
  // Update total size and accumulated data
1460
1490
  totalDataSize += Int64(data.count)
1461
1491
  accumulatedData.append(data)
1492
+ accumulatedAnalysisData.append(data)
1462
1493
 
1463
1494
  // Handle notifications if enabled
1464
1495
  if recordingSettings?.showNotification == true {
@@ -1534,7 +1565,21 @@ class AudioStreamManager: NSObject {
1534
1565
  compressionInfo: compressionInfo
1535
1566
  )
1536
1567
 
1537
- // Process audio if enabled
1568
+ // Update state after emission
1569
+ self.lastEmissionTime = currentTime
1570
+ self.lastEmittedSize = totalDataSize
1571
+ accumulatedData.removeAll()
1572
+ }
1573
+
1574
+
1575
+ if let lastEmissionTimeAnalysis = lastEmissionTimeAnalysis,
1576
+ let startTime = startTime,
1577
+ currentTime.timeIntervalSince(lastEmissionTimeAnalysis) >= emissionIntervalAnalysis {
1578
+
1579
+ let recordingTime = currentTime.timeIntervalSince(startTime)
1580
+ let dataToProcess = accumulatedAnalysisData
1581
+
1582
+ // Process audio if enabled
1538
1583
  if settings.enableProcessing {
1539
1584
  DispatchQueue.global().async { [weak self] in
1540
1585
  guard let self = self else { return }
@@ -1555,14 +1600,14 @@ class AudioStreamManager: NSObject {
1555
1600
  self.delegate?.audioStreamManager(self, didReceiveProcessingResult: result)
1556
1601
  }
1557
1602
  }
1603
+
1604
+ // Update state after emission
1605
+ self.lastEmissionTimeAnalysis = currentTime
1606
+ self.lastEmittedSizeAnalysis = totalDataSizeAnalysis
1607
+ accumulatedAnalysisData.removeAll()
1558
1608
  }
1559
1609
  }
1560
1610
  }
1561
-
1562
- // Update state after emission
1563
- self.lastEmissionTime = currentTime
1564
- self.lastEmittedSize = totalDataSize
1565
- accumulatedData.removeAll()
1566
1611
  }
1567
1612
  }
1568
1613
 
@@ -124,6 +124,7 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
124
124
  /// - `channelConfig`: The number of channels (default is 1 for mono).
125
125
  /// - `audioFormat`: The bit depth for recording (default is 16 bits).
126
126
  /// - `interval`: The interval in milliseconds at which to emit recording data (default is 1000 ms).
127
+ /// - `intervalAnalysis`: The interval in milliseconds at which to emit analysis data (default is 500 ms).
127
128
  /// - `enableProcessing`: Boolean to enable/disable audio processing (default is false).
128
129
  /// - `pointsPerSecond`: The number of data points to extract per second of audio (default is 20).
129
130
  /// - `algorithm`: The algorithm to use for extraction (default is "rms").
@@ -156,7 +157,7 @@ public class ExpoAudioStreamModule: Module, AudioStreamManagerDelegate {
156
157
  }
157
158
  }
158
159
 
159
- if let result = self.streamManager.startRecording(settings: settings, intervalMilliseconds: settings.interval ?? 1000) {
160
+ if let result = self.streamManager.startRecording(settings: settings) {
160
161
  var resultDict: [String: Any] = [
161
162
  "fileUri": result.fileUri,
162
163
  "channels": result.channels,
@@ -81,6 +81,7 @@ struct RecordingSettings {
81
81
  var numberOfChannels: Int = 1
82
82
  var bitDepth: Int = 16
83
83
  var interval: Int?
84
+ var intervalAnalysis: Int?
84
85
 
85
86
  // Feature flags
86
87
  var keepAwake: Bool = true
@@ -140,6 +141,7 @@ struct RecordingSettings {
140
141
  settings.numberOfChannels = dict["channels"] as? Int ?? 1
141
142
  settings.bitDepth = dict["bitDepth"] as? Int ?? 16
142
143
  settings.interval = dict["interval"] as? Int
144
+ settings.intervalAnalysis = dict["intervalAnalysis"] as? Int
143
145
 
144
146
  // Parse feature flags
145
147
  settings.keepAwake = dict["keepAwake"] as? Bool ?? true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@siteed/expo-audio-stream",
3
- "version": "1.16.0",
3
+ "version": "1.17.0",
4
4
  "description": "stream audio crossplatform",
5
5
  "license": "MIT",
6
6
  "main": "build/index.js",
@@ -13,6 +13,8 @@ interface AudioStreamPluginOptions {
13
13
  iosConfig?: {
14
14
  allowBackgroundAudioControls?: boolean;
15
15
  backgroundProcessingTitle?: string;
16
+ microphoneUsageDescription?: string;
17
+ notificationUsageDescription?: string;
16
18
  };
17
19
  }
18
20
  declare const withRecordingPermission: ConfigPlugin<AudioStreamPluginOptions>;