12 Commits

Author SHA1 Message Date
Joel Wetzell
9cb7abcc8f add commit to version 2025-12-16 20:45:05 -06:00
Joel Wetzell
991111deba add version flag to cli command 2025-12-16 20:38:26 -06:00
Joel Wetzell
9b1ca9da96 Merge pull request #17 from jwetzell/feat/midi-message-create
add midi.message.create processor
2025-12-16 20:09:57 -06:00
Joel Wetzell
b726745aa2 add alternate casings for type 2025-12-16 20:08:32 -06:00
Joel Wetzell
a8bcf7a785 add ControlChange and ProgramChange 2025-12-16 20:02:16 -06:00
Joel Wetzell
32d8633402 add midi processor to create messages 2025-12-16 19:54:21 -06:00
Joel Wetzell
f3aaa86a1c Merge pull request #16 from jwetzell/feat/split-midi
split midi.client into input/output
2025-12-16 19:43:20 -06:00
Joel Wetzell
25b06ffc6d split midi.client into input/output 2025-12-16 19:24:20 -06:00
Joel Wetzell
ca3d662df7 Merge pull request #15 from jwetzell/dependabot/go_modules/gitlab.com/gomidi/midi/v2-2.3.18
Bump gitlab.com/gomidi/midi/v2 from 2.3.16 to 2.3.18
2025-12-15 13:53:09 -06:00
dependabot[bot]
86fe006af8 Bump gitlab.com/gomidi/midi/v2 from 2.3.16 to 2.3.18
Bumps [gitlab.com/gomidi/midi/v2](https://gitlab.com/gomidi/midi) from 2.3.16 to 2.3.18.
- [Commits](https://gitlab.com/gomidi/midi/compare/v2.3.16...v2.3.18)

---
updated-dependencies:
- dependency-name: gitlab.com/gomidi/midi/v2
  dependency-version: 2.3.18
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 18:11:35 +00:00
Joel Wetzell
d94232871e add launch.json 2025-12-14 12:14:03 -06:00
Joel Wetzell
5c8605104d update README 2025-12-13 13:59:54 -06:00
10 changed files with 575 additions and 133 deletions

16
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Launch showbridge",
"type": "go",
"request": "launch",
"mode": "auto",
"program": "cmd/showbridge"
}
]
}

View File

