tinygo/src/machine/usb/adc/midi/messages.go
deadprogram 9d6eb1ff06 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>
2023-09-07 08:41:57 +02:00

233 строки
7,1 КиБ
Go

package midi
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 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
}
// 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
}