mirror of
https://github.com/jwetzell/showbridge-go.git
synced 2026-04-27 21:35:30 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9cb7abcc8f | ||
|
|
991111deba | ||
|
|
9b1ca9da96 | ||
|
|
b726745aa2 | ||
|
|
a8bcf7a785 | ||
|
|
32d8633402 | ||
|
|
f3aaa86a1c | ||
|
|
25b06ffc6d | ||
|
|
ca3d662df7 | ||
|
|
86fe006af8 | ||
|
|
d94232871e | ||
|
|
5c8605104d |
16
.vscode/launch.json
vendored
Normal file
16
.vscode/launch.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -24,6 +24,10 @@ Simple protocol router _/s_
|
|||||||
- client
|
- client
|
||||||
- MIDI
|
- MIDI
|
||||||
- client (not included in pre-built binaries yet)
|
- 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
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ builds:
|
|||||||
goarch:
|
goarch:
|
||||||
- "amd64"
|
- "amd64"
|
||||||
- "arm64"
|
- "arm64"
|
||||||
|
ldflags:
|
||||||
|
- '-s -w -X main.version={{.RawVersion}}-{{.ShortCommit}}'
|
||||||
|
|
||||||
archives:
|
archives:
|
||||||
- formats: [tar.gz]
|
- formats: [tar.gz]
|
||||||
|
|||||||
@@ -13,9 +13,14 @@ import (
|
|||||||
"sigs.k8s.io/yaml"
|
"sigs.k8s.io/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
version = "dev"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
cmd := &cli.Command{
|
cmd := &cli.Command{
|
||||||
Name: "showbridge",
|
Name: "showbridge",
|
||||||
|
Version: version,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
&cli.StringFlag{
|
&cli.StringFlag{
|
||||||
Name: "config",
|
Name: "config",
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -12,7 +12,7 @@ require (
|
|||||||
github.com/jwetzell/psn-go v0.3.0
|
github.com/jwetzell/psn-go v0.3.0
|
||||||
github.com/nats-io/nats.go v1.47.0
|
github.com/nats-io/nats.go v1.47.0
|
||||||
github.com/urfave/cli/v3 v3.6.1
|
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
|
go.bug.st/serial v1.6.4
|
||||||
modernc.org/quickjs v0.17.0
|
modernc.org/quickjs v0.17.0
|
||||||
sigs.k8s.io/yaml v1.6.0
|
sigs.k8s.io/yaml v1.6.0
|
||||||
|
|||||||
14
go.sum
14
go.sum
@@ -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/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 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
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 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||||
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
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 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
|
||||||
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
|
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 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo=
|
||||||
github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo=
|
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 h1:VB+ESQFQhBXFytD+Gk8cxB6dXeVf2WQzg4aORvAvAAc=
|
||||||
github.com/pion/rtp v1.8.26/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
|
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 h1:lRGF4G61xxj+m/YluB3ZnBpiALSri2lTzba0kGZMrQY=
|
||||||
github.com/pion/srtp/v3 v3.0.9/go.mod h1:E+AuWd7Ug2Fp5u38MKnhduvpVkveXJX6J4Lq4rxUYt8=
|
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 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM=
|
||||||
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
|
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
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/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 h1:XZYkjjiAg9QTBnHqEg37m2I9q3IIDv5JRYXs2N8ma7c=
|
||||||
github.com/zaf/g711 v1.4.0/go.mod h1:eCDXt3dSp/kYYAoooba7ukD/Q75jvAaS4WOMr0l1Roo=
|
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.18 h1:sj2fOhtvOe+zI8YJe8qTxLw5zv0ntULLUDwcFOaZQbI=
|
||||||
gitlab.com/gomidi/midi/v2 v2.3.16/go.mod h1:jDpP4O4skYi+7iVwt6Zyp18bd2M4hkjtMuw2cmgKgfw=
|
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 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
|
||||||
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
|
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
|
||||||
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
81
internal/module/midi-input.go
Normal file
81
internal/module/midi-input.go
Normal 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")
|
||||||
|
}
|
||||||
89
internal/module/midi-ouptut.go
Normal file
89
internal/module/midi-ouptut.go
Normal 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)
|
||||||
|
}
|
||||||
374
internal/processor/midi-message-create.go
Normal file
374
internal/processor/midi-message-create.go
Normal 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(¬eBuffer, 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(¬eBuffer, 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)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user