@@ -24,6 +24,10 @@ Simple protocol router _/s_
- client
- MIDI
- client (not included in pre-built binaries yet)
- [SIP](https://en.wikipedia.org/wiki/Session_Initiation_Protocol)
- call server
- [DTMF](https://en.wikipedia.org/wiki/Dual-tone_multi-frequency_signaling) server

View File

@@ -14,6 +14,8 @@ builds:
goarch:
- "amd64"
- "arm64"
ldflags:
- '-s -w -X main.version={{.RawVersion}}-{{.ShortCommit}}'
archives:
- formats: [tar.gz]

View File

@@ -13,9 +13,14 @@ import (
"sigs.k8s.io/yaml"
)
var (
version = "dev"
)
func main() {
cmd := &cli.Command{
Name: "showbridge",
Name: "showbridge",
Version: version,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "config",

2
go.mod
View File

@@ -12,7 +12,7 @@ require (
github.com/jwetzell/psn-go v0.3.0
github.com/nats-io/nats.go v1.47.0
github.com/urfave/cli/v3 v3.6.1
gitlab.com/gomidi/midi/v2 v2.3.16
gitlab.com/gomidi/midi/v2 v2.3.18
go.bug.st/serial v1.6.4
modernc.org/quickjs v0.17.0
sigs.k8s.io/yaml v1.6.0

14
go.sum
View File

@@ -52,26 +52,16 @@ github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/pion/logging v0.2.3 h1:gHuf0zpoh1GW67Nr6Gj4cv5Z9ZscU7g/EaoC/Ke/igI=
github.com/pion/logging v0.2.3/go.mod h1:z8YfknkquMe1csOrxK5kc+5/ZPAzMxbKLX5aXpbpC90=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
github.com/pion/rtp v1.8.18 h1:yEAb4+4a8nkPCecWzQB6V/uEU18X1lQCGAQCjP+pyvU=
github.com/pion/rtp v1.8.18/go.mod h1:bAu2UFKScgzyFqvUKmbvzSdPr+NGbZtv6UB2hesqXBk=
github.com/pion/rtp v1.8.26 h1:VB+ESQFQhBXFytD+Gk8cxB6dXeVf2WQzg4aORvAvAAc=
github.com/pion/rtp v1.8.26/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
github.com/pion/srtp/v3 v3.0.6 h1:E2gyj1f5X10sB/qILUGIkL4C2CqK269Xq167PbGCc/4=
github.com/pion/srtp/v3 v3.0.6/go.mod h1:BxvziG3v/armJHAaJ87euvkhHqWe9I7iiOy50K2QkhY=
github.com/pion/srtp/v3 v3.0.9 h1:lRGF4G61xxj+m/YluB3ZnBpiALSri2lTzba0kGZMrQY=
github.com/pion/srtp/v3 v3.0.9/go.mod h1:E+AuWd7Ug2Fp5u38MKnhduvpVkveXJX6J4Lq4rxUYt8=
github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -86,8 +76,8 @@ github.com/urfave/cli/v3 v3.6.1 h1:j8Qq8NyUawj/7rTYdBGrxcH7A/j7/G8Q5LhWEW4G3Mo=
github.com/urfave/cli/v3 v3.6.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
github.com/zaf/g711 v1.4.0 h1:XZYkjjiAg9QTBnHqEg37m2I9q3IIDv5JRYXs2N8ma7c=
github.com/zaf/g711 v1.4.0/go.mod h1:eCDXt3dSp/kYYAoooba7ukD/Q75jvAaS4WOMr0l1Roo=
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.18 h1:sj2fOhtvOe+zI8YJe8qTxLw5zv0ntULLUDwcFOaZQbI=
gitlab.com/gomidi/midi/v2 v2.3.18/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw=
go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=

View File

@@ -1,119 +0,0 @@
//go:build cgo
package module
import (
"context"
"fmt"
"log/slog"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
"gitlab.com/gomidi/midi/v2"
_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
)
type MIDIClient struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
InputPort string
OutputPort string
SendFunc func(midi.Message) error
}
func init() {
RegisterModule(ModuleRegistration{
//TODO(jwetzell): find a better namespace than "misc"
Type: "midi.client",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
params := config.Params
input, ok := params["input"]
if !ok {
return nil, fmt.Errorf("midi.client requires a input parameter")
}
inputString, ok := input.(string)
if !ok {
return nil, fmt.Errorf("midi.client input must be a string")
}
output, ok := params["output"]
if !ok {
return nil, fmt.Errorf("midi.client requires a output parameter")
}
outputString, ok := output.(string)
if !ok {
return nil, fmt.Errorf("midi.client output must be a string")
}
return &MIDIClient{config: config, InputPort: inputString, OutputPort: outputString, ctx: ctx, router: router}, nil
},
})
}
func (mc *MIDIClient) Id() string {
return mc.config.Id
}
func (mc *MIDIClient) Type() string {
return mc.config.Type
}
func (mc *MIDIClient) Run() error {
defer midi.CloseDriver()
in, err := midi.FindInPort(mc.InputPort)
if err != nil {
return fmt.Errorf("midi.client can't find input port: %s", mc.InputPort)
}
stop, err := midi.ListenTo(in, func(msg midi.Message, timestampms int32) {
if mc.router != nil {
// TODO(jwetzell): unpack MIDI messsage into something more useful?
mc.router.HandleInput(mc.Id(), msg)
}
}, midi.UseSysEx())
if err != nil {
return err
}
defer stop()
out, err := midi.FindOutPort(mc.OutputPort)
if err != nil {
return fmt.Errorf("midi.client can't find output port: %s", mc.OutputPort)
}
send, err := midi.SendTo(out)
if err != nil {
return err
}
mc.SendFunc = send
<-mc.ctx.Done()
slog.Debug("router context done in module", "id", mc.Id())
return nil
}
func (mc *MIDIClient) Output(payload any) error {
if mc.SendFunc == nil {
return fmt.Errorf("midi.client output is not setup")
}
payloadMessage, ok := payload.(midi.Message)
if !ok {
return fmt.Errorf("midi.client can only ouptut midi.Message")
}
return mc.SendFunc(payloadMessage)
}

View File

@@ -0,0 +1,81 @@
//go:build cgo
package module
import (
"context"
"fmt"
"log/slog"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
"gitlab.com/gomidi/midi/v2"
_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
)
type MIDIInput struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
Port string
SendFunc func(midi.Message) error
}
func init() {
RegisterModule(ModuleRegistration{
Type: "midi.input",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
params := config.Params
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("midi.input requires a port parameter")
}
portString, ok := port.(string)
if !ok {
return nil, fmt.Errorf("midi.input port must be a string")
}
return &MIDIInput{config: config, Port: portString, ctx: ctx, router: router}, nil
},
})
}
func (mc *MIDIInput) Id() string {
return mc.config.Id
}
func (mc *MIDIInput) Type() string {
return mc.config.Type
}
func (mc *MIDIInput) Run() error {
defer midi.CloseDriver()
in, err := midi.FindInPort(mc.Port)
if err != nil {
return fmt.Errorf("midi.input can't find input port: %s", mc.Port)
}
stop, err := midi.ListenTo(in, func(msg midi.Message, timestampms int32) {
if mc.router != nil {
mc.router.HandleInput(mc.Id(), msg)
}
}, midi.UseSysEx())
if err != nil {
return err
}
defer stop()
<-mc.ctx.Done()
slog.Debug("router context done in module", "id", mc.Id())
return nil
}
func (mc *MIDIInput) Output(payload any) error {
return fmt.Errorf("midi.input output is not implemented")
}

