@loqalabs/loqa-audio-dsp
Version:
Production-grade Expo native module for audio DSP analysis (FFT, pitch detection, formant extraction, spectral analysis)
112 lines • 4.35 kB
JavaScript
// extractFormants - Formant extraction API using LPC analysis
import LoqaAudioDspModule from './LoqaAudioDspModule';
import { NativeModuleError } from './errors';
import { logDebug } from './utils';
import { validateAudioBuffer, validateSampleRate } from './validation';
/**
* Extracts formants (F1, F2, F3) using LPC analysis
*
* This function performs Linear Predictive Coding (LPC) analysis to extract
* the first three formant frequencies from audio data. Formants are resonant
* frequencies of the vocal tract and are essential for vowel identification
* and speech analysis.
*
* @param audioBuffer - Audio samples (Float32Array or number[])
* @param sampleRate - Sample rate in Hz (8000-48000)
* @param options - Formant extraction options (lpcOrder)
* @returns Promise resolving to formants result with F1, F2, F3 and bandwidths
* @throws ValidationError if buffer or sample rate are invalid
* @throws NativeModuleError if native computation fails
*
* @example
* ```typescript
* const audioData = new Float32Array(2048);
* // ... fill with voiced audio samples ...
*
* const result = await extractFormants(audioData, 44100, {
* lpcOrder: 14 // Optional: LPC order (defaults to sampleRate/1000 + 2)
* });
*
* console.log(`F1: ${result.f1} Hz`);
* console.log(`F2: ${result.f2} Hz`);
* console.log(`F3: ${result.f3} Hz`);
* console.log(`Bandwidths:`, result.bandwidths);
* ```
*/
export async function extractFormants(audioBuffer, sampleRate, options) {
// Step 1: Validate audio buffer and sample rate
logDebug('extractFormants called', {
bufferLength: audioBuffer.length,
bufferType: audioBuffer instanceof Float32Array ? 'Float32Array' : 'number[]',
sampleRate,
options,
});
validateAudioBuffer(audioBuffer);
validateSampleRate(sampleRate);
// Step 2: Calculate default LPC order based on sample rate
// Formula: lpcOrder = (sampleRate / 1000) + 2
// This provides appropriate resolution for formant analysis
const defaultLpcOrder = Math.floor(sampleRate / 1000) + 2;
const lpcOrder = options?.lpcOrder ?? defaultLpcOrder;
// Validate LPC order is positive
if (lpcOrder <= 0) {
throw new NativeModuleError('LPC order must be positive', {
lpcOrder,
});
}
// Step 3: Convert to number[] for React Native bridge
// React Native bridge requires plain arrays, not typed arrays
const bufferArray = audioBuffer instanceof Float32Array ? Array.from(audioBuffer) : audioBuffer;
logDebug('Calling native module for formant extraction', {
sampleRate,
lpcOrder,
defaultLpcOrder,
});
try {
// Step 4: Call native module
const nativeResult = await LoqaAudioDspModule.extractFormants(bufferArray, sampleRate, {
lpcOrder,
});
logDebug('Native module returned formants result', {
f1: nativeResult.f1,
f2: nativeResult.f2,
f3: nativeResult.f3,
hasBandwidths: !!nativeResult.bandwidths,
});
// Step 5: Convert result to FormantsResult type
// Native module returns dictionary/map, convert to proper TypeScript type
const result = {
f1: nativeResult.f1,
f2: nativeResult.f2,
f3: nativeResult.f3,
bandwidths: {
f1: nativeResult.bandwidths.f1,
f2: nativeResult.bandwidths.f2,
f3: nativeResult.bandwidths.f3,
},
};
logDebug('extractFormants completed successfully', {
f1: result.f1,
f2: result.f2,
f3: result.f3,
});
return result;
}
catch (error) {
// Step 6: Wrap native errors in NativeModuleError with context
const errorMessage = error instanceof Error ? error.message : String(error);
logDebug('extractFormants failed', {
error: errorMessage,
sampleRate,
lpcOrder,
bufferLength: audioBuffer.length,
});
throw new NativeModuleError(`Formant extraction failed: ${errorMessage}`, {
originalError: error,
sampleRate,
lpcOrder,
bufferLength: audioBuffer.length,
});
}
}
//# sourceMappingURL=extractFormants.js.map