Files
midi-hid/translation/mapping.go

140 lines
3.3 KiB
Go

package translation
import (
"fmt"
"math"
"github.com/charmbracelet/log"
"gitlab.com/gomidi/midi/v2"
"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 {
CommentStr string
MidiChannel uint8
MidiKey uint8
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
switch {
case msg.GetNoteOn(&channel, &key, nil), msg.GetNoteOff(&channel, &key, nil):
return (m.MidiChannel == channel && m.MidiKey == key)
default:
return false
}
}
// 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
msg.GetNoteOn(nil, nil, &velocity)
switch msg.Type() {
case midi.NoteOnMsg:
if velocity != 0 {
log.Debug(m.CommentStr, "status", "down")
return virtGamepad.ButtonDown(m.GamepadKey)
}
fallthrough // if reached here, velocity is 0 -> NoteOff
case midi.NoteOffMsg:
log.Debug(m.CommentStr, "status", "up")
return virtGamepad.ButtonUp(m.GamepadKey)
default:
return fmt.Errorf("Invalid message type triggered ButtonMapping")
}
}
return nil
}
// Comment returns the Mappings comment.
func (m ButtonMapping) Comment() string {
return m.CommentStr
}
type ControllerAxis int
const (
LeftX ControllerAxis = iota
LeftY
RightX
RightY
)
type ControlMapping struct {
CommentStr string
MidiChannel uint8
MidiController uint8
Axis ControllerAxis
IsSigned bool
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
if msg.GetControlChange(&channel, &controller, nil) {
return (m.MidiChannel == channel && m.MidiController == controller)
} else {
return false
}
}
// 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 (
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.CommentStr, "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
}
// Comment returns the Mappings comment.
func (m ControlMapping) Comment() string {
return m.CommentStr
}