-
Notifications
You must be signed in to change notification settings - Fork 917
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
machine/usb/adc/midi: improve implementation to include several new m…
…essages 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 <[email protected]>
- Loading branch information
1 parent
4643401
commit 9d6eb1f
Showing
3 changed files
with
278 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
|
||
// 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 | ||
} | ||
|
||
// 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[:]) | ||
// 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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters