Add output control and interactive demo with paired faders, encoder rings, meters, and LCDs

This commit is contained in:
Ian Gulliver
2026-02-10 21:38:04 -08:00
parent 14a361e7a0
commit fa7b310308
3 changed files with 166 additions and 5 deletions

View File

@@ -4,3 +4,4 @@ Never commit without permission.
Never mention Claude in commit messages. Single-line commit messages only. No Co-Authored-By line.
Never add comments to code.
Never ignore CLAUDE.md rules under any circumstances.
Never run tasks in the background.

78
main.go
View File

@@ -12,10 +12,42 @@ import (
"qrun/xtouch"
)
var lcdColors = []xtouch.LCDColor{
xtouch.ColorRed,
xtouch.ColorGreen,
xtouch.ColorYellow,
xtouch.ColorBlue,
xtouch.ColorMagenta,
xtouch.ColorCyan,
xtouch.ColorWhite,
}
var (
faderValues [9]uint8
encoderValues [8]int
)
func updateLCD(out *xtouch.Output, ch uint8) {
color := lcdColors[encoderValues[ch]*len(lcdColors)/128]
out.SetLCD(ch, color, false, false,
fmt.Sprintf("E%-3d F%-3d", encoderValues[ch], faderValues[ch]),
fmt.Sprintf("Chan %d", ch+1))
}
func clamp(v, lo, hi int) int {
if v < lo {
return lo
}
if v > hi {
return hi
}
return v
}
func main() {
defer midi.CloseDriver()
port, err := xtouch.FindInPort("x-touch")
inPort, err := xtouch.FindInPort("x-touch")
if err != nil {
fmt.Println("Available MIDI input ports:")
for _, p := range midi.GetInPorts() {
@@ -25,13 +57,51 @@ func main() {
os.Exit(1)
}
outPort, err := xtouch.FindOutPort("x-touch")
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
out, err := xtouch.NewOutput(outPort, xtouch.DeviceIDExtender)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
for i := uint8(0); i < 8; i++ {
updateLCD(out, i)
}
dec := &xtouch.Decoder{EncoderMode: xtouch.EncoderRelative}
fmt.Printf("Listening on: %s\n", port)
fmt.Printf("Listening on: %s\n", inPort)
stop, err := midi.ListenTo(port, func(msg midi.Message, timestampms int32) {
if event := dec.Decode(msg); event != nil {
stop, err := midi.ListenTo(inPort, func(msg midi.Message, timestampms int32) {
event := dec.Decode(msg)
if event == nil {
return
}
fmt.Println(event)
switch e := event.(type) {
case xtouch.FaderEvent:
if e.Fader > 7 {
return
}
faderValues[e.Fader] = e.Value
pair := e.Fader ^ 1
faderValues[pair] = e.Value
out.SetFader(pair, e.Value)
out.SetMeter(e.Fader, e.Value)
out.SetMeter(pair, e.Value)
updateLCD(out, e.Fader)
updateLCD(out, pair)
case xtouch.EncoderRelativeEvent:
encoderValues[e.Encoder] = clamp(encoderValues[e.Encoder]+e.Delta, 0, 127)
out.SetEncoderRing(e.Encoder, uint8(encoderValues[e.Encoder]))
updateLCD(out, e.Encoder)
}
})
if err != nil {

90
xtouch/output.go Normal file
View File

@@ -0,0 +1,90 @@
package xtouch
import (
"fmt"
"gitlab.com/gomidi/midi/v2"
"gitlab.com/gomidi/midi/v2/drivers"
)
type LCDColor uint8
const (
ColorBlack LCDColor = 0
ColorRed LCDColor = 1
ColorGreen LCDColor = 2
ColorYellow LCDColor = 3
ColorBlue LCDColor = 4
ColorMagenta LCDColor = 5
ColorCyan LCDColor = 6
ColorWhite LCDColor = 7
)
type LEDState uint8
const (
LEDOff LEDState = 0
LEDFlash LEDState = 64
LEDOn LEDState = 127
)
type Output struct {
send func(msg midi.Message) error
DeviceID uint8
}
func NewOutput(port drivers.Out, deviceID uint8) (*Output, error) {
send, err := midi.SendTo(port)
if err != nil {
return nil, fmt.Errorf("open output port: %w", err)
}
return &Output{send: send, DeviceID: deviceID}, nil
}
func (o *Output) SetFader(fader uint8, value uint8) error {
cc := CCFaderFirst + fader
if fader == 8 {
cc = CCFaderMain
}
return o.send(midi.ControlChange(0, cc, value))
}
func (o *Output) SetButtonLED(button uint8, state LEDState) error {
return o.send(midi.NoteOn(0, button, uint8(state)))
}
func (o *Output) SetEncoderRing(encoder uint8, value uint8) error {
return o.send(midi.ControlChange(0, CCEncoderFirst+encoder, value))
}
func (o *Output) SetMeter(channel uint8, value uint8) error {
return o.send(midi.ControlChange(0, CCMeterFirst+channel, value))
}
func (o *Output) SetLCD(lcd uint8, color LCDColor, invertUpper bool, invertLower bool, upper string, lower string) error {
cc := uint8(color)
if invertUpper {
cc |= 0x10
}
if invertLower {
cc |= 0x20
}
upper = padOrTruncate(upper, 7)
lower = padOrTruncate(lower, 7)
data := []byte{0x00, 0x20, 0x32, o.DeviceID, 0x4C, lcd, cc}
data = append(data, []byte(upper)...)
data = append(data, []byte(lower)...)
return o.send(midi.SysEx(data))
}
func padOrTruncate(s string, n int) string {
if len(s) > n {
return s[:n]
}
for len(s) < n {
s += " "
}
return s
}