138 lines
2.4 KiB
Go
138 lines
2.4 KiB
Go
package audio
|
|
|
|
import (
|
|
"math"
|
|
)
|
|
|
|
// CalculateRMSLevel calculates the RMS level of PCM samples and returns 0-100 (Logarithmic/dB)
|
|
func CalculateRMSLevel(samples []int16) int {
|
|
if len(samples) == 0 {
|
|
return 0
|
|
}
|
|
|
|
var sum float64
|
|
for _, s := range samples {
|
|
sum += float64(s) * float64(s)
|
|
}
|
|
|
|
rms := math.Sqrt(sum / float64(len(samples)))
|
|
|
|
// Normalize to 0.0 - 1.0
|
|
val := rms / 32768.0
|
|
if val < 0.000001 { // Avoid log(0)
|
|
return 0
|
|
}
|
|
|
|
// Convert to dB
|
|
db := 20 * math.Log10(val)
|
|
|
|
// Map -50dB (silence floor) to 0dB (max) to 0-100
|
|
const minDB = -50.0
|
|
|
|
if db < minDB {
|
|
return 0
|
|
}
|
|
|
|
// Scale
|
|
level := int((db - minDB) * (100.0 / (0 - minDB)))
|
|
|
|
if level > 100 {
|
|
level = 100
|
|
}
|
|
return level
|
|
}
|
|
|
|
// CalculatePeakLevel returns the peak level of PCM samples as 0-100 (Logarithmic/dB)
|
|
func CalculatePeakLevel(samples []int16) int {
|
|
if len(samples) == 0 {
|
|
return 0
|
|
}
|
|
|
|
var peak int16
|
|
for _, s := range samples {
|
|
if s < 0 {
|
|
s = -s
|
|
}
|
|
if s > peak {
|
|
peak = s
|
|
}
|
|
}
|
|
|
|
// Normalize
|
|
val := float64(peak) / 32768.0
|
|
if val < 0.000001 {
|
|
return 0
|
|
}
|
|
|
|
db := 20 * math.Log10(val)
|
|
const minDB = -50.0
|
|
|
|
if db < minDB {
|
|
// Linear falloff for very low signals to avoid clutter
|
|
return 0
|
|
}
|
|
|
|
level := int((db - minDB) * (100.0 / (0 - minDB)))
|
|
if level > 100 {
|
|
level = 100
|
|
}
|
|
|
|
return level
|
|
}
|
|
|
|
// LevelToBar converts a 0-100 level to a visual bar string
|
|
func LevelToBar(level, width int) string {
|
|
if level < 0 {
|
|
level = 0
|
|
}
|
|
if level > 100 {
|
|
level = 100
|
|
}
|
|
|
|
filled := level * width / 100
|
|
empty := width - filled
|
|
|
|
bar := ""
|
|
for i := 0; i < filled; i++ {
|
|
bar += "█"
|
|
}
|
|
for i := 0; i < empty; i++ {
|
|
bar += "░"
|
|
}
|
|
|
|
return bar
|
|
}
|
|
|
|
// LevelToMeter converts a 0-100 level to a visual VU meter with varying heights
|
|
func LevelToMeter(level, width int) string {
|
|
if level < 0 {
|
|
level = 0
|
|
}
|
|
if level > 100 {
|
|
level = 100
|
|
}
|
|
|
|
// Use block characters of varying heights
|
|
blocks := []rune{'░', '▁', '▂', '▃', '▄', '▅', '▆', '▇', '█'}
|
|
|
|
meter := ""
|
|
for i := 0; i < width; i++ {
|
|
// Each position represents a portion of the level
|
|
threshold := (i + 1) * 100 / width
|
|
if level >= threshold {
|
|
meter += string(blocks[8]) // Full
|
|
} else if level >= threshold-10 {
|
|
// Partial - calculate which block to use
|
|
partial := (level - (threshold - 10)) * 8 / 10
|
|
if partial < 0 {
|
|
partial = 0
|
|
}
|
|
meter += string(blocks[partial])
|
|
} else {
|
|
meter += string(blocks[0]) // Empty
|
|
}
|
|
}
|
|
|
|
return meter
|
|
}
|