package audio import ( "math" ) // CalculateRMSLevel calculates the RMS level of PCM samples and returns 0-100 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-100 (max int16 is 32767) level := int(rms / 32767.0 * 100.0) if level > 100 { level = 100 } return level } // CalculatePeakLevel returns the peak level of PCM samples as 0-100 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 } } return int(float64(peak) / 32767.0 * 100.0) } // 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 }