housekeeping: Refactored API to separate packages

This commit is contained in:
2025-08-09 17:43:55 +02:00
parent ce0f466a0e
commit ae28a4d385
5 changed files with 47 additions and 43 deletions

View File

@@ -1,9 +1,11 @@
package main package config
import ( import (
"fmt" "fmt"
"os" "os"
"git.datalore.sh/datalore/midi-hid/translation"
"github.com/goccy/go-yaml" "github.com/goccy/go-yaml"
"github.com/bendahl/uinput" "github.com/bendahl/uinput"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
@@ -84,8 +86,8 @@ func ParseConfig(path string) (Config, error) {
// Construct iterates over all ControllerConfigs and constructs the Controller objects. // Construct iterates over all ControllerConfigs and constructs the Controller objects.
// In case of a failure, it aborts and returns an error. // In case of a failure, it aborts and returns an error.
func (config Config) Construct() (ControllerList, error) { func (config Config) Construct() (translation.ControllerList, error) {
var controllerList ControllerList var controllerList translation.ControllerList
for _, controllerConfig := range config.Controller { for _, controllerConfig := range config.Controller {
actualController, err := controllerConfig.Construct() actualController, err := controllerConfig.Construct()
@@ -102,8 +104,8 @@ func (config Config) Construct() (ControllerList, error) {
// Construct builds a Controller object and its corresponding mappings. // Construct builds a Controller object and its corresponding mappings.
// Aborts and returns an error if the midi port was not found or one of // Aborts and returns an error if the midi port was not found or one of
// the Mappings is invalid. // the Mappings is invalid.
func (cc ControllerConfig) Construct() (*Controller, error) { func (cc ControllerConfig) Construct() (*translation.Controller, error) {
actualController, err := NewController(cc.PortName, cc.VendorID, cc.ProductID) actualController, err := translation.NewController(cc.PortName, cc.VendorID, cc.ProductID)
if err != nil { if err != nil {
return actualController, err return actualController, err
} }
@@ -121,28 +123,28 @@ func (cc ControllerConfig) Construct() (*Controller, error) {
} }
// Construct builds the Mapping object. Returns an error if config is invalid. // Construct builds the Mapping object. Returns an error if config is invalid.
func (mc MappingConfig) Construct() (Mapping, error) { func (mc MappingConfig) Construct() (translation.Mapping, error) {
switch mc.Type { switch mc.Type {
case ButtonMappingType: case ButtonMappingType:
button, err := mc.Button.Construct() button, err := mc.Button.Construct()
if err != nil { if err != nil {
return ButtonMapping{}, err return translation.ButtonMapping{}, err
} }
log.Debug("Parsed button mapping", "comment", mc.Comment, "midiChannel", mc.MidiChannel, "midiKey", mc.MidiKey, "button", button) log.Debug("Parsed button mapping", "comment", mc.Comment, "midiChannel", mc.MidiChannel, "midiKey", mc.MidiKey, "button", button)
return ButtonMapping{mc.Comment, mc.MidiChannel, mc.MidiKey, button}, nil return translation.ButtonMapping{mc.Comment, mc.MidiChannel, mc.MidiKey, button}, nil
case ControlMappingType: case ControlMappingType:
axis, err := mc.Axis.Construct() axis, err := mc.Axis.Construct()
if err != nil { if err != nil {
return ControlMapping{}, err return translation.ControlMapping{}, err
} }
log.Debug("Parsed control mapping", "comment", mc.Comment, "midiChannel", mc.MidiChannel, "midiController", mc.MidiController, "axis", axis, "isSigned", mc.IsSigned, "deadzone", mc.Deadzone) log.Debug("Parsed control mapping", "comment", mc.Comment, "midiChannel", mc.MidiChannel, "midiController", mc.MidiController, "axis", axis, "isSigned", mc.IsSigned, "deadzone", mc.Deadzone)
return ControlMapping{mc.Comment, mc.MidiChannel, mc.MidiController, axis, mc.IsSigned, mc.Deadzone}, nil return translation.ControlMapping{mc.Comment, mc.MidiChannel, mc.MidiController, axis, mc.IsSigned, mc.Deadzone}, nil
default: default:
return ButtonMapping{}, fmt.Errorf("Invalid mapping type") return translation.ButtonMapping{}, fmt.Errorf("Invalid mapping type")
} }
} }
@@ -189,16 +191,16 @@ func (bn ButtonName) Construct() (int, error) {
// Construct converts an AxisName into the internal representation for a ControllerAxis. // Construct converts an AxisName into the internal representation for a ControllerAxis.
// Returns an error if AxisName is invalid. // Returns an error if AxisName is invalid.
func (an AxisName) Construct() (ControllerAxis, error) { func (an AxisName) Construct() (translation.ControllerAxis, error) {
switch an { switch an {
case AxisLeftX: case AxisLeftX:
return LeftX, nil return translation.LeftX, nil
case AxisLeftY: case AxisLeftY:
return LeftY, nil return translation.LeftY, nil
case AxisRightX: case AxisRightX:
return RightX, nil return translation.RightX, nil
case AxisRightY: case AxisRightY:
return RightY, nil return translation.RightY, nil
default: default:
return -1, fmt.Errorf("Invalid axis name \"%s\"", an) return -1, fmt.Errorf("Invalid axis name \"%s\"", an)
} }

View File

@@ -5,6 +5,8 @@ import (
"os" "os"
"os/signal" "os/signal"
"git.datalore.sh/datalore/midi-hid/config"
"github.com/charmbracelet/log" "github.com/charmbracelet/log"
"gitlab.com/gomidi/midi/v2" "gitlab.com/gomidi/midi/v2"
) )
@@ -34,8 +36,8 @@ func main() {
} }
log.Info("Starting...") log.Info("Starting...")
config := must(ParseConfig(configPath)) conf := must(config.ParseConfig(configPath))
controllerList := must(config.Construct()) controllerList := must(conf.Construct())
defer controllerList.Stop() defer controllerList.Stop()
// wait for SIGINT // wait for SIGINT

View File

@@ -1,4 +1,4 @@
package main package translation
import ( import (
"github.com/charmbracelet/log" "github.com/charmbracelet/log"

View File

@@ -1,4 +1,4 @@
package main package translation
import ( import (
"fmt" "fmt"
@@ -18,10 +18,10 @@ type Mapping interface {
// A ButtonMapping maps a MIDI Note to a gamepad button. // A ButtonMapping maps a MIDI Note to a gamepad button.
type ButtonMapping struct { type ButtonMapping struct {
comment string CommentStr string
midiChannel uint8 MidiChannel uint8
midiKey uint8 MidiKey uint8
gamepadKey int GamepadKey int
} }
// Is checks if the MIDI message msg triggers this Mapping, without actually triggering it. // Is checks if the MIDI message msg triggers this Mapping, without actually triggering it.
@@ -30,7 +30,7 @@ func (m ButtonMapping) Is(msg midi.Message) bool {
switch { switch {
case msg.GetNoteOn(&channel, &key, nil), msg.GetNoteOff(&channel, &key, nil): case msg.GetNoteOn(&channel, &key, nil), msg.GetNoteOff(&channel, &key, nil):
return (m.midiChannel == channel && m.midiKey == key) return (m.MidiChannel == channel && m.MidiKey == key)
default: default:
return false return false
} }
@@ -45,13 +45,13 @@ func (m ButtonMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Gamep
switch msg.Type() { switch msg.Type() {
case midi.NoteOnMsg: case midi.NoteOnMsg:
if velocity != 0 { if velocity != 0 {
log.Debug(m.comment, "status", "down") log.Debug(m.CommentStr, "status", "down")
return virtGamepad.ButtonDown(m.gamepadKey) return virtGamepad.ButtonDown(m.GamepadKey)
} }
fallthrough // if reached here, velocity is 0 -> NoteOff fallthrough // if reached here, velocity is 0 -> NoteOff
case midi.NoteOffMsg: case midi.NoteOffMsg:
log.Debug(m.comment, "status", "up") log.Debug(m.CommentStr, "status", "up")
return virtGamepad.ButtonUp(m.gamepadKey) return virtGamepad.ButtonUp(m.GamepadKey)
default: default:
return fmt.Errorf("Invalid message type triggered ButtonMapping") return fmt.Errorf("Invalid message type triggered ButtonMapping")
} }
@@ -62,7 +62,7 @@ func (m ButtonMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Gamep
// Comment returns the Mappings comment. // Comment returns the Mappings comment.
func (m ButtonMapping) Comment() string { func (m ButtonMapping) Comment() string {
return m.comment return m.CommentStr
} }
type ControllerAxis int type ControllerAxis int
@@ -75,12 +75,12 @@ const (
) )
type ControlMapping struct { type ControlMapping struct {
comment string CommentStr string
midiChannel uint8 MidiChannel uint8
midiController uint8 MidiController uint8
axis ControllerAxis Axis ControllerAxis
isSigned bool IsSigned bool
deadzone float64 Deadzone float64
} }
// Is checks if the MIDI message msg triggers this Mapping, without actually triggering it. // Is checks if the MIDI message msg triggers this Mapping, without actually triggering it.
@@ -88,7 +88,7 @@ func (m ControlMapping) Is(msg midi.Message) bool {
var channel, controller uint8 var channel, controller uint8
if msg.GetControlChange(&channel, &controller, nil) { if msg.GetControlChange(&channel, &controller, nil) {
return (m.midiChannel == channel && m.midiController == controller) return (m.MidiChannel == channel && m.MidiController == controller)
} else { } else {
return false return false
} }
@@ -107,18 +107,18 @@ func (m ControlMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Game
// value is 0-127, normalise // value is 0-127, normalise
valueNormalised = float64(valueAbsolute) / 127 valueNormalised = float64(valueAbsolute) / 127
if m.isSigned { if m.IsSigned {
valueNormalised *= 2 valueNormalised *= 2
valueNormalised -= 1 valueNormalised -= 1
} }
if math.Abs(valueNormalised) < m.deadzone { if math.Abs(valueNormalised) < m.Deadzone {
valueNormalised = 0 valueNormalised = 0
} }
log.Debug(m.comment, "value", valueNormalised, "deadzone", m.deadzone) log.Debug(m.CommentStr, "value", valueNormalised, "deadzone", m.Deadzone)
switch m.axis { switch m.Axis {
case LeftX: case LeftX:
return virtGamepad.LeftStickMoveX(float32(valueNormalised)) return virtGamepad.LeftStickMoveX(float32(valueNormalised))
case LeftY: case LeftY:
@@ -135,5 +135,5 @@ func (m ControlMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Game
// Comment returns the Mappings comment. // Comment returns the Mappings comment.
func (m ControlMapping) Comment() string { func (m ControlMapping) Comment() string {
return m.comment return m.CommentStr
} }

View File

@@ -1,4 +1,4 @@
package main package translation
import ( import (
"gitlab.com/gomidi/midi/v2" "gitlab.com/gomidi/midi/v2"