174 lines
5.4 KiB
Go
174 lines
5.4 KiB
Go
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}
|
|
|
|
// InterpolateEQ10To15Band converts a 10-band EQ profile to Supersonic's 15-band ISO equalizer.
|
|
// Uses logarithmic frequency positioning with linear dB interpolation.
|
|
//
|
|
// Parameters:
|
|
// - eq10Gains: Array of 10 gain values (dB) from 10-band EQ 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 InterpolateEQ10To15Band(eq10Gains [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] = eq10Gains[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, eq10Gains)
|
|
} 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] = eq10Gains[len(eq10Gains)-1]
|
|
} else {
|
|
// Interpolate between two AutoEQ bands
|
|
fLow := autoEQFreqs[lowerIdx]
|
|
fHigh := autoEQFreqs[upperIdx]
|
|
gLow := eq10Gains[lowerIdx]
|
|
gHigh := eq10Gains[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)
|
|
}
|
|
|
|
// InterpolateEQ15BandTo10Band converts a 15-band ISO equalizer to 10-band format.
|
|
// Uses logarithmic frequency positioning with linear dB interpolation.
|
|
func InterpolateEQ15BandTo10Band(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
|
|
}
|