machine/usb/adc/midi: improve implementation to include several new messages

such as program changes and pitch bend. Also add error handling for invalid
parameter values such as MIDI channel. This however makes a somewhat breaking
change to the current implementation, in that we now use the typical MIDI user
system of counting MIDI channels from 1-16 instead of from 0-15 as the lower
level USB-MIDI API itself expects.

Also add constant values for continuous controller messages, rename SendCC
function, and add SysEx function.

Signed-off-by: deadprogram <ron@hybridgroup.com>
Этот коммит содержится в:
deadprogram 2023-09-03 12:06:00 +02:00 коммит произвёл Ron Evans
родитель 4643401a1d
коммит 9d6eb1ff06
3 изменённых файлов: 278 добавлений и 33 удалений

Просмотреть файл

@ -10,47 +10,57 @@ import (
// Try it easily by opening the following site in Chrome.
// https://www.onlinemusictools.com/kb/
const (
cable = 0
channel = 1
velocity = 0x40
)
func main() {
led := machine.LED
led.Configure(machine.PinConfig{Mode: machine.PinOutput})
button := machine.BUTTON
button.Configure(machine.PinConfig{Mode: machine.PinInputPulldown})
button.Configure(machine.PinConfig{Mode: machine.PinInputPullup})
m := midi.Port()
m.SetHandler(func(b []byte) {
m.SetRxHandler(func(b []byte) {
// blink when we receive a MIDI message
led.Set(!led.Get())
})
m.SetTxHandler(func() {
// blink when we send a MIDI message
led.Set(!led.Get())
m.Write(b)
})
prev := true
chords := []struct {
name string
keys []midi.Note
name string
notes []midi.Note
}{
{name: "C ", keys: []midi.Note{midi.C4, midi.E4, midi.G4}},
{name: "G ", keys: []midi.Note{midi.G3, midi.B3, midi.D4}},
{name: "Am", keys: []midi.Note{midi.A3, midi.C4, midi.E4}},
{name: "F ", keys: []midi.Note{midi.F3, midi.A3, midi.C4}},
{name: "C ", notes: []midi.Note{midi.C4, midi.E4, midi.G4}},
{name: "G ", notes: []midi.Note{midi.G3, midi.B3, midi.D4}},
{name: "Am", notes: []midi.Note{midi.A3, midi.C4, midi.E4}},
{name: "F ", notes: []midi.Note{midi.F3, midi.A3, midi.C4}},
}
index := 0
for {
current := button.Get()
if prev != current {
led.Set(current)
if current {
for _, c := range chords[index].keys {
m.NoteOff(0, 0, c, 0x40)
for _, note := range chords[index].notes {
m.NoteOff(cable, channel, note, velocity)
}
index = (index + 1) % len(chords)
} else {
for _, c := range chords[index].keys {
m.NoteOn(0, 0, c, 0x40)
for _, note := range chords[index].notes {
m.NoteOn(cable, channel, note, velocity)
}
}
prev = current
}
time.Sleep(10 * time.Millisecond)
time.Sleep(100 * time.Millisecond)
}
}

Просмотреть файл

@ -1,19 +1,233 @@
package midi
// NoteOn sends a note on message.
func (m *midi) NoteOn(cable, channel uint8, note Note, velocity uint8) {
m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|0x9, 0x90|(channel&0xf), byte(note)&0x7f, velocity&0x7f
m.Write(m.msg[:])
import (
"errors"
)
// From USB-MIDI section 4.1 "Code Index Number (CIN) Classifications"
const (
CINSystemCommon2 = 0x2
CINSystemCommon3 = 0x3
CINSysExStart = 0x4
CINSysExEnd1 = 0x5
CINSysExEnd2 = 0x6
CINSysExEnd3 = 0x7
CINNoteOff = 0x8
CINNoteOn = 0x9
CINPoly = 0xA
CINControlChange = 0xB
CINProgramChange = 0xC
CINChannelPressure = 0xD
CINPitchBendChange = 0xE
CINSingleByte = 0xF
)
// Standard MIDI channel messages
const (
MsgNoteOff = 0x80
MsgNoteOn = 0x90
MsgPolyAftertouch = 0xA0
MsgControlChange = 0xB0
MsgProgramChange = 0xC0
MsgChannelAftertouch = 0xD0
MsgPitchBend = 0xE0
MsgSysExStart = 0xF0
MsgSysExEnd = 0xF7
)
// Standard MIDI control change messages
const (
CCModulationWheel = 0x01
CCBreathController = 0x02
CCFootPedal = 0x04
CCPortamentoTime = 0x05
CCDataEntry = 0x06
CCVolume = 0x07
CCBalance = 0x08
CCPan = 0x0A
CCExpression = 0x0B
CCEffectControl1 = 0x0C
CCEffectControl2 = 0x0D
CCGeneralPurpose1 = 0x10
CCGeneralPurpose2 = 0x11
CCGeneralPurpose3 = 0x12
CCGeneralPurpose4 = 0x13
CCBankSelect = 0x20
CCModulationDepthRange = 0x21
CCBreathControllerDepth = 0x22
CCFootPedalDepth = 0x24
CCEffectsLevel = 0x5B
CCTremeloLevel = 0x5C
CCChorusLevel = 0x5D
CCCelesteLevel = 0x5E
CCPhaserLevel = 0x5F
CCDataIncrement = 0x60
CCDataDecrement = 0x61
CCNRPNLSB = 0x62
CCNRPNMSB = 0x63
CCRPNLSB = 0x64
CCRPNMSB = 0x65
CCAllSoundOff = 0x78
CCResetAllControllers = 0x79
CCAllNotesOff = 0x7B
CCChannelVolume = 0x7F
)
var (
errInvalidMIDICable = errors.New("invalid MIDI cable")
errInvalidMIDIChannel = errors.New("invalid MIDI channel")
errInvalidMIDIVelocity = errors.New("invalid MIDI velocity")
errInvalidMIDIControl = errors.New("invalid MIDI control number")
errInvalidMIDIControlValue = errors.New("invalid MIDI control value")
errInvalidMIDIPatch = errors.New("invalid MIDI patch number")
errInvalidMIDIPitchBend = errors.New("invalid MIDI pitch bend value")
errInvalidMIDISysExData = errors.New("invalid MIDI SysEx data")
)
// NoteOn sends a channel note on message.
// The cable parameter is the cable number 0-15.
// The channel parameter is the MIDI channel number 1-16.
func (m *midi) NoteOn(cable, channel uint8, note Note, velocity uint8) error {
switch {
case cable > 15:
return errInvalidMIDICable
case channel == 0 || channel > 16:
return errInvalidMIDIChannel
case velocity > 127:
return errInvalidMIDIVelocity
}
m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINNoteOn, MsgNoteOn|(channel-1&0xf), byte(note)&0x7f, velocity&0x7f
_, err := m.Write(m.msg[:])
return err
}
// NoteOff sends a note off message.
func (m *midi) NoteOff(cable, channel uint8, note Note, velocity uint8) {
m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|0x8, 0x80|(channel&0xf), byte(note)&0x7f, velocity&0x7f
m.Write(m.msg[:])
// NoteOff sends a channel note off message.
// The cable parameter is the cable number 0-15.
// The channel parameter is the MIDI channel number 1-16.
func (m *midi) NoteOff(cable, channel uint8, note Note, velocity uint8) error {
switch {
case cable > 15:
return errInvalidMIDICable
case channel == 0 || channel > 16:
return errInvalidMIDIChannel
case velocity > 127:
return errInvalidMIDIVelocity
}
m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINNoteOff, MsgNoteOff|(channel-1&0xf), byte(note)&0x7f, velocity&0x7f
_, err := m.Write(m.msg[:])
return err
}
// SendCC sends a continuous controller message.
func (m *midi) SendCC(cable, channel, control, value uint8) {
m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|0xB, 0xB0|(channel&0xf), control&0x7f, value&0x7f
m.Write(m.msg[:])
// ControlChange sends a channel continuous controller message.
// The cable parameter is the cable number 0-15.
// The channel parameter is the MIDI channel number 1-16.
// The control parameter is the controller number 0-127.
// The value parameter is the controller value 0-127.
func (m *midi) ControlChange(cable, channel, control, value uint8) error {
switch {
case cable > 15:
return errInvalidMIDICable
case channel == 0 || channel > 16:
return errInvalidMIDIChannel
case control > 127:
return errInvalidMIDIControl
case value > 127:
return errInvalidMIDIControlValue
}
m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINControlChange, MsgControlChange|(channel-1&0xf), control&0x7f, value&0x7f
_, err := m.Write(m.msg[:])
return err
}
// ProgramChange sends a channel program change message.
// The cable parameter is the cable number 0-15.
// The channel parameter is the MIDI channel number 1-16.
// The patch parameter is the program number 0-127.
func (m *midi) ProgramChange(cable, channel uint8, patch uint8) error {
switch {
case cable > 15:
return errInvalidMIDICable
case channel == 0 || channel > 16:
return errInvalidMIDIChannel
case patch > 127:
return errInvalidMIDIPatch
}
m.msg[0], m.msg[1], m.msg[2] = (cable&0xf<<4)|CINProgramChange, MsgProgramChange|(channel-1&0xf), patch&0x7f
_, err := m.Write(m.msg[:3])
return err
}
// PitchBend sends a channel pitch bend message.
// The cable parameter is the cable number 0-15.
// The channel parameter is the MIDI channel number 1-16.
// The bend parameter is the 14-bit pitch bend value (maximum 0x3FFF).
// Setting bend above 0x2000 (up to 0x3FFF) will increase the pitch.
// Setting bend below 0x2000 (down to 0x0000) will decrease the pitch.
func (m *midi) PitchBend(cable, channel uint8, bend uint16) error {
switch {
case cable > 15:
return errInvalidMIDICable
case channel == 0 || channel > 16:
return errInvalidMIDIChannel
case bend > 0x3FFF:
return errInvalidMIDIPitchBend
}
m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINPitchBendChange, MsgPitchBend|(channel-1&0xf), byte(bend&0x7f), byte(bend>>8)&0x7f
_, err := m.Write(m.msg[:])
return err
}
// SysEx sends a System Exclusive message.
// The cable parameter is the cable number 0-15.
// The data parameter is a slice with the data to send.
// It needs to start with the manufacturer ID, which is either
// 1 or 3 bytes in length.
// The data slice should not include the SysEx start (0xF0) or
// end (0xF7) bytes, only the data in between.
func (m *midi) SysEx(cable uint8, data []byte) error {
switch {
case cable > 15:
return errInvalidMIDICable
case len(data) < 3:
return errInvalidMIDISysExData
}
// write start
m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExStart, MsgSysExStart
m.msg[2], m.msg[3] = data[0], data[1]
if _, err := m.Write(m.msg[:]); err != nil {
return err
}
// write middle
i := 2
for ; i < len(data)-2; i += 3 {
m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExStart, data[i]
m.msg[2], m.msg[3] = data[i+1], data[i+2]
if _, err := m.Write(m.msg[:]); err != nil {
return err
}
}
// write end
switch len(data) - i {
case 2:
m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd3, data[i]
m.msg[2], m.msg[3] = data[i+1], MsgSysExEnd
case 1:
m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd2, data[i]
m.msg[2], m.msg[3] = MsgSysExEnd, 0
case 0:
m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd1, MsgSysExEnd
m.msg[2], m.msg[3] = 0, 0
}
if _, err := m.Write(m.msg[:]); err != nil {
return err
}
return nil
}

Просмотреть файл

@ -17,6 +17,7 @@ type midi struct {
msg [4]byte
buf *RingBuffer
rxHandler func([]byte)
txHandler func()
waitTxc bool
}
@ -53,7 +54,7 @@ func newMidi() *midi {
Index: usb.MIDI_ENDPOINT_IN,
IsIn: true,
Type: usb.ENDPOINT_TYPE_BULK,
TxHandler: m.Handler,
TxHandler: m.TxHandler,
},
},
[]usb.SetupConfig{},
@ -61,16 +62,32 @@ func newMidi() *midi {
return m
}
// SetHandler is now deprecated, please use SetRxHandler().
func (m *midi) SetHandler(rxHandler func([]byte)) {
m.SetRxHandler(rxHandler)
}
// SetRxHandler sets the handler function for incoming MIDI messages.
func (m *midi) SetRxHandler(rxHandler func([]byte)) {
m.rxHandler = rxHandler
}
// SetTxHandler sets the handler function for outgoing MIDI messages.
func (m *midi) SetTxHandler(txHandler func()) {
m.txHandler = txHandler
}
func (m *midi) Write(b []byte) (n int, err error) {
i := 0
for i = 0; i < len(b); i += 4 {
m.tx(b[i : i+4])
s, e := 0, 0
for s = 0; s < len(b); s += 4 {
e = s + 4
if e > len(b) {
e = len(b)
}
m.tx(b[s:e])
}
return i, nil
return e, nil
}
// sendUSBPacket sends a MIDIPacket.
@ -79,7 +96,11 @@ func (m *midi) sendUSBPacket(b []byte) {
}
// from BulkIn
func (m *midi) Handler() {
func (m *midi) TxHandler() {
if m.txHandler != nil {
m.txHandler()
}
m.waitTxc = false
if b, ok := m.buf.Get(); ok {
m.waitTxc = true