diff --git a/config.go b/config.go index 62627ab..26f9d13 100644 --- a/config.go +++ b/config.go @@ -9,10 +9,12 @@ import ( "github.com/charmbracelet/log" ) +// Config is the root type of a config, consisting of an arbitrary number of controller configs. type Config struct { Controller []ControllerConfig `yaml:"controller"` } +// A ControllerConfig represents the data needed to later construct a Controller object. type ControllerConfig struct { PortName string `yaml:"portName"` VendorID uint16 `yaml:"vendorID"` @@ -20,6 +22,7 @@ type ControllerConfig struct { Mappings []MappingConfig `yaml:"mappings"` } +// A MappingConfig consists of all data possibly needed to construct a mapping, both button and control. type MappingConfig struct { Comment string `yaml:"comment"` Type MappingType `yaml:"type"` @@ -61,6 +64,8 @@ const ( AxisRightY AxisName = "right-y" ) +// ParseConfig takes the path to a config file and returns the parsed Config object, +// or an error if thrown. func ParseConfig(path string) (Config, error) { var config Config @@ -77,6 +82,8 @@ func ParseConfig(path string) (Config, error) { return config, nil } +// Construct iterates over all ControllerConfigs and constructs the Controller objects. +// In case of a failure, it aborts and returns an error. func (config Config) Construct() (ControllerList, error) { var controllerList ControllerList @@ -92,6 +99,9 @@ func (config Config) Construct() (ControllerList, error) { return controllerList, nil } +// Construct builds a Controller object and its corresponding mappings. +// Aborts and returns an error if the midi port was not found or one of +// the Mappings is invalid. func (cc ControllerConfig) Construct() (*Controller, error) { actualController, err := NewController(cc.PortName, cc.VendorID, cc.ProductID) if err != nil { @@ -110,6 +120,7 @@ func (cc ControllerConfig) Construct() (*Controller, error) { return actualController, nil } +// Construct builds the Mapping object. Returns an error if config is invalid. func (mc MappingConfig) Construct() (Mapping, error) { switch mc.Type { case ButtonMappingType: @@ -135,6 +146,8 @@ func (mc MappingConfig) Construct() (Mapping, error) { } } +// Construct converts a ButtonName to its corresponding key code, or returns an error if the +// name is unknown. func (bn ButtonName) Construct() (int, error) { switch bn { case ButtonNorth: @@ -174,6 +187,8 @@ func (bn ButtonName) Construct() (int, error) { } } +// Construct converts an AxisName into the internal representation for a ControllerAxis. +// Returns an error if AxisName is invalid. func (an AxisName) Construct() (ControllerAxis, error) { switch an { case AxisLeftX: diff --git a/controller.go b/controller.go index 508da79..4f974e7 100644 --- a/controller.go +++ b/controller.go @@ -6,14 +6,18 @@ import ( "github.com/bendahl/uinput" ) +// A ControllerList is a list of controllers. Duh. type ControllerList []*Controller +// Stop iterates over all Controller objects and Stops their update loops and MIDI connections. +// Always call this for a clean shutdown. Meant to be deferred. func (cl ControllerList) Stop() { for _, controller := range cl { controller.Stop() } } +// A Controller object manages the translation from MIDI to uinput. type Controller struct { midiInput *MidiInput mappings []Mapping @@ -21,6 +25,8 @@ type Controller struct { virtGamepad uinput.Gamepad } +// NewController builds a new Controller object reading from the MIDI port specified by portName, +// and registers a virtual uinput-Gamepad using vendorID and productID. func NewController(portName string, vendorID, productID uint16) (*Controller, error) { if vendorID == 0 && productID == 0 { // if no IDs were defined, imitate XBox 360 controller @@ -55,10 +61,12 @@ func NewController(portName string, vendorID, productID uint16) (*Controller, er return controller, nil } +// AddMapping adds a mapping to the Controller. func (c *Controller) AddMapping(mapping Mapping) { c.mappings = append(c.mappings, mapping) } +// Stop quits the update loop and terminates all corresponding connections. func (c Controller) Stop() { c.midiInput.Stop() c.abortChan <- struct{}{} diff --git a/mapping.go b/mapping.go index a5b9ebe..07c1a9e 100644 --- a/mapping.go +++ b/mapping.go @@ -9,12 +9,14 @@ import ( "github.com/bendahl/uinput" ) +// A Mapping is an interface for all types of Mappings. type Mapping interface { Is(midi.Message) bool TriggerIfMatch(midi.Message, uinput.Gamepad) error Comment() string } +// A ButtonMapping maps a MIDI Note to a gamepad button. type ButtonMapping struct { comment string midiChannel uint8 @@ -22,6 +24,7 @@ type ButtonMapping struct { gamepadKey int } +// Is checks if the MIDI message msg triggers this Mapping, without actually triggering it. func (m ButtonMapping) Is(msg midi.Message) bool { var channel, key uint8 @@ -33,6 +36,8 @@ func (m ButtonMapping) Is(msg midi.Message) bool { } } +// TriggerIfMatch checks if the MIDI message msg triggers this Mapping, and if so, +// sends the corresponding input to virtGamepad. func (m ButtonMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Gamepad) error { if m.Is(msg) { var velocity uint8 @@ -55,6 +60,7 @@ func (m ButtonMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Gamep return nil } +// Comment returns the Mappings comment. func (m ButtonMapping) Comment() string { return m.comment } @@ -77,6 +83,7 @@ type ControlMapping struct { deadzone float64 } +// Is checks if the MIDI message msg triggers this Mapping, without actually triggering it. func (m ControlMapping) Is(msg midi.Message) bool { var channel, controller uint8 @@ -87,6 +94,8 @@ func (m ControlMapping) Is(msg midi.Message) bool { } } +// TriggerIfMatch checks if the MIDI message msg triggers this Mapping, and if so, +// sends the corresponding input to virtGamepad. func (m ControlMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Gamepad) error { if m.Is(msg) { var ( @@ -124,6 +133,7 @@ func (m ControlMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Game return nil } +// Comment returns the Mappings comment. func (m ControlMapping) Comment() string { return m.comment } diff --git a/midi.go b/midi.go index 47ee925..923d74e 100644 --- a/midi.go +++ b/midi.go @@ -6,12 +6,16 @@ import ( _ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv" ) +// A MidiInput represents a MIDI input and provides a channel to read incoming +// Messages, as well as a Stop function to terminate the connection. type MidiInput struct { input drivers.In Messages chan midi.Message Stop func() } +// NewMidiInput initialises a MidiInput object connected to the MIDI port specified +// by portName. Retuns an error if the connection fails. func NewMidiInput(portName string) (*MidiInput, error) { input, err := midi.FindInPort(portName) if err != nil {