Add output control and interactive demo with paired faders, encoder rings, meters, and LCDs
This commit is contained in:
@@ -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 mention Claude in commit messages. Single-line commit messages only. No Co-Authored-By line.
|
||||||
Never add comments to code.
|
Never add comments to code.
|
||||||
Never ignore CLAUDE.md rules under any circumstances.
|
Never ignore CLAUDE.md rules under any circumstances.
|
||||||
|
Never run tasks in the background.
|
||||||
|
|||||||
78
main.go
78
main.go
@@ -12,10 +12,42 @@ import (
|
|||||||
"qrun/xtouch"
|
"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() {
|
func main() {
|
||||||
defer midi.CloseDriver()
|
defer midi.CloseDriver()
|
||||||
|
|
||||||
port, err := xtouch.FindInPort("x-touch")
|
inPort, err := xtouch.FindInPort("x-touch")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("Available MIDI input ports:")
|
fmt.Println("Available MIDI input ports:")
|
||||||
for _, p := range midi.GetInPorts() {
|
for _, p := range midi.GetInPorts() {
|
||||||
@@ -25,13 +57,51 @@ func main() {
|
|||||||
os.Exit(1)
|
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}
|
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) {
|
stop, err := midi.ListenTo(inPort, func(msg midi.Message, timestampms int32) {
|
||||||
if event := dec.Decode(msg); event != nil {
|
event := dec.Decode(msg)
|
||||||
|
if event == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Println(event)
|
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 {
|
if err != nil {
|
||||||
|
|||||||
90
xtouch/output.go
Normal file
90
xtouch/output.go
Normal 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
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user