// // dolmetschctl // // // Copyright (C) 2019 Christian Pointner // // This file is part of dolmetschctl. // // dolmetschctl is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // dolmetschctl is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with dolmetschctl. If not, see . // package mixer import ( "container/list" "errors" "fmt" "log" "net" "strings" "sync" "time" "github.com/scgolang/osc" ) const ( OSCSubscriptionTimeFactor = 0 ) type Channel string type EventType int const ( EventFaderChange EventType = iota EventMute ) func (et EventType) String() string { switch et { case EventFaderChange: return "fader-change" case EventMute: return "mute" default: return "unknown" } } type FaderLevel float32 const ( FaderLevelUnknown = FaderLevel(-1.0) FaderLevelMax = FaderLevel(1.00) FaderLevel0db = FaderLevel(0.75) FaderLevelOff = FaderLevel(0.00) FaderLevelIncrement = FaderLevel(0.01) ) func (fl FaderLevel) String() string { if fl > FaderLevelMax { return "unknown" } val := fmt.Sprintf("%3f", fl) switch fl { case FaderLevelMax: return val + " (max)" case FaderLevel0db: return val + " (0db)" case FaderLevelOff: return val + " (off)" default: return val } } type Mute int const ( MuteUnknown = Mute(-1) MuteUnmuted = Mute(0) MuteMuted = Mute(1) ) func (m Mute) String() string { switch m { case MuteUnmuted: return "unmuted" case MuteMuted: return "muted" default: return "unknown" } } type Event struct { Channel Channel Type EventType Level FaderLevel Mute Mute } func (e Event) String() string { return fmt.Sprintf("Event(%s) for channel '%s': level=%s, muted=%s", e.Type, e.Channel, e.Level, e.Mute) } type subscriber struct { publish chan<- Event unsubscribe <-chan struct{} } type MixerInfo struct { ServerVersion string ServerName string ConsoleModel string ConsoleVersion string } type Mixer struct { config Config client *osc.UDPConn info MixerInfo subscribersLock sync.Mutex subscribers map[Channel]*list.List } func waitForInfo(client *osc.UDPConn, infoCh chan<- MixerInfo, errCh chan<- error) { err := client.Serve(1, osc.PatternMatching{ "/info": osc.Method(func(msg osc.Message) (err error) { mi := MixerInfo{} if len(msg.Arguments) != 4 { return errors.New("invalid number of arguments in /info response") } if mi.ServerVersion, err = msg.Arguments[0].ReadString(); err != nil { return } if mi.ServerName, err = msg.Arguments[1].ReadString(); err != nil { return } if mi.ConsoleModel, err = msg.Arguments[2].ReadString(); err != nil { return } if mi.ConsoleVersion, err = msg.Arguments[3].ReadString(); err != nil { return } infoCh <- mi return nil }), }) errCh <- err close(infoCh) close(errCh) } func NewMixer(c Config) (*Mixer, error) { if c.Port == "" { c.Port = "10023" } m := &Mixer{config: c} server, err := net.ResolveUDPAddr("udp", c.Host+":"+c.Port) if err != nil { return nil, err } m.client, err = osc.DialUDP("udp", nil, server) if err != nil { return nil, err } if err = m.client.Send(osc.Message{Address: "/info"}); err != nil { return nil, err } infoCh := make(chan MixerInfo) errCh := make(chan error) go waitForInfo(m.client, infoCh, errCh) select { case <-time.After(10 * time.Second): return nil, errors.New("timeout") case err := <-errCh: return nil, err case info := <-infoCh: m.info = info } m.subscribers = make(map[Channel]*list.List) return m, nil } func (m *Mixer) String() string { return fmt.Sprintf("Model %s (Firmware-Version: %s)", m.info.ConsoleModel, m.info.ConsoleVersion) } func (m *Mixer) publishEvent(ev Event) { m.subscribersLock.Lock() defer m.subscribersLock.Unlock() subs, exists := m.subscribers[ev.Channel] if exists && subs != nil { var next *list.Element for entry := subs.Front(); entry != nil; entry = next { next = entry.Next() sub, ok := (entry.Value).(subscriber) if !ok { panic(fmt.Sprintf("mixer: subscriber list element value has wrong type: %T", entry.Value)) } select { case <-sub.unsubscribe: // log.Printf("mixer: removing subscriber '%v', because it has unsubscribed", sub.publish) close(sub.publish) subs.Remove(entry) default: select { case sub.publish <- ev: default: // log.Printf("mixer: removing subscriber '%v', because it is not responding", sub.publish) close(sub.publish) subs.Remove(entry) } } } } } type oscDispatcher struct { mixer *Mixer } func (d oscDispatcher) Dispatch(bundle osc.Bundle, exactMatch bool) error { // TODO: X32 seems to not use bundles at the momemnt - ingoring them for now return nil } func (d oscDispatcher) Invoke(msg osc.Message, exactMatch bool) error { addrParts := strings.Split(strings.TrimPrefix(msg.Address, "/"), "/") if len(addrParts) < 4 { return nil } if addrParts[2] != "mix" { return nil } if len(msg.Arguments) != 1 { return nil } ev := Event{Level: FaderLevelUnknown, Mute: MuteUnknown} ev.Channel = Channel(addrParts[0] + "/" + addrParts[1]) switch addrParts[3] { case "on": ev.Type = EventMute arg, err := msg.Arguments[0].ReadInt32() if err != nil { return err } if arg == 0 { ev.Mute = MuteMuted } else { ev.Mute = MuteUnmuted } case "fader": ev.Type = EventFaderChange arg, err := msg.Arguments[0].ReadFloat32() if err != nil { return err } ev.Level = FaderLevel(arg) default: return nil } d.mixer.publishEvent(ev) return nil } func (m *Mixer) Init() error { go func() { for { dispatcher := oscDispatcher{mixer: m} err := m.client.Serve(1, dispatcher) if err != nil { log.Printf("mixer: failed to dispatch message: %v", err) time.Sleep(time.Second) } } }() go func() { for { time.Sleep(5 * time.Second) err := m.client.Send(osc.Message{Address: "/renew"}) if err != nil { log.Printf("mixer: failed to renew subsciptions: %v", err) } } }() return nil } func (m *Mixer) Shutdown() error { if m.client != nil { m.client.Send(osc.Message{Address: "/unsubscribe"}) m.client.Close() m.client = nil } m.subscribersLock.Lock() defer m.subscribersLock.Unlock() for _, subs := range m.subscribers { var next *list.Element for entry := subs.Front(); entry != nil; entry = next { next = entry.Next() sub, ok := (entry.Value).(subscriber) if !ok { panic(fmt.Sprintf("mixer: subscriber list element value has wrong type: %T", entry.Value)) } close(sub.publish) subs.Remove(entry) } } return nil } func (m *Mixer) sendMute(channel Channel, value string) error { msg := osc.Message{Address: "/" + string(channel) + "/mix/on"} msg.Arguments = append(msg.Arguments, osc.String(value)) return m.client.Send(msg) } func (m *Mixer) Mute(ch Channel) error { return m.sendMute(ch, "OFF") } func (m *Mixer) Unmute(ch Channel) error { return m.sendMute(ch, "ON") } func (m *Mixer) SetLevel(channel Channel, level FaderLevel) error { if level > FaderLevelMax { level = FaderLevelMax } msg := osc.Message{Address: "/" + string(channel) + "/mix/fader"} msg.Arguments = append(msg.Arguments, osc.Float(level)) return m.client.Send(msg) } func (m *Mixer) sendSubscribe(channel Channel) (err error) { msg := osc.Message{Address: "/subscribe"} msg.Arguments = append(msg.Arguments, osc.String("/"+string(channel)+"/mix/on"), osc.Int(OSCSubscriptionTimeFactor)) if err = m.client.Send(msg); err != nil { return } msg = osc.Message{Address: "/subscribe"} msg.Arguments = append(msg.Arguments, osc.String("/"+string(channel)+"/mix/fader"), osc.Int(OSCSubscriptionTimeFactor)) if err = m.client.Send(msg); err != nil { return } return } func (m *Mixer) Subscribe(channel Channel, out chan<- Event) (chan<- struct{}, error) { m.subscribersLock.Lock() defer m.subscribersLock.Unlock() subs, exists := m.subscribers[channel] if !exists { subs = list.New() m.subscribers[channel] = subs } if err := m.sendSubscribe(channel); err != nil { return nil, err } // log.Printf("mixer: subscribing '%v' to events for channel: %v", out, ch) unsubscribe := make(chan struct{}) subs.PushBack(subscriber{publish: out, unsubscribe: unsubscribe}) return unsubscribe, nil }