Skip to content

Instantly share code, notes, and snippets.

@ForeverZer0
Created February 16, 2024 18:34
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ForeverZer0/9811346b0590850d9aafbf6e3a318275 to your computer and use it in GitHub Desktop.
Save ForeverZer0/9811346b0590850d9aafbf6e3a318275 to your computer and use it in GitHub Desktop.
Cavabar - easily get formatted CAVA output that is suitable for any taskbar.
// Compile executable with "go build cavabar.go" or just run with "go run cavabar.go"
package main
import (
"encoding/binary"
"flag"
"fmt"
"io"
"math"
"os"
"os/exec"
"os/signal"
"syscall"
)
func checkError(msg string, err error) {
if err != nil {
fmt.Fprintf(os.Stderr, msg+": %s", err)
os.Exit(1)
}
}
func main() {
// Parse arguments
var source, bars, cava string
var rate, count int
var mono, help bool
flag.StringVar(&cava, "cava", "cava", "filename/path to execute cava with")
flag.StringVar(&source, "source", "pipewire", "cava audio source")
flag.IntVar(&rate, "rate", 30, "number of updates per second")
flag.IntVar(&count, "count", 14, "number of bars to display")
flag.StringVar(&bars, "bars", "▁▂▃▄▅▆▇█", "ordered string of characters to use for the bars")
flag.BoolVar(&mono, "mono", false, "use averaged single channel for output")
flag.BoolVar(&help, "help", false, "display this message")
flag.Parse()
if help {
flag.CommandLine.SetOutput(os.Stdout)
flag.PrintDefaults()
os.Exit(0)
}
channels := "stereo"
if mono {
channels = "mono"
}
// Create a temporary config file for cava
cfg, err := os.CreateTemp("", "cavabar-*.conf")
checkError("failed to create cava config", err)
defer os.Remove(cfg.Name())
_, err = fmt.Fprintf(cfg, `[general]
mode = normal
framerate = %d
bars = %d
[input]
source = %s
[output]
channels = %s
method = raw
raw_target = /dev/stdout
data_format = binary
binary_bits = 16bit`, rate, count, source, channels)
checkError("failed to write config", err)
cfg.Close()
// Prepare sub-process and setup a pipe
cmd := exec.Command(cava, "-p", cfg.Name())
stdout, err := cmd.StdoutPipe()
checkError("failed to create pipe", err)
checkError("failed to start cava", cmd.Start())
defer cmd.Process.Kill()
// Initialize buffers and values used for computation
runes := []rune(bars)
maxIndex := float64(len(runes) - 1)
const maxValue float64 = 65530 // Cava docs say 65530, not 65535
result := make([]rune, count)
buf := make([]uint16, count)
// Trap SIGTERM and SIGINT signals, and exit gracefully
var done bool
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
done = true
}()
// Main loop
for {
if done {
close(sigs)
break
}
if err := binary.Read(stdout, binary.NativeEndian, buf); err != nil {
if err != io.EOF {
checkError("error reading from pipe", err)
}
break
}
for i, pcm := range buf {
index := int(math.Round((float64(pcm) / maxValue) * maxIndex))
result[i] = runes[index]
}
fmt.Println(string(result))
}
}
// vim: tabstop=4 shiftwidth=4 softtabstop=0 noexpandtab smarttab
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment