Merge branch 'develop' for 0.3.0
This commit is contained in:
4
LICENSE
4
LICENSE
@@ -208,7 +208,7 @@ If you develop a new program, and you want it to be of the greatest possible use
|
|||||||
|
|
||||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
|
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
gpl-test
|
midi-hid
|
||||||
Copyright (C) 2025 datalore
|
Copyright (C) 2025 datalore
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||||
@@ -221,7 +221,7 @@ Also add information on how to contact you by electronic and paper mail.
|
|||||||
|
|
||||||
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
|
If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
gpl-test Copyright (C) 2025 datalore
|
midi-hid Copyright (C) 2025 datalore
|
||||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
|
This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
33
README.md
33
README.md
@@ -7,19 +7,44 @@ This software allows mapping and translating of MIDI commands to HID inputs on L
|
|||||||
Install with
|
Install with
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go install git.datalore.sh/datalore/midi-hid@latest
|
go install github.com/d4t4l0r3/midi-hid@latest
|
||||||
```
|
```
|
||||||
|
|
||||||
and run it with `midi-hid`. If no config file is specified, it reads `~/.config/midi-hid/config.yaml`.
|
and run it with `midi-hid`. If no config file is specified, it reads `~/.config/midi-hid/config.yaml`.
|
||||||
Every configured midi controller will be represented by a virtual gamepad, and the inputs will be translated until SIGINT is received.
|
Every configured midi controller will be represented by a virtual gamepad, and the inputs will be translated until SIGINT is received.
|
||||||
|
|
||||||
See the provided example config on how to configure your controller, it should be pretty self-explanatory.
|
## Configuration
|
||||||
|
|
||||||
## Known issues
|
See `example-config.yaml` For an annotated configuration file.
|
||||||
|
The valid names for buttons are:
|
||||||
|
|
||||||
The midi library used seems to recognise NoteOff messages as NoteOn messages. However, they can still be recognised by checking the velocity, which is always 0 in NoteOff messages. A workaround has been implemented.
|
| Button name | Description |
|
||||||
|
| ------------ | -------------------------------- |
|
||||||
|
| `north` | E.g. Y on XBox or triangle on PS |
|
||||||
|
| `east` | E.g. B on XBox or circle on PS |
|
||||||
|
| `south` | E.g. A on XBox or X on PS |
|
||||||
|
| `west` | E.g. X on XBox or square on PS |
|
||||||
|
| `l1` | Left bumper |
|
||||||
|
| `l2` | Left trigger |
|
||||||
|
| `l3` | Left stick pressed down |
|
||||||
|
| `r1` | Right bumper |
|
||||||
|
| `r2` | Right trigger |
|
||||||
|
| `r3` | Right stick pressed down |
|
||||||
|
| `select` | Select, or Back on XBox |
|
||||||
|
| `start` | Start button |
|
||||||
|
| `dpad-up` | Directional pad up |
|
||||||
|
| `dpad-down` | Directional pad down |
|
||||||
|
| `dpad-left` | Directional pad left |
|
||||||
|
| `dpad-right` | Directional pad right |
|
||||||
|
|
||||||
|
Valid axis are `left-x`, `left-y`, `right-x` and `right-y`.
|
||||||
|
|
||||||
|
### Finding the MIDI channel and note / controller
|
||||||
|
|
||||||
|
Many vendors provide official documentation on the MIDI commands sent by their devices, but if you are unable to find them, you can use `aseqdump -p <port>` to print all MIDI messages sent by your device. Simply interact with the controls you want to map and you will see the corresponding messages.
|
||||||
|
|
||||||
## Third-party libraries
|
## Third-party libraries
|
||||||
|
|
||||||
- <https://github.com/bendahl/uinput>
|
- <https://github.com/bendahl/uinput>
|
||||||
- <https://gitlab.com/gomidi/midi>
|
- <https://gitlab.com/gomidi/midi>
|
||||||
|
- <https://github.com/charmbracelet/log>
|
||||||
|
49
config.yaml
49
config.yaml
@@ -1,49 +0,0 @@
|
|||||||
controller:
|
|
||||||
- portName: DJControl Inpulse 500 MIDI 1
|
|
||||||
vendorID: 0x45e
|
|
||||||
productID: 0x285
|
|
||||||
mappings:
|
|
||||||
- comment: Play left
|
|
||||||
type: button
|
|
||||||
midiChannel: 1
|
|
||||||
midiKey: 7
|
|
||||||
button: west
|
|
||||||
- comment: Hotcue 1 left
|
|
||||||
type: button
|
|
||||||
midiChannel: 6
|
|
||||||
midiKey: 0
|
|
||||||
button: north
|
|
||||||
- comment: Hotcue 5 left
|
|
||||||
type: button
|
|
||||||
midiChannel: 6
|
|
||||||
midiKey: 4
|
|
||||||
button: south
|
|
||||||
- comment: Play right
|
|
||||||
type: button
|
|
||||||
midiChannel: 2
|
|
||||||
midiKey: 7
|
|
||||||
button: east
|
|
||||||
- comment: Volume left
|
|
||||||
type: control
|
|
||||||
midiChannel: 1
|
|
||||||
midiController: 0
|
|
||||||
axis: left-y
|
|
||||||
- comment: Volume right
|
|
||||||
type: control
|
|
||||||
midiChannel: 2
|
|
||||||
midiController: 0
|
|
||||||
axis: right-y
|
|
||||||
- comment: Filter left
|
|
||||||
type: control
|
|
||||||
midiChannel: 1
|
|
||||||
midiController: 1
|
|
||||||
axis: left-x
|
|
||||||
isSigned: true
|
|
||||||
deadzone: 0.01
|
|
||||||
- comment: Filter right
|
|
||||||
type: control
|
|
||||||
midiChannel: 2
|
|
||||||
midiController: 1
|
|
||||||
axis: right-x
|
|
||||||
isSigned: true
|
|
||||||
deadzone: 0.01
|
|
@@ -4,7 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"git.datalore.sh/datalore/midi-hid/translation"
|
"github.com/d4t4l0r3/midi-hid/translation"
|
||||||
|
|
||||||
"github.com/goccy/go-yaml"
|
"github.com/goccy/go-yaml"
|
||||||
"github.com/bendahl/uinput"
|
"github.com/bendahl/uinput"
|
||||||
@@ -32,6 +32,7 @@ type MappingConfig struct {
|
|||||||
MidiKey uint8 `yaml:"midiKey"`
|
MidiKey uint8 `yaml:"midiKey"`
|
||||||
MidiController uint8 `yaml:"midiController"`
|
MidiController uint8 `yaml:"midiController"`
|
||||||
Button ButtonName `yaml:"button"`
|
Button ButtonName `yaml:"button"`
|
||||||
|
ButtonNegative ButtonName `yaml:"buttonNegative"`
|
||||||
Axis AxisName `yaml:"axis"`
|
Axis AxisName `yaml:"axis"`
|
||||||
IsSigned bool `yaml:"isSigned"`
|
IsSigned bool `yaml:"isSigned"`
|
||||||
Deadzone float64 `yaml:"deadzone"`
|
Deadzone float64 `yaml:"deadzone"`
|
||||||
@@ -44,6 +45,7 @@ type AxisName string
|
|||||||
const (
|
const (
|
||||||
ButtonMappingType MappingType = "button"
|
ButtonMappingType MappingType = "button"
|
||||||
ControlMappingType MappingType = "control"
|
ControlMappingType MappingType = "control"
|
||||||
|
EncoderMappingType MappingType = "encoder"
|
||||||
ButtonNorth ButtonName = "north"
|
ButtonNorth ButtonName = "north"
|
||||||
ButtonEast ButtonName = "east"
|
ButtonEast ButtonName = "east"
|
||||||
ButtonSouth ButtonName = "south"
|
ButtonSouth ButtonName = "south"
|
||||||
@@ -134,6 +136,20 @@ func (mc MappingConfig) Construct() (translation.Mapping, error) {
|
|||||||
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 translation.ButtonMapping{mc.Comment, mc.MidiChannel, mc.MidiKey, button}, nil
|
return translation.ButtonMapping{mc.Comment, mc.MidiChannel, mc.MidiKey, button}, nil
|
||||||
|
case EncoderMappingType:
|
||||||
|
button, err := mc.Button.Construct()
|
||||||
|
if err != nil {
|
||||||
|
return translation.EncoderMapping{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonNegative, err := mc.ButtonNegative.Construct()
|
||||||
|
if err != nil {
|
||||||
|
return translation.EncoderMapping{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Parsed encoder mapping", "comment", mc.Comment, "midiChannel", mc.MidiChannel, "midiController", mc.MidiController, "button", button, "buttonNegative", buttonNegative)
|
||||||
|
|
||||||
|
return translation.EncoderMapping{mc.Comment, mc.MidiChannel, mc.MidiController, button, buttonNegative}, nil
|
||||||
case ControlMappingType:
|
case ControlMappingType:
|
||||||
axis, err := mc.Axis.Construct()
|
axis, err := mc.Axis.Construct()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
22
example-config.yaml
Normal file
22
example-config.yaml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
controller:
|
||||||
|
- portName: DJControl Inpulse 500 MIDI 1 # name of the MIDI port to read from. You can find it with aseqdump -l
|
||||||
|
vendorID: 0x45e # vendor ID for the virtual gamepad. Default is 0x45e. See https://gist.github.com/nondebug/aec93dff7f0f1969f4cc2291b24a3171
|
||||||
|
productID: 0x285 # product ID for the virtual gamepad. Default is 0x285. See https://gist.github.com/nondebug/aec93dff7f0f1969f4cc2291b24a3171
|
||||||
|
mappings:
|
||||||
|
- comment: Play left # This is optional, but it can increase comprehensibility. Will also be used in debug output.
|
||||||
|
type: button # Either button or control. A button can be pressed or released, a control represents a range of values.
|
||||||
|
midiChannel: 1 # MIDI channel to listen to
|
||||||
|
midiKey: 7 # MIDI note representing the button
|
||||||
|
button: west # Name of the gamepad button this should be mapped to. For valid names, see README.md
|
||||||
|
- comment: Filter left
|
||||||
|
type: control
|
||||||
|
midiChannel: 1
|
||||||
|
midiController: 1 # MIDI controller representing the physical control
|
||||||
|
axis: left-x # Axis on the virtual gamepad this should be mapped to. For valid names, see README.md
|
||||||
|
isSigned: true # Whether the controller axis should range from -1 to 1 or from 0 to 1. Default is false
|
||||||
|
deadzone: 0.01 # Number between 0 and 1. In this case, all values between -0.01 and 0.01 will be clamped to 0. Default is 0.0
|
||||||
|
- comment: Volume left
|
||||||
|
type: control
|
||||||
|
midiChannel: 1
|
||||||
|
midiController: 0
|
||||||
|
axis: left-y
|
2
go.mod
2
go.mod
@@ -1,4 +1,4 @@
|
|||||||
module git.datalore.sh/datalore/midi-hid
|
module github.com/d4t4l0r3/midi-hid
|
||||||
|
|
||||||
go 1.24.5
|
go 1.24.5
|
||||||
|
|
||||||
|
2
main.go
2
main.go
@@ -5,7 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
|
||||||
"git.datalore.sh/datalore/midi-hid/config"
|
"github.com/d4t4l0r3/midi-hid/config"
|
||||||
|
|
||||||
"github.com/charmbracelet/log"
|
"github.com/charmbracelet/log"
|
||||||
"gitlab.com/gomidi/midi/v2"
|
"gitlab.com/gomidi/midi/v2"
|
||||||
|
@@ -65,6 +65,53 @@ func (m ButtonMapping) Comment() string {
|
|||||||
return m.CommentStr
|
return m.CommentStr
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// An EncoderMapping maps a MIDI Controller to two buttons.
|
||||||
|
type EncoderMapping struct {
|
||||||
|
CommentStr string
|
||||||
|
MidiChannel uint8
|
||||||
|
MidiController uint8
|
||||||
|
GamepadKeyPositive int
|
||||||
|
GamepadKeyNegative int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is checks if the MIDI message msg triggers this Mapping, without actually triggering it.
|
||||||
|
func (m EncoderMapping) 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 EncoderMapping) TriggerIfMatch(msg midi.Message, virtGamepad uinput.Gamepad) error {
|
||||||
|
if m.Is(msg) {
|
||||||
|
var valueAbsolute uint8
|
||||||
|
|
||||||
|
msg.GetControlChange(nil, nil, &valueAbsolute)
|
||||||
|
|
||||||
|
switch valueAbsolute {
|
||||||
|
case 1:
|
||||||
|
log.Debug(m.CommentStr, "status", "increased")
|
||||||
|
return virtGamepad.ButtonPress(m.GamepadKeyPositive)
|
||||||
|
case 127:
|
||||||
|
log.Debug(m.CommentStr, "status", "decreased")
|
||||||
|
return virtGamepad.ButtonPress(m.GamepadKeyNegative)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Invalid message type triggered ButtonMapping")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comment returns the Mappings comment.
|
||||||
|
func (m EncoderMapping) Comment() string {
|
||||||
|
return m.CommentStr
|
||||||
|
}
|
||||||
type ControllerAxis int
|
type ControllerAxis int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
Reference in New Issue
Block a user