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.

268 lines (247 loc) 7.74 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateRanges = calculateRanges; exports.extractAudioFeatures = extractAudioFeatures; exports.processAudioToWaveform = processAudioToWaveform; /** * 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 */ 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 */ 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 */ 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