feat(GamePad): Implemented mappings

This commit is contained in:
2025-08-04 14:36:35 +02:00
parent 3942b6d8b2
commit 945a78c463
5 changed files with 87 additions and 13 deletions

View File

@@ -1,25 +1,31 @@
package main package main
import ( import (
"log"
"gitlab.com/gomidi/midi/v2" "gitlab.com/gomidi/midi/v2"
"github.com/bendahl/uinput"
) )
type Controller struct { type Controller struct {
midiInput *MidiInput midiInput *MidiInput
mappings []Mapping mappings []Mapping
abortChan chan interface{} 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) midiInput, err := NewMidiInput(portName)
if err != nil { if err != nil {
return nil, err return nil, err
} }
virtGamepad, err := uinput.CreateGamepad("/dev/uinput", []byte(portName), vendorID, productID)
if err != nil {
return nil, err
}
abortChan := make(chan interface{}) abortChan := make(chan interface{})
controller := &Controller{midiInput, nil, abortChan} controller := &Controller{midiInput, nil, abortChan, virtGamepad}
go func() { go func() {
for { for {
@@ -42,12 +48,11 @@ func (c *Controller) AddMapping(mapping Mapping) {
func (c Controller) Stop() { func (c Controller) Stop() {
c.midiInput.Stop() c.midiInput.Stop()
c.abortChan <- struct{}{} c.abortChan <- struct{}{}
c.virtGamepad.Close()
} }
func (c Controller) update(msg midi.Message) { func (c Controller) update(msg midi.Message) {
for _, mapping := range c.mappings { for _, mapping := range c.mappings {
if mapping.Is(msg) { mapping.TriggerIfMatch(msg, c.virtGamepad)
log.Println("Mapping triggered!\n")
}
} }
} }

5
go.mod
View File

@@ -2,4 +2,7 @@ module datalore/midi-hid
go 1.24.5 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
)

2
go.sum
View File

@@ -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 h1:yufWSENyjnJ4LFQa9BerzUm4E4aLfTyzw5nmnCteO0c=
gitlab.com/gomidi/midi/v2 v2.3.16/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw= gitlab.com/gomidi/midi/v2 v2.3.16/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw=

12
main.go
View File

@@ -5,6 +5,7 @@ import (
"time" "time"
"gitlab.com/gomidi/midi/v2" "gitlab.com/gomidi/midi/v2"
"github.com/bendahl/uinput"
) )
func must[T any](obj T, err error) T { func must[T any](obj T, err error) T {
@@ -19,12 +20,11 @@ func main() {
defer midi.CloseDriver() defer midi.CloseDriver()
log.Println("Starting...") log.Println("Starting...")
controller := must(NewController("DJControl Inpulse 500 MIDI 1")) controller := must(NewController("DJControl Inpulse 500 MIDI 1", 0x45e, 0x285)) // mimics xbox 360 controller
controller.AddMapping(ButtonMapping{1, 7}) // play left defer controller.Stop()
controller.AddMapping(ControlMapping{1, 0}) // volume left
controller.AddMapping(ButtonMapping{"Play left", 1, 7, uinput.ButtonSouth})
controller.AddMapping(ControlMapping{"Volume left", 1, 0, LeftY, false})
time.Sleep(time.Second * 20) time.Sleep(time.Second * 20)
log.Println("Stopping...")
controller.Stop()
log.Println("Stopped.")
} }

View File

@@ -1,16 +1,22 @@
package main package main
import ( import (
"fmt"
"gitlab.com/gomidi/midi/v2" "gitlab.com/gomidi/midi/v2"
"github.com/bendahl/uinput"
) )
type Mapping interface { type Mapping interface {
Is(midi.Message) bool Is(midi.Message) bool
TriggerIfMatch(midi.Message, uinput.Gamepad) error
} }
type ButtonMapping struct { type ButtonMapping struct {
comment string
midiChannel uint8 midiChannel uint8
midiKey uint8 midiKey uint8
gamepadKey int
} }
func (m ButtonMapping) Is(msg midi.Message) bool { 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 { type ControlMapping struct {
comment string
midiChannel uint8 midiChannel uint8
midiController uint8 midiController uint8
axis ControllerAxis
isSigned bool
} }
func (m ControlMapping) Is(msg midi.Message) bool { func (m ControlMapping) Is(msg midi.Message) bool {
@@ -38,3 +71,34 @@ func (m ControlMapping) Is(msg midi.Message) bool {
return false 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
}