Add 10-band equalizer support and AutoEQ integration
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
package backend
|
||||
|
||||
import "math"
|
||||
|
||||
// AutoEQ uses 10 bands at these frequencies (in Hz)
|
||||
var autoEQFreqs = []float64{31.25, 62.5, 125, 250, 500, 1000, 2000, 4000, 8000, 16000}
|
||||
|
||||
// Supersonic uses 15 bands at these frequencies (in Hz)
|
||||
var supersonicFreqs = []float64{25, 40, 63, 100, 160, 250, 400, 630, 1000, 1600, 2500, 4000, 6300, 10000, 16000}
|
||||
|
||||
// InterpolateAutoEQTo15Band converts a 10-band AutoEQ profile to Supersonic's 15-band ISO equalizer.
|
||||
// Uses logarithmic frequency positioning with linear dB interpolation.
|
||||
//
|
||||
// Parameters:
|
||||
// - autoEQGains: Array of 10 gain values (dB) from AutoEQ at 31, 62, 125, 250, 500, 1k, 2k, 4k, 8k, 16k Hz
|
||||
//
|
||||
// Returns:
|
||||
// - Array of 15 gain values (dB) for Supersonic at 25, 40, 63, 100, 160, 250, 400, 630, 1k, 1.6k, 2.5k, 4k, 6.3k, 10k, 16k Hz
|
||||
func InterpolateAutoEQTo15Band(autoEQGains [10]float64) [15]float64 {
|
||||
var result [15]float64
|
||||
|
||||
for i, targetFreq := range supersonicFreqs {
|
||||
// Find the surrounding AutoEQ bands for this target frequency
|
||||
lowerIdx, upperIdx := findSurroundingBands(targetFreq)
|
||||
|
||||
if lowerIdx == upperIdx {
|
||||
// Exact match - use the AutoEQ gain directly
|
||||
result[i] = autoEQGains[lowerIdx]
|
||||
} else if lowerIdx == -1 {
|
||||
// Target frequency is below the lowest AutoEQ band (25 Hz < 31.25 Hz)
|
||||
// Extrapolate using the first two AutoEQ bands
|
||||
result[i] = extrapolateBelow(targetFreq, autoEQGains)
|
||||
} else if upperIdx == -1 {
|
||||
// Target frequency is above the highest AutoEQ band (should not happen with our ranges)
|
||||
// Use the highest AutoEQ gain
|
||||
result[i] = autoEQGains[len(autoEQGains)-1]
|
||||
} else {
|
||||
// Interpolate between two AutoEQ bands
|
||||
fLow := autoEQFreqs[lowerIdx]
|
||||
fHigh := autoEQFreqs[upperIdx]
|
||||
gLow := autoEQGains[lowerIdx]
|
||||
gHigh := autoEQGains[upperIdx]
|
||||
|
||||
// Calculate logarithmic position between the two bands
|
||||
// t = log(targetFreq/fLow) / log(fHigh/fLow)
|
||||
t := math.Log(targetFreq/fLow) / math.Log(fHigh/fLow)
|
||||
|
||||
// Linear interpolation of gain values
|
||||
result[i] = gLow + t*(gHigh-gLow)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// findSurroundingBands finds the AutoEQ band indices that surround the target frequency.
|
||||
// Returns (idx, idx) if there's an exact match, (lowerIdx, upperIdx) if between bands,
|
||||
// (-1, 0) if below all bands, or (lastIdx, -1) if above all bands.
|
||||
func findSurroundingBands(targetFreq float64) (lowerIdx, upperIdx int) {
|
||||
const epsilon = 0.01 // Tolerance for floating point comparison
|
||||
|
||||
// Check if below all bands
|
||||
if targetFreq < autoEQFreqs[0]-epsilon {
|
||||
return -1, 0
|
||||
}
|
||||
|
||||
// Check if above all bands
|
||||
if targetFreq > autoEQFreqs[len(autoEQFreqs)-1]+epsilon {
|
||||
return len(autoEQFreqs) - 1, -1
|
||||
}
|
||||
|
||||
// Find surrounding bands
|
||||
for i := 0; i < len(autoEQFreqs)-1; i++ {
|
||||
// Check for exact match
|
||||
if math.Abs(targetFreq-autoEQFreqs[i]) < epsilon {
|
||||
return i, i
|
||||
}
|
||||
|
||||
// Check if between this band and the next
|
||||
if targetFreq > autoEQFreqs[i] && targetFreq < autoEQFreqs[i+1] {
|
||||
return i, i + 1
|
||||
}
|
||||
}
|
||||
|
||||
// Check for exact match with last band
|
||||
if math.Abs(targetFreq-autoEQFreqs[len(autoEQFreqs)-1]) < epsilon {
|
||||
return len(autoEQFreqs) - 1, len(autoEQFreqs) - 1
|
||||
}
|
||||
|
||||
// Should not reach here with valid input
|
||||
return len(autoEQFreqs) - 1, -1
|
||||
}
|
||||
|
||||
// extrapolateBelow extrapolates the gain for frequencies below the lowest AutoEQ band.
|
||||
// Uses the slope between the first two AutoEQ bands.
|
||||
func extrapolateBelow(targetFreq float64, autoEQGains [10]float64) float64 {
|
||||
// Use the slope between the first two bands to extrapolate
|
||||
f1 := autoEQFreqs[0]
|
||||
f2 := autoEQFreqs[1]
|
||||
g1 := autoEQGains[0]
|
||||
g2 := autoEQGains[1]
|
||||
|
||||
// Calculate the slope in log-frequency space
|
||||
slope := (g2 - g1) / math.Log(f2/f1)
|
||||
|
||||
// Extrapolate
|
||||
return g1 + slope*math.Log(targetFreq/f1)
|
||||
}
|
||||
|
||||
// Interpolate15BandTo10Band converts a 15-band ISO equalizer to 10-band format.
|
||||
// Uses logarithmic frequency positioning with linear dB interpolation.
|
||||
func Interpolate15BandTo10Band(gains15Band [15]float64) [10]float64 {
|
||||
var result [10]float64
|
||||
|
||||
for i, targetFreq := range autoEQFreqs {
|
||||
// Find the surrounding 15-band frequencies for this target
|
||||
lowerIdx, upperIdx := findSurrounding15Bands(targetFreq)
|
||||
|
||||
if lowerIdx == upperIdx {
|
||||
// Exact match
|
||||
result[i] = gains15Band[lowerIdx]
|
||||
} else if lowerIdx == -1 {
|
||||
// Below lowest band - use first band value
|
||||
result[i] = gains15Band[0]
|
||||
} else if upperIdx == -1 {
|
||||
// Above highest band - use last band value
|
||||
result[i] = gains15Band[len(gains15Band)-1]
|
||||
} else {
|
||||
// Interpolate between two 15-band frequencies
|
||||
fLow := supersonicFreqs[lowerIdx]
|
||||
fHigh := supersonicFreqs[upperIdx]
|
||||
gLow := gains15Band[lowerIdx]
|
||||
gHigh := gains15Band[upperIdx]
|
||||
|
||||
// Logarithmic position
|
||||
t := math.Log(targetFreq/fLow) / math.Log(fHigh/fLow)
|
||||
|
||||
// Linear interpolation of gain
|
||||
result[i] = gLow + t*(gHigh-gLow)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// findSurrounding15Bands finds the 15-band indices that surround the target frequency
|
||||
func findSurrounding15Bands(targetFreq float64) (lowerIdx, upperIdx int) {
|
||||
const epsilon = 0.01
|
||||
|
||||
if targetFreq < supersonicFreqs[0]-epsilon {
|
||||
return -1, 0
|
||||
}
|
||||
|
||||
if targetFreq > supersonicFreqs[len(supersonicFreqs)-1]+epsilon {
|
||||
return len(supersonicFreqs) - 1, -1
|
||||
}
|
||||
|
||||
for i := 0; i < len(supersonicFreqs)-1; i++ {
|
||||
if math.Abs(targetFreq-supersonicFreqs[i]) < epsilon {
|
||||
return i, i
|
||||
}
|
||||
|
||||
if targetFreq > supersonicFreqs[i] && targetFreq < supersonicFreqs[i+1] {
|
||||
return i, i + 1
|
||||
}
|
||||
}
|
||||
|
||||
if math.Abs(targetFreq-supersonicFreqs[len(supersonicFreqs)-1]) < epsilon {
|
||||
return len(supersonicFreqs) - 1, len(supersonicFreqs) - 1
|
||||
}
|
||||
|
||||
return len(supersonicFreqs) - 1, -1
|
||||
}
|
||||
Reference in New Issue
Block a user