UNPKG

@lvyanxiang/react-native-audio-waveform

Version:

A React Native library for extracting audio waveform data from audio and video files. Supports iOS and Android platforms with streaming processing for large files.

260 lines (240 loc) 7.53 kB
/** * Calculate CRC32 checksum for data integrity verification */ function calculateCRC32(data) { // Simple CRC32 implementation const crc32Table = new Uint32Array(256); for (let i = 0; i < 256; i++) { let crc = i; for (let j = 0; j < 8; j++) { if (crc & 1) { crc = crc >>> 1 ^ 0xedb88320; } else { crc = crc >>> 1; } } crc32Table[i] = crc; } let crc = 0xffffffff; const byteArray = new Uint8Array(data.buffer); for (let i = 0; i < byteArray.length; i++) { crc = crc >>> 8 ^ crc32Table[(crc ^ byteArray[i]) & 0xff]; } return (crc ^ 0xffffffff) >>> 0; } /** * Extract basic audio features from a segment of audio data */ export function extractAudioFeatures(audioData, sampleRate, options) { const features = {}; const n = audioData.length; if (!options || Object.keys(options).length === 0) { return features; } // Basic amplitude statistics let sum = 0; let sumSquares = 0; let min = Infinity; let max = -Infinity; let zeroCrossings = 0; for (let i = 0; i < n; i++) { const sample = audioData[i]; sum += Math.abs(sample); sumSquares += sample * sample; min = Math.min(min, sample); max = Math.max(max, sample); // Zero crossing rate if (i > 0 && Math.sign(audioData[i]) !== Math.sign(audioData[i - 1])) { zeroCrossings++; } } // Energy if (options.energy) { features.energy = sumSquares; } // RMS if (options.rms) { features.rms = Math.sqrt(sumSquares / n); } // Min/Max amplitude if (options.minAmplitude || options.maxAmplitude) { if (options.minAmplitude) features.minAmplitude = min; if (options.maxAmplitude) features.maxAmplitude = max; } // Zero crossing rate if (options.zcr) { features.zcr = zeroCrossings / (n - 1); } // Spectral features (simplified implementations) if (options.spectralCentroid || options.spectralFlatness || options.spectralRolloff) { const fft = performSimpleFFT(audioData); const magnitude = fft.map(complex => Math.sqrt(complex.real * complex.real + complex.imag * complex.imag)); if (options.spectralCentroid) { features.spectralCentroid = calculateSpectralCentroid(magnitude, sampleRate); } if (options.spectralFlatness) { features.spectralFlatness = calculateSpectralFlatness(magnitude); } if (options.spectralRolloff) { features.spectralRolloff = calculateSpectralRolloff(magnitude, sampleRate); } } // CRC32 checksum if (options.crc32) { features.crc32 = calculateCRC32(audioData); } return features; } /** * Simple FFT implementation for spectral analysis */ function performSimpleFFT(audioData) { const n = audioData.length; const result = []; // Simple DFT implementation (not optimized) for (let k = 0; k < n / 2; k++) { let real = 0; let imag = 0; for (let j = 0; j < n; j++) { const angle = -2 * Math.PI * k * j / n; real += audioData[j] * Math.cos(angle); imag += audioData[j] * Math.sin(angle); } result.push({ real, imag }); } return result; } /** * Calculate spectral centroid */ function calculateSpectralCentroid(magnitude, sampleRate) { let weightedSum = 0; let totalMagnitude = 0; for (let i = 0; i < magnitude.length; i++) { const frequency = i * sampleRate / (2 * magnitude.length); weightedSum += frequency * magnitude[i]; totalMagnitude += magnitude[i]; } return totalMagnitude > 0 ? weightedSum / totalMagnitude : 0; } /** * Calculate spectral flatness */ function calculateSpectralFlatness(magnitude) { let geometricMean = 0; let arithmeticMean = 0; let count = 0; for (let i = 1; i < magnitude.length; i++) { // Skip DC component if (magnitude[i] > 0) { geometricMean += Math.log(magnitude[i]); arithmeticMean += magnitude[i]; count++; } } if (count === 0) return 0; geometricMean = Math.exp(geometricMean / count); arithmeticMean = arithmeticMean / count; return arithmeticMean > 0 ? geometricMean / arithmeticMean : 0; } /** * Calculate spectral rolloff (85% energy point) */ function calculateSpectralRolloff(magnitude, sampleRate) { const totalEnergy = magnitude.reduce((sum, mag) => sum + mag * mag, 0); const threshold = totalEnergy * 0.85; let cumulativeEnergy = 0; for (let i = 0; i < magnitude.length; i++) { cumulativeEnergy += magnitude[i] * magnitude[i]; if (cumulativeEnergy >= threshold) { return i * sampleRate / (2 * magnitude.length); } } return sampleRate / 2; // Nyquist frequency } /** * Process audio data into waveform segments */ export function processAudioToWaveform(audioData, sampleRate, segmentDurationMs, features) { const samplesPerSegment = Math.floor(sampleRate * segmentDurationMs / 1000); const numSegments = Math.ceil(audioData.length / samplesPerSegment); const dataPoints = []; for (let i = 0; i < numSegments; i++) { const startSample = i * samplesPerSegment; const endSample = Math.min(startSample + samplesPerSegment, audioData.length); const segmentData = audioData.slice(startSample, endSample); // Calculate basic metrics with improved amplitude calculation let maxAmplitude = 0; let sumSquares = 0; let sumAbs = 0; for (let j = 0; j < segmentData.length; j++) { const sample = Math.abs(segmentData[j]); maxAmplitude = Math.max(maxAmplitude, sample); sumSquares += sample * sample; sumAbs += sample; } // Apply more aggressive dynamic range compression // Use a combination of logarithmic and power compression for better dynamic range let compressedAmplitude; if (maxAmplitude > 0.01) { // Apply logarithmic compression for better dynamic range // Scale to 0-1 range, apply log compression, then scale back const normalized = maxAmplitude; const logCompressed = Math.log(1 + normalized * 9) / Math.log(10); // log10(1 + 9x) compressedAmplitude = logCompressed; } else { compressedAmplitude = maxAmplitude; } const rms = Math.sqrt(sumSquares / segmentData.length); const dB = rms > 0 ? 20 * Math.log10(rms) : -Infinity; const silent = rms < 0.001; // Silence threshold const dataPoint = { id: i, amplitude: compressedAmplitude, rms, dB, silent, startTime: startSample / sampleRate * 1000, endTime: endSample / sampleRate * 1000, startPosition: startSample * 2, // Assuming 16-bit samples endPosition: endSample * 2, samples: segmentData.length }; // Extract additional features if requested if (features && Object.keys(features).length > 0) { dataPoint.features = extractAudioFeatures(segmentData, sampleRate, features); } dataPoints.push(dataPoint); } return dataPoints; } /** * Calculate amplitude and RMS ranges from data points */ export function calculateRanges(dataPoints) { let minAmplitude = Infinity; let maxAmplitude = -Infinity; let minRms = Infinity; let maxRms = -Infinity; for (const point of dataPoints) { minAmplitude = Math.min(minAmplitude, point.amplitude); maxAmplitude = Math.max(maxAmplitude, point.amplitude); minRms = Math.min(minRms, point.rms); maxRms = Math.max(maxRms, point.rms); } return { amplitudeRange: { min: minAmplitude, max: maxAmplitude }, rmsRange: { min: minRms, max: maxRms } }; } //# sourceMappingURL=audioProcessing.js.map