Created
February 16, 2024 18:34
-
-
Save ForeverZer0/9811346b0590850d9aafbf6e3a318275 to your computer and use it in GitHub Desktop.
Cavabar - easily get formatted CAVA output that is suitable for any taskbar.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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