package main import ( "fmt" "math" "github.com/charmbracelet/log" "gitlab.com/gomidi/midi/v2" "github.com/bendahl/uinput" ) type Mapping interface { Is(midi.Message) bool TriggerIfMatch(midi.Message, uinput.Gamepad) error Comment() string } type ButtonMapping struct { comment string midiChannel uint8 midiKey uint8 gamepadKey int } func (m ButtonMapping) Is(msg midi.Message) bool { var channel, key uint8 switch { case msg.GetNoteOn(&channel, &key, nil), msg.GetNoteOff(&channel, &key, nil): return (m.midiChannel == channel && m.midiKey == key) default: return false } } func (m ButtonMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Gamepad) error { if m.Is(msg) { var velocity uint8 msg.GetNoteOn(nil, nil, &velocity) switch msg.Type() { case midi.NoteOnMsg: if velocity != 0 { log.Debug(m.comment, "status", "down") return virtGamepad.ButtonDown(m.gamepadKey) } fallthrough // if reached here, velocity is 0 -> NoteOff case midi.NoteOffMsg: log.Debug(m.comment, "status", "up") return virtGamepad.ButtonUp(m.gamepadKey) default: return fmt.Errorf("Invalid message type triggered ButtonMapping") } } return nil } func (m ButtonMapping) Comment() string { return m.comment } type ControllerAxis int const ( LeftX ControllerAxis = iota LeftY RightX RightY ) type ControlMapping struct { comment string midiChannel uint8 midiController uint8 axis ControllerAxis isSigned bool deadzone float64 } func (m ControlMapping) Is(msg midi.Message) bool { var channel, controller uint8 if msg.GetControlChange(&channel, &controller, nil) { return (m.midiChannel == channel && m.midiController == controller) } else { return false } } func (m ControlMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Gamepad) error { if m.Is(msg) { var ( valueAbsolute uint8 valueNormalised float64 ) msg.GetControlChange(nil, nil, &valueAbsolute) // value is 0-127, normalise valueNormalised = float64(valueAbsolute) / 127 if m.isSigned { valueNormalised *= 2 valueNormalised -= 1 } if math.Abs(valueNormalised) < m.deadzone { valueNormalised = 0 } log.Debug(m.comment, "value", valueNormalised, "deadzone", m.deadzone) switch m.axis { case LeftX: return virtGamepad.LeftStickMoveX(float32(valueNormalised)) case LeftY: return virtGamepad.LeftStickMoveY(float32(valueNormalised)) case RightX: return virtGamepad.RightStickMoveX(float32(valueNormalised)) case RightY: return virtGamepad.RightStickMoveY(float32(valueNormalised)) } } return nil } func (m ControlMapping) Comment() string { return m.comment }