UNPKG

@loqalabs/loqa-audio-dsp

Version:

Production-grade Expo native module for audio DSP analysis (FFT, pitch detection, formant extraction, spectral analysis)

116 lines 4.56 kB
// detectPitch - Pitch detection API using YIN algorithm import LoqaAudioDspModule from './LoqaAudioDspModule'; import { NativeModuleError } from './errors'; import { logDebug } from './utils'; import { validateAudioBuffer, validateSampleRate } from './validation'; /** * Detects pitch using YIN algorithm * * This function performs fundamental frequency (F0) detection on audio data using * the YIN algorithm, which is optimized for voice and monophonic instruments. * It accepts audio buffers as Float32Array or number[], validates the input, * and returns pitch information with confidence scores. * * @param audioBuffer - Audio samples (Float32Array or number[]) * @param sampleRate - Sample rate in Hz (8000-48000) * @param options - Pitch detection options (minFrequency, maxFrequency) * @returns Promise resolving to pitch result with frequency, confidence, and voicing * @throws ValidationError if buffer or sample rate are invalid * @throws NativeModuleError if native computation fails * * @example * ```typescript * const audioData = new Float32Array(2048); * // ... fill with audio samples ... * * const result = await detectPitch(audioData, 44100, { * minFrequency: 80, // Minimum detectable pitch (Hz) * maxFrequency: 400 // Maximum detectable pitch (Hz) * }); * * if (result.isVoiced) { * console.log(`Detected pitch: ${result.frequency} Hz`); * console.log(`Confidence: ${result.confidence}`); * } else { * console.log('No pitch detected (unvoiced segment)'); * } * ``` */ export async function detectPitch(audioBuffer, sampleRate, options) { // Step 1: Validate audio buffer and sample rate logDebug('detectPitch called', { bufferLength: audioBuffer.length, bufferType: audioBuffer instanceof Float32Array ? 'Float32Array' : 'number[]', sampleRate, options, }); validateAudioBuffer(audioBuffer); validateSampleRate(sampleRate); // Step 2: Extract and set defaults for optional parameters // Default to human voice range: 80-400 Hz const minFrequency = options?.minFrequency ?? 80; const maxFrequency = options?.maxFrequency ?? 400; // Validate frequency range if (minFrequency <= 0 || maxFrequency <= 0) { throw new NativeModuleError('Frequency range must be positive', { minFrequency, maxFrequency, }); } if (minFrequency >= maxFrequency) { throw new NativeModuleError('minFrequency must be less than maxFrequency', { minFrequency, maxFrequency, }); } // 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 pitch detection', { sampleRate, minFrequency, maxFrequency, }); try { // Step 4: Call native module const nativeResult = await LoqaAudioDspModule.detectPitch(bufferArray, sampleRate, { minFrequency, maxFrequency, }); logDebug('Native module returned pitch result', { frequency: nativeResult.frequency, confidence: nativeResult.confidence, isVoiced: nativeResult.isVoiced, }); // Step 5: Convert result to PitchResult type // Native module returns dictionary/map, convert to proper TypeScript type const result = { frequency: nativeResult.frequency !== null ? nativeResult.frequency : null, confidence: nativeResult.confidence, isVoiced: nativeResult.isVoiced, }; logDebug('detectPitch completed successfully', { frequency: result.frequency, confidence: result.confidence, isVoiced: result.isVoiced, }); return result; } catch (error) { // Step 6: Wrap native errors in NativeModuleError with context const errorMessage = error instanceof Error ? error.message : String(error); logDebug('detectPitch failed', { error: errorMessage, sampleRate, bufferLength: audioBuffer.length, }); throw new NativeModuleError(`Pitch detection failed: ${errorMessage}`, { originalError: error, sampleRate, minFrequency, maxFrequency, bufferLength: audioBuffer.length, }); } } //# sourceMappingURL=detectPitch.js.map