1
0
Fork 0
livestream-tools/icedreammusic/auto-restart-voicemeeter/main.go

244 lines
6.0 KiB
Go

package main
import (
"errors"
"fmt"
"log"
"math"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"time"
asio "github.com/JamesDunne/go-asio"
"github.com/cenkalti/backoff/v4"
)
func reverse(s string) string {
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r)
}
func main() {
// TODO - handle interrupt signal here rather than in sample loop
bo := backoff.NewConstantBackOff(time.Second)
err := backoff.Retry(run, bo)
if err != nil {
log.Fatal(err)
}
}
func run() error {
fmt.Printf("CoInitialize(0)\n")
asio.CoInitialize(0)
defer fmt.Printf("CoUninitialize()\n")
defer asio.CoUninitialize()
drivers, err := asio.ListDrivers()
if err != nil {
return backoff.Permanent(err)
}
var mainOutDriver *asio.ASIODriver
for _, driver := range drivers {
if driver.Name != "Voicemeeter Virtual ASIO" {
continue
}
mainOutDriver = driver
break
}
if mainOutDriver == nil {
return backoff.Permanent(errors.New("could not find main Voicemeeter ASIO output"))
}
log.Println("ASIO driver:", mainOutDriver.GUID, mainOutDriver.CLSID, mainOutDriver.Name)
if err := mainOutDriver.Open(); err != nil {
return err
}
defer mainOutDriver.Close()
drv := mainOutDriver.ASIO
fmt.Printf("getDriverName(): '%s'\n", drv.GetDriverName())
fmt.Printf("getDriverVersion(): %d\n", drv.GetDriverVersion())
// mainOutUnknown := drv.AsIUnknown()
// mainOutUnknown.AddRef()
// defer mainOutUnknown.Release()
// getChannels
in, out, err := drv.GetChannels()
if err != nil {
return err
}
fmt.Printf("getChannels(): %d, %d\n", in, out)
// getBufferSize
minSize, maxSize, preferredSize, granularity, err := drv.GetBufferSize()
if err != nil {
return err
}
fmt.Printf("getBufferSize(): %d, %d, %d, %d\n", minSize, maxSize, preferredSize, granularity)
// getSampleRate
srate, err := drv.GetSampleRate()
if err != nil {
return err
}
fmt.Printf("getSampleRate(): %v\n", srate)
// canSampleRate
var sampleRate float64
// for _, canSampleRate := range []int{
// 48000,
// 44100,
// } {
// sampleRateF64 := float64(canSampleRate)
// err = drv.CanSampleRate(sampleRateF64)
// fmt.Printf("canSampleRate(%q): %v\n", sampleRateF64, err)
// if err != nil {
// continue
// }
// sampleRate = canSampleRate
// break
// }
// if sampleRate == 0 {
// // log.Fatal("Could not negotiate a compatible samplerate")
// // return
// fmt.Println("WARNING: Defaulting to 48000 Hz")
// sampleRate = 48000
// }
sampleRate = srate
// sampleRate = 48000
// SetSampleRate
err = drv.SetSampleRate(sampleRate)
fmt.Printf("setSampleRate(%v): %v\n", float64(sampleRate), err)
if err != nil {
fmt.Println("WARNING: setSampleRate failed, ignoring:", err)
}
// outputReady
fmt.Printf("outputReady(): %v\n", drv.OutputReady())
// open control panel:
// drv.ControlPanel()
bufferDescriptors := make([]asio.BufferInfo, 0, in+out)
for i := 0; i < in; i++ {
bufferDescriptors = append(bufferDescriptors, asio.BufferInfo{
Channel: i,
IsInput: true,
})
cinfo, err := drv.GetChannelInfo(i, true)
if err != nil {
log.Fatal(err)
continue
}
fmt.Printf(" IN%-2d: active=%v, group=%d, type=%d, name=%s\n", i+1, cinfo.IsActive, cinfo.ChannelGroup, cinfo.SampleType, cinfo.Name)
}
for i := 0; i < out; i++ {
bufferDescriptors = append(bufferDescriptors, asio.BufferInfo{
Channel: i,
IsInput: false,
})
cinfo, err := drv.GetChannelInfo(i, false)
if err != nil {
log.Fatal(err)
continue
}
fmt.Printf("OUT%-2d: active=%v, group=%d, type=%d, name=%s\n", i+1, cinfo.IsActive, cinfo.ChannelGroup, cinfo.SampleType, cinfo.Name)
}
err = drv.CreateBuffers(bufferDescriptors, 512, asio.Callbacks{
Message: func(selector, value int32, message uintptr, opt *float64) int32 {
log.Println("Message:", selector, value, message, opt)
return 0
},
BufferSwitch: func(doubleBufferIndex int, directProcess bool) {
log.Println("Buffer switch:", doubleBufferIndex, directProcess)
},
BufferSwitchTimeInfo: func(params *asio.ASIOTime, doubleBufferIndex int32, directProcess bool) *asio.ASIOTime {
log.Println("Buffer switch time info:", params, doubleBufferIndex, directProcess)
return params
},
SampleRateDidChange: func(rate float64) {
log.Println("Sample rate did change:", rate)
},
})
if err != nil {
return err
}
defer fmt.Printf("disposeBuffers()\n")
defer drv.DisposeBuffers()
fmt.Printf("createBuffers()\n")
// getLatencies
latin, latout, err := drv.GetLatencies()
if err != nil {
return err
}
fmt.Printf("getLatencies(): %d, %d\n", latin, latout)
err = drv.Start()
if err != nil {
return err
}
defer drv.Stop()
c := make(chan os.Signal, 1)
go signal.Notify(c, os.Interrupt, syscall.SIGTERM)
output := new(strings.Builder)
ladder := " ▁▂▃▄▅▆▇█"
chars := []rune(reverse(ladder) + ladder)
grace := 5 * time.Second
lastSignalTime := time.Now()
for {
select {
case <-time.After(33 * time.Millisecond):
output.Reset()
for _, desc := range bufferDescriptors {
for _, buf := range desc.Buffers {
output.WriteString("|")
if buf == nil {
output.WriteString("?")
continue
}
if *buf != 0 {
lastSignalTime = time.Now()
}
output.WriteString(fmt.Sprintf("%c",
chars[len(chars)/2+
int(float64(len(chars))/2*
float64(*buf)/float64(math.MaxInt32))]))
}
}
os.Stdout.WriteString(output.String() + "\r")
if time.Now().Sub(lastSignalTime) > time.Second {
os.Stdout.WriteString("Silence!\r")
}
if time.Now().Sub(lastSignalTime) > grace {
os.Stdout.WriteString("\n")
log.Println("Restarting audio engine...")
cmd := exec.Command(`C:\Program Files (x86)\VB\Voicemeeter\voicemeeterpro.exe`, "-r")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
log.Printf("Restart audio engine result: %v", cmd.Run())
// time.Sleep(3 * time.Second)
// lastSignalTime = time.Now()
return errors.New("audio engine restarted, retry")
}
case <-c:
return nil
}
}
}