Hey @icedream
Great bit of code you put together…it served as a fantastic starting point and really helped.
This is the modified code I put together…its rough and ready as it was all edited in TextEdit on the Mac. I don’t have any GitHub experience I’m afraid.
The code below outputs to a text file which I use as a source in OBS. Works well…only issue as mentioned is the tendency to mess with Soundswitch.
I modified the string array for decks one and two to exclude the bits I wasn’t interested in, and added the external mixer float value so I could get the up fader positions. This means it will only write to the output file when the fader value is near 1 (up).
If I had more time I would probably tidy this up but it may be useful to you.
package main
import (
"stagelinq"
"log"
"time"
"sync"
"fmt"
"os"
"bufio"
)
const (
appName = "Icedream StagelinQ Receiver"
appVersion = "0.0.0"
timeout = 5 * time.Second
)
var stateValues = []string{
stagelinq.EngineDeck1ExternalMixerVolume,
stagelinq.EngineDeck1Play,
stagelinq.EngineDeck1PlayState,
stagelinq.EngineDeck1TrackArtistName,
stagelinq.EngineDeck1TrackSongLoaded,
stagelinq.EngineDeck1TrackSongName,
stagelinq.EngineDeck2ExternalMixerVolume,
stagelinq.EngineDeck2Play,
stagelinq.EngineDeck2PlayState,
stagelinq.EngineDeck2TrackArtistName,
stagelinq.EngineDeck2TrackSongLoaded,
stagelinq.EngineDeck2TrackSongName,
stagelinq.EngineDeck3ExternalMixerVolume,
stagelinq.EngineDeck3Play,
stagelinq.EngineDeck3PlayState,
stagelinq.EngineDeck3PlayStatePath,
stagelinq.EngineDeck3TrackArtistName,
stagelinq.EngineDeck3TrackTrackNetworkPath,
stagelinq.EngineDeck3TrackSongLoaded,
stagelinq.EngineDeck3TrackSongName,
stagelinq.EngineDeck3TrackTrackData,
stagelinq.EngineDeck3TrackTrackName,
stagelinq.EngineDeck4ExternalMixerVolume,
stagelinq.EngineDeck4Play,
stagelinq.EngineDeck4PlayState,
stagelinq.EngineDeck4PlayStatePath,
stagelinq.EngineDeck4TrackArtistName,
stagelinq.EngineDeck4TrackTrackNetworkPath,
stagelinq.EngineDeck4TrackSongLoaded,
stagelinq.EngineDeck4TrackSongName,
stagelinq.EngineDeck4TrackTrackData,
stagelinq.EngineDeck4TrackTrackName,
}
func makeStateMap() map[string]bool {
retval := map[string]bool{}
for _, value := range stateValues {
retval[value] = false
}
return retval
}
func allStateValuesReceived(v map[string]bool) bool {
for _, value := range v {
if !value {
return false
}
}
return true
}
func connectDevice(device *stagelinq.Device, listener *stagelinq.Listener){
log.Printf("%s %q",device.IP.String(), "Found StageLinQ Device on Network")
// discover provided services
log.Println("\tattempting to connect to this device…")
deviceConn, err := device.Connect(listener.Token(), []*stagelinq.Service{})
if err != nil {
log.Printf("%s", "Failed to connect to device - Probably because no device but self found" + device.Name)
log.Printf("WARNING: %s", err.Error())
return
} else {
defer deviceConn.Close()
log.Println("\trequesting device data services…")
services, err := deviceConn.RequestServices()
if err != nil {
log.Printf("WARNING: %s", err.Error())
}
for _, service := range services {
log.Printf("\toffers %s at port %d", service.Name, service.Port)
switch service.Name {
case "StateMap":
stateMapTCPConn, err := device.Dial(service.Port)
defer stateMapTCPConn.Close()
if err != nil {
log.Printf("WARNING: %s", err.Error())
}
stateMapConn, err := stagelinq.NewStateMapConnection(stateMapTCPConn, listener.Token())
if err != nil {
log.Printf("WARNING: %s", err.Error())
}
m := makeStateMap()
for _, stateValue := range stateValues {
stateMapConn.Subscribe(stateValue)
}
var artistName string
var songName string
//var strft string
var count int
var fltft float64
for state := range stateMapConn.StateC() {
count = count + 1
//log.Printf("%s", count)
if state.Name == "/Engine/Deck1/ExternalMixerVolume" {
//strft = fmt.Sprint(state.Value["value"].(float64))
fltft = state.Value["value"].(float64)
//log.Printf("%s %s %q",device.IP.String(), state.Name, strft)
}else if state.Name == "/Engine/Deck1/Track/ArtistName" {
artistName = state.Value["string"].(string)
//log.Printf("%s %s %q",device.IP.String(), state.Name, state.Value["string"])
}else if state.Name == "/Engine/Deck1/Track/SongName"{
songName = state.Value["string"].(string)
//log.Printf("%s %s %q",device.IP.String(), state.Name, state.Value["string"])
//log.Printf("%s %s %q",device.IP.String(), state.Name, songName)
}
m[state.Name] = true
if allStateValuesReceived(m) {
log.Printf("%s", "Broken Out")
break
}
//log.Printf("%s", count)
if count == 12{
//first connect gets all values
if fltft > 0.99{
//The fader is not a zero value and the index is 12 - Quick and Dirty
log.Printf("%s %s %s",device.IP.String(),artistName, songName)
writeFile(artistName + " - " + songName + " ")
}
//count = count + 1
}else if count > 12{
//Already running so just check the fader positino
if fltft > 0.99{
//The fader is not a zero value
log.Printf("%s %s %s",device.IP.String(),artistName, songName)
writeFile(artistName + " - " + songName + " ")
}
}
}
select {
case err := <-stateMapConn.ErrorC():
log.Printf("WARNING: %s", err.Error())
default:
}
stateMapTCPConn.Close()
}
}
log.Println("\tend of list of device data services")
}
}
func writeFile(text string){
file, err := os.OpenFile("/Users/Shared/OBS_Denon/output.txt", os.O_WRONLY|os.O_CREATE, 0666)
file.Truncate(0)
if err != nil {
log.Printf("File does not exists or cannot be created")
os.Exit(1)
}
defer file.Close()
w := bufio.NewWriter(file)
fmt.Fprintf(w, "%v\n", text)
w.Flush()
}
func main() {
//Cleardown TextFile
writeFile("")
listener, err := stagelinq.ListenWithConfiguration(&stagelinq.ListenerConfiguration{
DiscoveryTimeout: timeout,
SoftwareName: appName,
SoftwareVersion: appVersion,
Name: "OBS_Plug",
})
if err != nil {
panic(err)
}
defer listener.Close()
listener.AnnounceEvery(time.Second)
deadline := time.After(timeout)
foundDevices := []*stagelinq.Device{}
log.Printf("Listening for devices for %s", timeout)
var wg sync.WaitGroup
discoveryLoop:
for {
select {
case <-deadline:
break discoveryLoop
default:
device, deviceState, err := listener.Discover(timeout)
if err != nil {
log.Printf("WARNING: %s", err.Error())
continue discoveryLoop
}
if device == nil {
continue
}
// ignore device leaving messages since we do a one-off list
if deviceState != stagelinq.DevicePresent {
continue discoveryLoop
}
// check if we already found this device before
for _, foundDevice := range foundDevices {
if foundDevice.IsEqual(device) {
continue discoveryLoop
}
}
foundDevices = append(foundDevices, device)
log.Printf("%s %q %q %q", device.IP.String(), device.Name, device.SoftwareName, device.SoftwareVersion)
//WE NEED TO ADD A DIFFERENT METHOD HERE TO ITERATE THROUGH THE FOUNDDEVICES ARRAY AND CONNECT TO ALL - SG
//BELOW METHOD ONLY CONNECTS TO A SINGLE DEVICE BASED ON THE FIRST DISCOVERY
//USE OF GO CHANNELS WILL HELP CREATE A MULTI-THREAD
//Structure of a printf command (<declare the types of string>, String A, String B.....etc )
}
}
if foundDevices == nil {
log.Printf("No Devices Found")
} else {
for _, dev := range foundDevices{
if(dev.Name == "OBS_Plug"){
//IGNORE LOCAL SERVICE
}else if(dev.Name == "DN-X1800Prime"){
}else if(dev.Name == "SoundSwitchPC"){
}else{
log.Printf("%s %q",dev.IP.String(), "Found Device")
//writeFile(dev.IP.String())
wg.Add(1)
go func(dev *stagelinq.Device){
connectDevice(dev, listener)
}(dev)
}
}
}
wg.Wait()
log.Printf("Found devices: %d", len(foundDevices))
}