feat: Implement initial tuner application with core logic, audio processing, and presentation components.
This commit is contained in:
81
src/domain/harmonic-analyzer.ts
Normal file
81
src/domain/harmonic-analyzer.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Pure domain logic for harmonic analysis.
|
||||
* Helps identify fundamental frequency from harmonics.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check if two frequencies have a harmonic relationship.
|
||||
* @param f1 - First frequency
|
||||
* @param f2 - Second frequency
|
||||
* @param tolerance - Tolerance in cents (default 50)
|
||||
*/
|
||||
export function isHarmonic(f1: number, f2: number, tolerance: number = 50): boolean {
|
||||
const ratio = f2 / f1;
|
||||
const roundedRatio = Math.round(ratio);
|
||||
|
||||
// Check if ratio is close to an integer (2x, 3x, 4x, etc.)
|
||||
if (roundedRatio < 2 || roundedRatio > 6) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate cents difference
|
||||
const expectedFreq = f1 * roundedRatio;
|
||||
const cents = 1200 * Math.log2(f2 / expectedFreq);
|
||||
|
||||
return Math.abs(cents) < tolerance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect harmonic series from a list of frequency candidates.
|
||||
* Returns the fundamental frequency if a harmonic series is detected.
|
||||
*/
|
||||
export function detectHarmonicSeries(frequencies: number[]): number | null {
|
||||
if (frequencies.length < 2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sort frequencies
|
||||
const sorted = [...frequencies].sort((a, b) => a - b);
|
||||
|
||||
// Check if we have a harmonic series starting from the lowest frequency
|
||||
const fundamental = sorted[0];
|
||||
let harmonicCount = 1; // fundamental counts as first harmonic
|
||||
|
||||
for (let i = 1; i < sorted.length; i++) {
|
||||
if (isHarmonic(fundamental, sorted[i])) {
|
||||
harmonicCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found at least 2 harmonics (fundamental + 1 more), it's likely a harmonic series
|
||||
if (harmonicCount >= 2) {
|
||||
return fundamental;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the fundamental frequency from a list of candidates.
|
||||
* Uses harmonic analysis to filter out overtones.
|
||||
*/
|
||||
export function findFundamentalFromHarmonics(candidates: number[]): number {
|
||||
if (candidates.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (candidates.length === 1) {
|
||||
return candidates[0];
|
||||
}
|
||||
|
||||
// Try to detect harmonic series
|
||||
const fundamental = detectHarmonicSeries(candidates);
|
||||
|
||||
if (fundamental) {
|
||||
return fundamental;
|
||||
}
|
||||
|
||||
// If no harmonic series detected, return the lowest frequency
|
||||
// (most conservative approach)
|
||||
return Math.min(...candidates);
|
||||
}
|
||||
Reference in New Issue
Block a user