@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
JavaScript
"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