commit 0c0e1e985e0a88cd4ae91714f27569b7b0096a9e Author: datalore Date: Sun Aug 3 16:21:33 2025 +0200 Initial commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..1c13733 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# midi-hid + +This software allows mapping and translating of MIDI commands to HID inputs on Linux. diff --git a/controller.go b/controller.go new file mode 100644 index 0000000..b98d048 --- /dev/null +++ b/controller.go @@ -0,0 +1,53 @@ +package main + +import ( + "log" + "gitlab.com/gomidi/midi/v2" +) + +type Controller struct { + midiInput *MidiInput + mappings []Mapping + abortChan chan interface{} +} + +func NewController(portName string) (*Controller, error) { + midiInput, err := NewMidiInput(portName) + if err != nil { + return nil, err + } + + abortChan := make(chan interface{}) + + controller := &Controller{midiInput, nil, abortChan} + + go func() { + for { + select { + case midiMessage := <-midiInput.Messages: + controller.update(midiMessage) + case <-abortChan: + return + } + } + }() + + return controller, nil +} + +func (c *Controller) AddMapping(mapping Mapping) { + c.mappings = append(c.mappings, mapping) +} + +func (c Controller) Stop() { + c.midiInput.Stop() + c.abortChan <- struct{}{} +} + +func (c Controller) update(msg midi.Message) { + for _, mapping := range c.mappings { + if mapping.Is(msg) { + log.Println("Mapping triggered!\n") + } + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9d6322e --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module datalore/midi-hid + +go 1.24.5 + +require gitlab.com/gomidi/midi/v2 v2.3.16 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..56e5727 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +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 new file mode 100644 index 0000000..15be537 --- /dev/null +++ b/main.go @@ -0,0 +1,25 @@ +package main + +import ( + "log" + "time" + + "gitlab.com/gomidi/midi/v2" +) + +func must[T any](obj T, err error) T { + if err != nil { + log.Fatal(err) + } + + return obj +} + +func main() { + defer midi.CloseDriver() + + midiInput := must(NewMidiInput("DJControl Inpulse 500 MIDI 1")) + + time.Sleep(time.Second * 20) + midiInput.Stop() +} diff --git a/mapping.go b/mapping.go new file mode 100644 index 0000000..22e2fa0 --- /dev/null +++ b/mapping.go @@ -0,0 +1,40 @@ +package main + +import ( + "gitlab.com/gomidi/midi/v2" +) + +type Mapping interface { + Is(midi.Message) bool +} + +type ButtonMapping struct { + midiChannel uint8 + midiKey uint8 +} + +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 + } +} + +type ControlMapping struct { + midiChannel uint8 + midiController uint8 +} + +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 + } +} diff --git a/midi.go b/midi.go new file mode 100644 index 0000000..47ee925 --- /dev/null +++ b/midi.go @@ -0,0 +1,35 @@ +package main + +import ( + "gitlab.com/gomidi/midi/v2" + "gitlab.com/gomidi/midi/v2/drivers" + _ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv" +) + +type MidiInput struct { + input drivers.In + Messages chan midi.Message + Stop func() +} + +func NewMidiInput(portName string) (*MidiInput, error) { + input, err := midi.FindInPort(portName) + if err != nil { + return nil, err + } + + messages := make(chan midi.Message) + + midiListener := func(msg midi.Message, timestampMs int32) { + if msg.IsOneOf(midi.NoteOnMsg, midi.NoteOffMsg, midi.ControlChangeMsg) { + messages <- msg + } + } + + stopFunc, err := midi.ListenTo(input, midiListener, midi.UseSysEx()) + if err != nil { + return nil, err + } + + return &MidiInput{input, messages, stopFunc}, nil +}