|
1 | 1 | package midi |
2 | 2 |
|
3 | | -// NoteOn sends a note on message. |
4 | | -func (m *midi) NoteOn(cable, channel uint8, note Note, velocity uint8) { |
5 | | - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|0x9, 0x90|(channel&0xf), byte(note)&0x7f, velocity&0x7f |
6 | | - m.Write(m.msg[:]) |
| 3 | +import ( |
| 4 | + "errors" |
| 5 | +) |
| 6 | + |
| 7 | +// From USB-MIDI section 4.1 "Code Index Number (CIN) Classifications" |
| 8 | +const ( |
| 9 | + CINSystemCommon2 = 0x2 |
| 10 | + CINSystemCommon3 = 0x3 |
| 11 | + CINSysExStart = 0x4 |
| 12 | + CINSysExEnd1 = 0x5 |
| 13 | + CINSysExEnd2 = 0x6 |
| 14 | + CINSysExEnd3 = 0x7 |
| 15 | + CINNoteOff = 0x8 |
| 16 | + CINNoteOn = 0x9 |
| 17 | + CINPoly = 0xA |
| 18 | + CINControlChange = 0xB |
| 19 | + CINProgramChange = 0xC |
| 20 | + CINChannelPressure = 0xD |
| 21 | + CINPitchBendChange = 0xE |
| 22 | + CINSingleByte = 0xF |
| 23 | +) |
| 24 | + |
| 25 | +// Standard MIDI channel messages |
| 26 | +const ( |
| 27 | + MsgNoteOff = 0x80 |
| 28 | + MsgNoteOn = 0x90 |
| 29 | + MsgPolyAftertouch = 0xA0 |
| 30 | + MsgControlChange = 0xB0 |
| 31 | + MsgProgramChange = 0xC0 |
| 32 | + MsgChannelAftertouch = 0xD0 |
| 33 | + MsgPitchBend = 0xE0 |
| 34 | + MsgSysExStart = 0xF0 |
| 35 | + MsgSysExEnd = 0xF7 |
| 36 | +) |
| 37 | + |
| 38 | +// Standard MIDI control change messages |
| 39 | +const ( |
| 40 | + CCModulationWheel = 0x01 |
| 41 | + CCBreathController = 0x02 |
| 42 | + CCFootPedal = 0x04 |
| 43 | + CCPortamentoTime = 0x05 |
| 44 | + CCDataEntry = 0x06 |
| 45 | + CCVolume = 0x07 |
| 46 | + CCBalance = 0x08 |
| 47 | + CCPan = 0x0A |
| 48 | + CCExpression = 0x0B |
| 49 | + CCEffectControl1 = 0x0C |
| 50 | + CCEffectControl2 = 0x0D |
| 51 | + CCGeneralPurpose1 = 0x10 |
| 52 | + CCGeneralPurpose2 = 0x11 |
| 53 | + CCGeneralPurpose3 = 0x12 |
| 54 | + CCGeneralPurpose4 = 0x13 |
| 55 | + CCBankSelect = 0x20 |
| 56 | + CCModulationDepthRange = 0x21 |
| 57 | + CCBreathControllerDepth = 0x22 |
| 58 | + CCFootPedalDepth = 0x24 |
| 59 | + CCEffectsLevel = 0x5B |
| 60 | + CCTremeloLevel = 0x5C |
| 61 | + CCChorusLevel = 0x5D |
| 62 | + CCCelesteLevel = 0x5E |
| 63 | + CCPhaserLevel = 0x5F |
| 64 | + CCDataIncrement = 0x60 |
| 65 | + CCDataDecrement = 0x61 |
| 66 | + CCNRPNLSB = 0x62 |
| 67 | + CCNRPNMSB = 0x63 |
| 68 | + CCRPNLSB = 0x64 |
| 69 | + CCRPNMSB = 0x65 |
| 70 | + CCAllSoundOff = 0x78 |
| 71 | + CCResetAllControllers = 0x79 |
| 72 | + CCAllNotesOff = 0x7B |
| 73 | + CCChannelVolume = 0x7F |
| 74 | +) |
| 75 | + |
| 76 | +var ( |
| 77 | + errInvalidMIDICable = errors.New("invalid MIDI cable") |
| 78 | + errInvalidMIDIChannel = errors.New("invalid MIDI channel") |
| 79 | + errInvalidMIDIVelocity = errors.New("invalid MIDI velocity") |
| 80 | + errInvalidMIDIControl = errors.New("invalid MIDI control number") |
| 81 | + errInvalidMIDIControlValue = errors.New("invalid MIDI control value") |
| 82 | + errInvalidMIDIPatch = errors.New("invalid MIDI patch number") |
| 83 | + errInvalidMIDIPitchBend = errors.New("invalid MIDI pitch bend value") |
| 84 | + errInvalidMIDISysExData = errors.New("invalid MIDI SysEx data") |
| 85 | +) |
| 86 | + |
| 87 | +// NoteOn sends a channel note on message. |
| 88 | +// The cable parameter is the cable number 0-15. |
| 89 | +// The channel parameter is the MIDI channel number 1-16. |
| 90 | +func (m *midi) NoteOn(cable, channel uint8, note Note, velocity uint8) error { |
| 91 | + switch { |
| 92 | + case cable > 15: |
| 93 | + return errInvalidMIDICable |
| 94 | + case channel == 0 || channel > 16: |
| 95 | + return errInvalidMIDIChannel |
| 96 | + case velocity > 127: |
| 97 | + return errInvalidMIDIVelocity |
| 98 | + } |
| 99 | + |
| 100 | + 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 |
| 101 | + _, err := m.Write(m.msg[:]) |
| 102 | + return err |
7 | 103 | } |
8 | 104 |
|
9 | | -// NoteOff sends a note off message. |
10 | | -func (m *midi) NoteOff(cable, channel uint8, note Note, velocity uint8) { |
11 | | - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|0x8, 0x80|(channel&0xf), byte(note)&0x7f, velocity&0x7f |
12 | | - m.Write(m.msg[:]) |
| 105 | +// NoteOff sends a channel note off message. |
| 106 | +// The cable parameter is the cable number 0-15. |
| 107 | +// The channel parameter is the MIDI channel number 1-16. |
| 108 | +func (m *midi) NoteOff(cable, channel uint8, note Note, velocity uint8) error { |
| 109 | + switch { |
| 110 | + case cable > 15: |
| 111 | + return errInvalidMIDICable |
| 112 | + case channel == 0 || channel > 16: |
| 113 | + return errInvalidMIDIChannel |
| 114 | + case velocity > 127: |
| 115 | + return errInvalidMIDIVelocity |
| 116 | + } |
| 117 | + |
| 118 | + 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 |
| 119 | + _, err := m.Write(m.msg[:]) |
| 120 | + return err |
| 121 | +} |
| 122 | + |
| 123 | +// ControlChange sends a channel continuous controller message. |
| 124 | +// The cable parameter is the cable number 0-15. |
| 125 | +// The channel parameter is the MIDI channel number 1-16. |
| 126 | +// The control parameter is the controller number 0-127. |
| 127 | +// The value parameter is the controller value 0-127. |
| 128 | +func (m *midi) ControlChange(cable, channel, control, value uint8) error { |
| 129 | + switch { |
| 130 | + case cable > 15: |
| 131 | + return errInvalidMIDICable |
| 132 | + case channel == 0 || channel > 16: |
| 133 | + return errInvalidMIDIChannel |
| 134 | + case control > 127: |
| 135 | + return errInvalidMIDIControl |
| 136 | + case value > 127: |
| 137 | + return errInvalidMIDIControlValue |
| 138 | + } |
| 139 | + |
| 140 | + m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|CINControlChange, MsgControlChange|(channel-1&0xf), control&0x7f, value&0x7f |
| 141 | + _, err := m.Write(m.msg[:]) |
| 142 | + return err |
13 | 143 | } |
14 | 144 |
|
15 | | -// SendCC sends a continuous controller message. |
16 | | -func (m *midi) SendCC(cable, channel, control, value uint8) { |
17 | | - m.msg[0], m.msg[1], m.msg[2], m.msg[3] = (cable&0xf<<4)|0xB, 0xB0|(channel&0xf), control&0x7f, value&0x7f |
18 | | - m.Write(m.msg[:]) |
| 145 | +// ProgramChange sends a channel program change message. |
| 146 | +// The cable parameter is the cable number 0-15. |
| 147 | +// The channel parameter is the MIDI channel number 1-16. |
| 148 | +// The patch parameter is the program number 0-127. |
| 149 | +func (m *midi) ProgramChange(cable, channel uint8, patch uint8) error { |
| 150 | + switch { |
| 151 | + case cable > 15: |
| 152 | + return errInvalidMIDICable |
| 153 | + case channel == 0 || channel > 16: |
| 154 | + return errInvalidMIDIChannel |
| 155 | + case patch > 127: |
| 156 | + return errInvalidMIDIPatch |
| 157 | + } |
| 158 | + |
| 159 | + m.msg[0], m.msg[1], m.msg[2] = (cable&0xf<<4)|CINProgramChange, MsgProgramChange|(channel-1&0xf), patch&0x7f |
| 160 | + _, err := m.Write(m.msg[:3]) |
| 161 | + return err |
| 162 | +} |
| 163 | + |
| 164 | +// PitchBend sends a channel pitch bend message. |
| 165 | +// The cable parameter is the cable number 0-15. |
| 166 | +// The channel parameter is the MIDI channel number 1-16. |
| 167 | +// The bend parameter is the 14-bit pitch bend value (maximum 0x3FFF). |
| 168 | +// Setting bend above 0x2000 (up to 0x3FFF) will increase the pitch. |
| 169 | +// Setting bend below 0x2000 (down to 0x0000) will decrease the pitch. |
| 170 | +func (m *midi) PitchBend(cable, channel uint8, bend uint16) error { |
| 171 | + switch { |
| 172 | + case cable > 15: |
| 173 | + return errInvalidMIDICable |
| 174 | + case channel == 0 || channel > 16: |
| 175 | + return errInvalidMIDIChannel |
| 176 | + case bend > 0x3FFF: |
| 177 | + return errInvalidMIDIPitchBend |
| 178 | + } |
| 179 | + |
| 180 | + 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 |
| 181 | + _, err := m.Write(m.msg[:]) |
| 182 | + return err |
| 183 | +} |
| 184 | + |
| 185 | +// SysEx sends a System Exclusive message. |
| 186 | +// The cable parameter is the cable number 0-15. |
| 187 | +// The data parameter is a slice with the data to send. |
| 188 | +// It needs to start with the manufacturer ID, which is either |
| 189 | +// 1 or 3 bytes in length. |
| 190 | +// The data slice should not include the SysEx start (0xF0) or |
| 191 | +// end (0xF7) bytes, only the data in between. |
| 192 | +func (m *midi) SysEx(cable uint8, data []byte) error { |
| 193 | + switch { |
| 194 | + case cable > 15: |
| 195 | + return errInvalidMIDICable |
| 196 | + case len(data) < 3: |
| 197 | + return errInvalidMIDISysExData |
| 198 | + } |
| 199 | + |
| 200 | + // write start |
| 201 | + m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExStart, MsgSysExStart |
| 202 | + m.msg[2], m.msg[3] = data[0], data[1] |
| 203 | + if _, err := m.Write(m.msg[:]); err != nil { |
| 204 | + return err |
| 205 | + } |
| 206 | + |
| 207 | + // write middle |
| 208 | + i := 2 |
| 209 | + for ; i < len(data)-2; i += 3 { |
| 210 | + m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExStart, data[i] |
| 211 | + m.msg[2], m.msg[3] = data[i+1], data[i+2] |
| 212 | + if _, err := m.Write(m.msg[:]); err != nil { |
| 213 | + return err |
| 214 | + } |
| 215 | + } |
| 216 | + // write end |
| 217 | + switch len(data) - i { |
| 218 | + case 2: |
| 219 | + m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd3, data[i] |
| 220 | + m.msg[2], m.msg[3] = data[i+1], MsgSysExEnd |
| 221 | + case 1: |
| 222 | + m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd2, data[i] |
| 223 | + m.msg[2], m.msg[3] = MsgSysExEnd, 0 |
| 224 | + case 0: |
| 225 | + m.msg[0], m.msg[1] = (cable&0xf<<4)|CINSysExEnd1, MsgSysExEnd |
| 226 | + m.msg[2], m.msg[3] = 0, 0 |
| 227 | + } |
| 228 | + if _, err := m.Write(m.msg[:]); err != nil { |
| 229 | + return err |
| 230 | + } |
| 231 | + |
| 232 | + return nil |
19 | 233 | } |
0 commit comments