diff --git a/controller.go b/controller.go index b98d048..2698b13 100644 --- a/controller.go +++ b/controller.go @@ -1,25 +1,31 @@ package main import ( - "log" "gitlab.com/gomidi/midi/v2" + "github.com/bendahl/uinput" ) type Controller struct { midiInput *MidiInput mappings []Mapping abortChan chan interface{} + virtGamepad uinput.Gamepad } -func NewController(portName string) (*Controller, error) { +func NewController(portName string, vendorID, productID uint16) (*Controller, error) { midiInput, err := NewMidiInput(portName) if err != nil { return nil, err } + virtGamepad, err := uinput.CreateGamepad("/dev/uinput", []byte(portName), vendorID, productID) + if err != nil { + return nil, err + } + abortChan := make(chan interface{}) - controller := &Controller{midiInput, nil, abortChan} + controller := &Controller{midiInput, nil, abortChan, virtGamepad} go func() { for { @@ -42,12 +48,11 @@ func (c *Controller) AddMapping(mapping Mapping) { func (c Controller) Stop() { c.midiInput.Stop() c.abortChan <- struct{}{} + c.virtGamepad.Close() } func (c Controller) update(msg midi.Message) { for _, mapping := range c.mappings { - if mapping.Is(msg) { - log.Println("Mapping triggered!\n") - } + mapping.TriggerIfMatch(msg, c.virtGamepad) } } diff --git a/go.mod b/go.mod index 9d6322e..9bc0c4c 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module datalore/midi-hid go 1.24.5 -require gitlab.com/gomidi/midi/v2 v2.3.16 +require ( + github.com/bendahl/uinput v1.7.0 + gitlab.com/gomidi/midi/v2 v2.3.16 +) diff --git a/go.sum b/go.sum index 56e5727..988e0f3 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,4 @@ +github.com/bendahl/uinput v1.7.0 h1:nA4fm8Wu8UYNOPykIZm66nkWEyvxzfmJ8YC02PM40jg= +github.com/bendahl/uinput v1.7.0/go.mod h1:Np7w3DINc9wB83p12fTAM3DPPhFnAKP0WTXRqCQJ6Z8= gitlab.com/gomidi/midi/v2 v2.3.16 h1:yufWSENyjnJ4LFQa9BerzUm4E4aLfTyzw5nmnCteO0c= gitlab.com/gomidi/midi/v2 v2.3.16/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw= diff --git a/main.go b/main.go index 97354c1..bea6fca 100644 --- a/main.go +++ b/main.go @@ -5,6 +5,7 @@ import ( "time" "gitlab.com/gomidi/midi/v2" + "github.com/bendahl/uinput" ) func must[T any](obj T, err error) T { @@ -19,12 +20,11 @@ func main() { defer midi.CloseDriver() log.Println("Starting...") - controller := must(NewController("DJControl Inpulse 500 MIDI 1")) - controller.AddMapping(ButtonMapping{1, 7}) // play left - controller.AddMapping(ControlMapping{1, 0}) // volume left + controller := must(NewController("DJControl Inpulse 500 MIDI 1", 0x45e, 0x285)) // mimics xbox 360 controller + defer controller.Stop() + + controller.AddMapping(ButtonMapping{"Play left", 1, 7, uinput.ButtonSouth}) + controller.AddMapping(ControlMapping{"Volume left", 1, 0, LeftY, false}) time.Sleep(time.Second * 20) - log.Println("Stopping...") - controller.Stop() - log.Println("Stopped.") } diff --git a/mapping.go b/mapping.go index 22e2fa0..28d9b01 100644 --- a/mapping.go +++ b/mapping.go @@ -1,16 +1,22 @@ package main import ( + "fmt" + "gitlab.com/gomidi/midi/v2" + "github.com/bendahl/uinput" ) type Mapping interface { Is(midi.Message) bool + TriggerIfMatch(midi.Message, uinput.Gamepad) error } type ButtonMapping struct { + comment string midiChannel uint8 midiKey uint8 + gamepadKey int } func (m ButtonMapping) Is(msg midi.Message) bool { @@ -24,9 +30,36 @@ func (m ButtonMapping) Is(msg midi.Message) bool { } } +func (m ButtonMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Gamepad) error { + if m.Is(msg) { + switch msg.Type() { + case midi.NoteOnMsg: + return virtGamepad.ButtonDown(m.gamepadKey) + case midi.NoteOffMsg: + return virtGamepad.ButtonUp(m.gamepadKey) + default: + return fmt.Errorf("Invalid message type triggered ButtonMapping") + } + } + + return nil +} + +type ControllerAxis int + +const ( + LeftX ControllerAxis = iota + LeftY + RightX + RightY +) + type ControlMapping struct { + comment string midiChannel uint8 midiController uint8 + axis ControllerAxis + isSigned bool } func (m ControlMapping) Is(msg midi.Message) bool { @@ -38,3 +71,34 @@ func (m ControlMapping) Is(msg midi.Message) bool { return false } } + +func (m ControlMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Gamepad) error { + if m.Is(msg) { + var ( + valueAbsolute uint8 + valueNormalised float32 + ) + + msg.GetControlChange(nil, nil, &valueAbsolute) + + // value is 0-127, normalise + valueNormalised = float32(valueAbsolute) / 127 + if m.isSigned { + valueNormalised *= 2 + valueNormalised -= 1 + } + + switch m.axis { + case LeftX: + return virtGamepad.LeftStickMoveX(valueNormalised) + case LeftY: + return virtGamepad.LeftStickMoveY(valueNormalised) + case RightX: + return virtGamepad.RightStickMoveX(valueNormalised) + case RightY: + return virtGamepad.RightStickMoveY(valueNormalised) + } + } + + return nil +}