// // 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 main import ( "fmt" "log" "spreadspace.org/dolmetschctl/pkg/mixer" ) type State int const ( StateNew = iota StateSettling StateSettled ) func (s State) String() string { switch s { case StateNew: return "new" case StateSettling: return "settling" case StateSettled: return "settled" default: return "unknown" } } type Language string func (l Language) String() string { if l == "" { return "none" } return string(l) } type LanguageChannel struct { Num mixer.Channel currentLevel mixer.FaderLevel muted bool } type LanguageChannels struct { main LanguageChannel voice LanguageChannel } type selectLangRequest struct { l Language resultCh chan error } type StateMachine struct { mixer *mixer.Mixer selectLangCh chan selectLangRequest quitCh chan bool exitedCh chan struct{} mixerEventCh chan mixer.Event languages map[Language]LanguageChannels channel2lang map[mixer.Channel]Language currentState State currentLang Language targetLang Language } func (sm *StateMachine) selectLang(l Language) error { if l != "" { if _, exists := sm.languages[l]; !exists { return fmt.Errorf("language '%s' does not exist", l) } } sm.targetLang = l log.Printf("new target language: '%s'", sm.targetLang) return nil } func (sm *StateMachine) reconcile() { if sm.targetLang == sm.currentLang { sm.currentState = StateSettled return } sm.currentState = StateSettling } func (sm *StateMachine) run() { defer close(sm.exitedCh) for { select { case req := <-sm.selectLangCh: req.resultCh <- sm.selectLang(req.l) case ev := <-sm.mixerEventCh: log.Printf("got event from mixer: %v", ev) // TODO: update mixer channel states... case <-sm.quitCh: return } oldState := sm.currentState sm.reconcile() if oldState != sm.currentState { log.Printf("new state: %s", sm.currentState) } } } func NewStateMachine(m *mixer.Mixer) (*StateMachine, error) { sm := &StateMachine{mixer: m} sm.selectLangCh = make(chan selectLangRequest, 10) sm.quitCh = make(chan bool, 1) sm.exitedCh = make(chan struct{}) sm.mixerEventCh = make(chan mixer.Event, 3) sm.languages = make(map[Language]LanguageChannels) sm.channel2lang = make(map[mixer.Channel]Language) sm.currentState = StateNew sm.currentLang = "" sm.targetLang = "" return sm, nil } func (sm *StateMachine) AddLanguage(name Language, main, voice mixer.Channel) error { if name == "none" { return fmt.Errorf("language 'none' is reserved") } if _, exists := sm.languages[name]; exists { return fmt.Errorf("language '%s' already exists", name) } for ch := range []mixer.Channel{main, voice} { if l, exists := sm.languages[name]; exists { return fmt.Errorf("mixer channel %v is already in use by language '%s'", ch, l) } } chMain := LanguageChannel{main, mixer.FaderLevel0db, false} chVoice := LanguageChannel{voice, mixer.FaderLevel0db, false} sm.languages[name] = LanguageChannels{chMain, chVoice} sm.channel2lang[main] = name sm.channel2lang[voice] = name sm.mixer.Subscribe(main, sm.mixerEventCh) sm.mixer.Subscribe(voice, sm.mixerEventCh) return nil } func (sm *StateMachine) Start() { go sm.run() } func (sm *StateMachine) SelectLanguage(l Language) error { resultCh := make(chan error) sm.selectLangCh <- selectLangRequest{l, resultCh} return <-resultCh } func (sm *StateMachine) Shutdown() { select { case sm.quitCh <- true: default: } <-sm.exitedCh }