View File

@@ -0,0 +1,89 @@
//go:build cgo
package module
import (
"context"
"fmt"
"log/slog"
"github.com/jwetzell/showbridge-go/internal/config"
"github.com/jwetzell/showbridge-go/internal/route"
"gitlab.com/gomidi/midi/v2"
_ "gitlab.com/gomidi/midi/v2/drivers/rtmididrv"
)
type MIDIOutput struct {
config config.ModuleConfig
ctx context.Context
router route.RouteIO
Port string
SendFunc func(midi.Message) error
}
func init() {
RegisterModule(ModuleRegistration{
//TODO(jwetzell): find a better namespace than "misc"
Type: "midi.output",
New: func(ctx context.Context, config config.ModuleConfig, router route.RouteIO) (Module, error) {
params := config.Params
port, ok := params["port"]
if !ok {
return nil, fmt.Errorf("midi.output requires a port parameter")
}
portString, ok := port.(string)
if !ok {
return nil, fmt.Errorf("midi.output port must be a string")
}
return &MIDIOutput{config: config, Port: portString, ctx: ctx, router: router}, nil
},
})
}
func (mc *MIDIOutput) Id() string {
return mc.config.Id
}
func (mc *MIDIOutput) Type() string {
return mc.config.Type
}
func (mc *MIDIOutput) Run() error {
defer midi.CloseDriver()
out, err := midi.FindOutPort(mc.Port)
if err != nil {
return fmt.Errorf("midi.output can't find output port: %s", mc.Port)
}
send, err := midi.SendTo(out)
if err != nil {
return err
}
mc.SendFunc = send
<-mc.ctx.Done()
slog.Debug("router context done in module", "id", mc.Id())
return nil
}
func (mc *MIDIOutput) Output(payload any) error {
if mc.SendFunc == nil {
return fmt.Errorf("midi.output output is not setup")
}
payloadMessage, ok := payload.(midi.Message)
if !ok {
return fmt.Errorf("midi.output can only ouptut midi.Message")
}
return mc.SendFunc(payloadMessage)
}

View File

