Add auto-restart-voicemeeter tool.
parent
9b8393f27c
commit
1bd68fc0ba
|
@ -0,0 +1,10 @@
|
|||
module git.icedream.tech/icedream/auto-restart-voicemeeter
|
||||
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/JamesDunne/go-asio v0.0.0-20150322061733-6c4b099ca927
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
)
|
||||
|
||||
require github.com/cenkalti/backoff/v4 v4.2.0 // indirect
|
|
@ -0,0 +1,6 @@
|
|||
github.com/JamesDunne/go-asio v0.0.0-20150322061733-6c4b099ca927 h1:qcTT3RNLm21eamlM+G/AJrh1KG9VcyW8AwOsbzVRW04=
|
||||
github.com/JamesDunne/go-asio v0.0.0-20150322061733-6c4b099ca927/go.mod h1:dRYieepeZSO8hlYXKnpnn6WqR7Myv+kb+Y+w14NxfFw=
|
||||
github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4=
|
||||
github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
|
@ -0,0 +1,243 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -26,7 +26,7 @@ then
|
|||
rm winfsp.zip
|
||||
fi
|
||||
|
||||
for bin in foobar2000 tunadish tunaposter prime4
|
||||
for bin in auto-restart-voicemeeter foobar2000 tunadish tunaposter prime4
|
||||
do
|
||||
cd "$SCRIPT_DIR/$bin"
|
||||
go build -ldflags "-s -w" -o "$GOBIN/$bin$ext" -v
|
||||
|
|
Loading…
Reference in New Issue