@@ -0,0 +1,374 @@
//go:build cgo
package processor
import (
"bytes"
"context"
"fmt"
"strconv"
"text/template"
"github.com/jwetzell/showbridge-go/internal/config"
"gitlab.com/gomidi/midi/v2"
)
type MIDIMessageCreate struct {
config config.ProcessorConfig
ProcessFunc func(ctx context.Context, payload any) (any, error)
}
func (mmd *MIDIMessageCreate) Process(ctx context.Context, payload any) (any, error) {
return mmd.ProcessFunc(ctx, payload)
}
func (mmd *MIDIMessageCreate) Type() string {
return mmd.config.Type
}
func newMidiNoteOnCreate(config config.ProcessorConfig) (Processor, error) {
params := config.Params
channel, ok := params["channel"]
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
if err != nil {
return nil, err
}
note, ok := params["note"]
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn requires a note parameter")
}
noteString, ok := note.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn note must be a string")
}
noteTemplate, err := template.New("note").Parse(noteString)
if err != nil {
return nil, err
}
velocity, ok := params["velocity"]
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn requires a velocity parameter")
}
velocityString, ok := velocity.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn velocity must be a string")
}
velocityTemplate, err := template.New("velocity").Parse(velocityString)
if err != nil {
return nil, err
}
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
if err != nil {
return nil, err
}
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var noteBuffer bytes.Buffer
err = noteTemplate.Execute(&noteBuffer, payload)
if err != nil {
return nil, err
}
noteValue, err := strconv.ParseUint(noteBuffer.String(), 10, 8)
var velocityBuffer bytes.Buffer
err = velocityTemplate.Execute(&velocityBuffer, payload)
if err != nil {
return nil, err
}
velocityValue, err := strconv.ParseUint(velocityBuffer.String(), 10, 8)
payloadMessage := midi.NoteOn(uint8(channelValue), uint8(noteValue), uint8(velocityValue))
return payloadMessage, nil
}}, nil
}
func newMidiNoteOffCreate(config config.ProcessorConfig) (Processor, error) {
params := config.Params
channel, ok := params["channel"]
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
if err != nil {
return nil, err
}
note, ok := params["note"]
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn requires a note parameter")
}
noteString, ok := note.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create NoteOn note must be a string")
}
noteTemplate, err := template.New("note").Parse(noteString)
if err != nil {
return nil, err
}
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
if err != nil {
return nil, err
}
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var noteBuffer bytes.Buffer
err = noteTemplate.Execute(&noteBuffer, payload)
if err != nil {
return nil, err
}
noteValue, err := strconv.ParseUint(noteBuffer.String(), 10, 8)
payloadMessage := midi.NoteOff(uint8(channelValue), uint8(noteValue))
return payloadMessage, nil
}}, nil
}
func newMidiControlChangeCreate(config config.ProcessorConfig) (Processor, error) {
params := config.Params
channel, ok := params["channel"]
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
if err != nil {
return nil, err
}
controller, ok := params["controller"]
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange requires a controller parameter")
}
controllerString, ok := controller.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange controller must be a string")
}
controllerTemplate, err := template.New("controller").Parse(controllerString)
if err != nil {
return nil, err
}
value, ok := params["value"]
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange requires a value parameter")
}
valueString, ok := value.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create ControlChange value must be a string")
}
valueTemplate, err := template.New("value").Parse(valueString)
if err != nil {
return nil, err
}
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
if err != nil {
return nil, err
}
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var controllerBuffer bytes.Buffer
err = controllerTemplate.Execute(&controllerBuffer, payload)
if err != nil {
return nil, err
}
controllerValue, err := strconv.ParseUint(controllerBuffer.String(), 10, 8)
var valueBuffer bytes.Buffer
err = valueTemplate.Execute(&valueBuffer, payload)
if err != nil {
return nil, err
}
valueValue, err := strconv.ParseUint(valueBuffer.String(), 10, 8)
payloadMessage := midi.ControlChange(uint8(channelValue), uint8(controllerValue), uint8(valueValue))
return payloadMessage, nil
}}, nil
}
func newMidiProgramChangeCreate(config config.ProcessorConfig) (Processor, error) {
params := config.Params
channel, ok := params["channel"]
if !ok {
return nil, fmt.Errorf("midi.message.create ProgramChange requires a channel parameter")
}
channelString, ok := channel.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create ProgramChange channel must be a string")
}
channelTemplate, err := template.New("channel").Parse(channelString)
if err != nil {
return nil, err
}
program, ok := params["program"]
if !ok {
return nil, fmt.Errorf("midi.message.create ProgramChange requires a program parameter")
}
programString, ok := program.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create ProgramChange program must be a string")
}
programTemplate, err := template.New("program").Parse(programString)
if err != nil {
return nil, err
}
return &MIDIMessageCreate{config: config, ProcessFunc: func(ctx context.Context, payload any) (any, error) {
var channelBuffer bytes.Buffer
err := channelTemplate.Execute(&channelBuffer, payload)
if err != nil {
return nil, err
}
channelValue, err := strconv.ParseUint(channelBuffer.String(), 10, 8)
var programBuffer bytes.Buffer
err = programTemplate.Execute(&programBuffer, payload)
if err != nil {
return nil, err
}
programValue, err := strconv.ParseUint(programBuffer.String(), 10, 8)
payloadMessage := midi.ProgramChange(uint8(channelValue), uint8(programValue))
return payloadMessage, nil
}}, nil
}
func init() {
RegisterProcessor(ProcessorRegistration{
Type: "midi.message.create",
New: func(config config.ProcessorConfig) (Processor, error) {
params := config.Params
msgType, ok := params["type"]
if !ok {
return nil, fmt.Errorf("midi.message.create requires a type parameter")
}
msgTypeString, ok := msgType.(string)
if !ok {
return nil, fmt.Errorf("midi.message.create type parameter must be a string")
}
switch msgTypeString {
case "NoteOn", "noteon", "note_on":
return newMidiNoteOnCreate(config)
case "NoteOff", "noteoff", "note_off":
return newMidiNoteOffCreate(config)
case "ControlChange", "controlchange", "control_change":
return newMidiControlChangeCreate(config)
case "ProgramChange", "programchange", "program_change":
return newMidiProgramChangeCreate(config)
default:
return nil, fmt.Errorf("midi.message.create does not support type %s", msgTypeString)
}
},
})
}