pymididefs 0.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
pymididefs/__init__.py ADDED
@@ -0,0 +1,34 @@
1
+ """PyMidiDefs — comprehensive MIDI 1.0 and 2.0 constant definitions for Python.
2
+
3
+ A zero-dependency reference library covering the MIDI 1.0 Detailed Specification,
4
+ General MIDI Level 1, Universal MIDI Packet (UMP) format, and MIDI-CI.
5
+
6
+ Modules
7
+ -------
8
+ notes — MIDI note numbers and name/number conversion.
9
+ cc — Control Change number assignments and 14-bit pack/unpack helpers.
10
+ rpn — Standard Registered Parameter Numbers and the 14-bit parameter conventions shared with NRPN.
11
+ drums — General MIDI Level 1 percussion key map.
12
+ gm — General MIDI Level 1 instrument program numbers and families.
13
+ status — MIDI 1.0 status bytes (channel voice, system common, system real-time).
14
+ meta — Standard MIDI File meta-event type bytes.
15
+ ump — MIDI 2.0 Universal MIDI Packet message types and constants.
16
+ ci — MIDI 2.0 Capability Inquiry (MIDI-CI) constants.
17
+ """
18
+
19
+ import importlib.metadata
20
+
21
+ import pymididefs.notes
22
+
23
+ # Version is derived from the latest git tag at build time via hatch-vcs;
24
+ # `importlib.metadata` then reads it from the installed package metadata.
25
+ # The fallback only fires if someone runs from a raw source checkout without
26
+ # installing the package (e.g. directly from a git clone with no `pip install`).
27
+ try:
28
+ __version__ = importlib.metadata.version("pymididefs")
29
+ except importlib.metadata.PackageNotFoundError:
30
+ __version__ = "0.0.0+unknown"
31
+
32
+ # Convenience re-exports for the most common operations.
33
+ note_to_name = pymididefs.notes.note_to_name
34
+ name_to_note = pymididefs.notes.name_to_note
pymididefs/cc.py ADDED
@@ -0,0 +1,284 @@
1
+ """MIDI Control Change (CC) number assignments.
2
+
3
+ The MIDI specification defines 128 controller numbers (0–127). This module
4
+ provides named constants for all controllers that have a defined function in
5
+ the MIDI 1.0 Detailed Specification. Undefined/reserved numbers (3, 9, 14–15,
6
+ 20–31, 85–90, 102–119) are omitted — they are available for general-purpose
7
+ use and do not have standard names.
8
+
9
+ Two ways to use this module::
10
+
11
+ import pymididefs.cc
12
+
13
+ # As named constants
14
+ pymididefs.cc.SUSTAIN_PEDAL # 64
15
+
16
+ # As a lookup dictionary
17
+ pymididefs.cc.CC_MAP["sustain_pedal"] # 64
18
+
19
+ Source: MIDI 1.0 Detailed Specification, Table III — Control Change Messages.
20
+ """
21
+
22
+ import typing
23
+
24
+
25
+ # ── MSB Controllers (0–31) ───────────────────────────────────────────────────
26
+ # High-resolution continuous controllers — most significant byte.
27
+ # Controllers 0–31 each have a corresponding LSB at CC 32–63.
28
+
29
+ BANK_SELECT_MSB = 0 # Bank Select
30
+ MODULATION_WHEEL = 1 # Modulation Wheel or Lever
31
+ BREATH_CONTROLLER = 2 # Breath Controller
32
+ # CC 3: Undefined
33
+ FOOT_CONTROLLER = 4 # Foot Controller
34
+ PORTAMENTO_TIME = 5 # Portamento Time
35
+ DATA_ENTRY_MSB = 6 # Data Entry MSB
36
+ VOLUME = 7 # Channel Volume (formerly Main Volume)
37
+ BALANCE = 8 # Balance
38
+ # CC 9: Undefined
39
+ PAN = 10 # Pan
40
+ EXPRESSION = 11 # Expression Controller
41
+ EFFECT_CONTROL_1 = 12 # Effect Control 1
42
+ EFFECT_CONTROL_2 = 13 # Effect Control 2
43
+ # CC 14–15: Undefined
44
+ GENERAL_PURPOSE_1 = 16 # General Purpose Controller 1
45
+ GENERAL_PURPOSE_2 = 17 # General Purpose Controller 2
46
+ GENERAL_PURPOSE_3 = 18 # General Purpose Controller 3
47
+ GENERAL_PURPOSE_4 = 19 # General Purpose Controller 4
48
+ # CC 20–31: Undefined
49
+
50
+
51
+ # ── LSB Controllers (32–63) ──────────────────────────────────────────────────
52
+ # Low-resolution counterparts for CC 0–31.
53
+ # Only the most commonly referenced LSB values are named here.
54
+
55
+ BANK_SELECT_LSB = 32 # Bank Select LSB
56
+ MODULATION_WHEEL_LSB = 33 # Modulation Wheel LSB
57
+ BREATH_CONTROLLER_LSB = 34 # Breath Controller LSB
58
+ # CC 35: Undefined LSB
59
+ FOOT_CONTROLLER_LSB = 36 # Foot Controller LSB
60
+ PORTAMENTO_TIME_LSB = 37 # Portamento Time LSB
61
+ DATA_ENTRY_LSB = 38 # Data Entry LSB
62
+ VOLUME_LSB = 39 # Channel Volume LSB
63
+ BALANCE_LSB = 40 # Balance LSB
64
+ # CC 41: Undefined LSB
65
+ PAN_LSB = 42 # Pan LSB
66
+ EXPRESSION_LSB = 43 # Expression LSB
67
+ EFFECT_CONTROL_1_LSB = 44 # Effect Control 1 LSB
68
+ EFFECT_CONTROL_2_LSB = 45 # Effect Control 2 LSB
69
+
70
+
71
+ # ── Switch Controllers (64–69) ───────────────────────────────────────────────
72
+ # On/off pedals and switches. Values 0–63 = Off, 64–127 = On.
73
+
74
+ SUSTAIN_PEDAL = 64 # Damper Pedal (Sustain)
75
+ PORTAMENTO_ON_OFF = 65 # Portamento On/Off
76
+ SOSTENUTO_PEDAL = 66 # Sostenuto
77
+ SOFT_PEDAL = 67 # Soft Pedal
78
+ LEGATO_PEDAL = 68 # Legato Footswitch
79
+ HOLD_2 = 69 # Hold 2
80
+
81
+
82
+ # ── Sound Controllers (70–79) ────────────────────────────────────────────────
83
+ # Defined by General MIDI Level 2 for real-time timbre adjustment.
84
+
85
+ SOUND_VARIATION = 70 # Sound Controller 1 (default: Sound Variation)
86
+ FILTER_RESONANCE = 71 # Sound Controller 2 (default: Timbre / Filter Resonance)
87
+ RELEASE_TIME = 72 # Sound Controller 3 (default: Release Time)
88
+ ATTACK_TIME = 73 # Sound Controller 4 (default: Attack Time)
89
+ FILTER_CUTOFF = 74 # Sound Controller 5 (default: Brightness / Filter Cutoff)
90
+ SOUND_CONTROL_6 = 75 # Sound Controller 6 (default: Decay Time — GM2)
91
+ SOUND_CONTROL_7 = 76 # Sound Controller 7 (default: Vibrato Rate — GM2)
92
+ SOUND_CONTROL_8 = 77 # Sound Controller 8 (default: Vibrato Depth — GM2)
93
+ SOUND_CONTROL_9 = 78 # Sound Controller 9 (default: Vibrato Delay — GM2)
94
+ SOUND_CONTROL_10 = 79 # Sound Controller 10 (default: undefined)
95
+
96
+
97
+ # ── General Purpose Controllers (80–83) ──────────────────────────────────────
98
+
99
+ GENERAL_PURPOSE_5 = 80 # General Purpose Controller 5
100
+ GENERAL_PURPOSE_6 = 81 # General Purpose Controller 6
101
+ GENERAL_PURPOSE_7 = 82 # General Purpose Controller 7
102
+ GENERAL_PURPOSE_8 = 83 # General Purpose Controller 8
103
+
104
+
105
+ # ── Effects Send Levels (91–95) ──────────────────────────────────────────────
106
+ # CC 84 is Portamento Control; CC 85–90 are undefined.
107
+
108
+ PORTAMENTO_CONTROL = 84 # Portamento Control (source note for portamento)
109
+ # CC 85–90: Undefined
110
+ REVERB_DEPTH = 91 # Effects 1 Depth (default: Reverb Send Level)
111
+ TREMOLO_DEPTH = 92 # Effects 2 Depth (default: Tremolo Depth)
112
+ CHORUS_DEPTH = 93 # Effects 3 Depth (default: Chorus Send Level)
113
+ CELESTE_DEPTH = 94 # Effects 4 Depth (default: Celeste / Detune Depth)
114
+ PHASER_DEPTH = 95 # Effects 5 Depth (default: Phaser Depth)
115
+
116
+
117
+ # ── Parameter Control (96–101) ───────────────────────────────────────────────
118
+ # Used for RPN / NRPN parameter addressing and data manipulation.
119
+
120
+ DATA_INCREMENT = 96 # Data Increment (Data Entry +1)
121
+ DATA_DECREMENT = 97 # Data Decrement (Data Entry -1)
122
+ NRPN_LSB = 98 # Non-Registered Parameter Number LSB
123
+ NRPN_MSB = 99 # Non-Registered Parameter Number MSB
124
+ RPN_LSB = 100 # Registered Parameter Number LSB
125
+ RPN_MSB = 101 # Registered Parameter Number MSB
126
+
127
+ # CC 102–119: Undefined
128
+
129
+
130
+ # ── Channel Mode Messages (120–127) ─────────────────────────────────────────
131
+ # These use the Control Change status byte but function as channel mode
132
+ # commands rather than continuous controllers.
133
+
134
+ ALL_SOUND_OFF = 120 # All Sound Off (value = 0)
135
+ RESET_ALL_CONTROLLERS = 121 # Reset All Controllers (value = 0)
136
+ LOCAL_CONTROL_ON_OFF = 122 # Local Control On/Off (0 = Off, 127 = On)
137
+ ALL_NOTES_OFF = 123 # All Notes Off (value = 0)
138
+ OMNI_MODE_OFF = 124 # Omni Mode Off (+ All Notes Off)
139
+ OMNI_MODE_ON = 125 # Omni Mode On (+ All Notes Off)
140
+ MONO_MODE_ON = 126 # Mono Mode On (+ All Notes Off)
141
+ POLY_MODE_ON = 127 # Poly Mode On (+ All Notes Off)
142
+
143
+
144
+ # ── Lookup dictionary ────────────────────────────────────────────────────────
145
+ # Maps snake_case names to CC numbers for string-based access.
146
+
147
+ CC_MAP: typing.Final[dict[str, int]] = {
148
+ # MSB controllers
149
+ "bank_select_msb": BANK_SELECT_MSB,
150
+ "modulation_wheel": MODULATION_WHEEL,
151
+ "breath_controller": BREATH_CONTROLLER,
152
+ "foot_controller": FOOT_CONTROLLER,
153
+ "portamento_time": PORTAMENTO_TIME,
154
+ "data_entry_msb": DATA_ENTRY_MSB,
155
+ "volume": VOLUME,
156
+ "balance": BALANCE,
157
+ "pan": PAN,
158
+ "expression": EXPRESSION,
159
+ "effect_control_1": EFFECT_CONTROL_1,
160
+ "effect_control_2": EFFECT_CONTROL_2,
161
+ "general_purpose_1": GENERAL_PURPOSE_1,
162
+ "general_purpose_2": GENERAL_PURPOSE_2,
163
+ "general_purpose_3": GENERAL_PURPOSE_3,
164
+ "general_purpose_4": GENERAL_PURPOSE_4,
165
+
166
+ # LSB controllers
167
+ "bank_select_lsb": BANK_SELECT_LSB,
168
+ "modulation_wheel_lsb": MODULATION_WHEEL_LSB,
169
+ "breath_controller_lsb": BREATH_CONTROLLER_LSB,
170
+ "foot_controller_lsb": FOOT_CONTROLLER_LSB,
171
+ "portamento_time_lsb": PORTAMENTO_TIME_LSB,
172
+ "data_entry_lsb": DATA_ENTRY_LSB,
173
+ "volume_lsb": VOLUME_LSB,
174
+ "balance_lsb": BALANCE_LSB,
175
+ "pan_lsb": PAN_LSB,
176
+ "expression_lsb": EXPRESSION_LSB,
177
+ "effect_control_1_lsb": EFFECT_CONTROL_1_LSB,
178
+ "effect_control_2_lsb": EFFECT_CONTROL_2_LSB,
179
+
180
+ # Switch controllers
181
+ "sustain_pedal": SUSTAIN_PEDAL,
182
+ "portamento_on_off": PORTAMENTO_ON_OFF,
183
+ "sostenuto_pedal": SOSTENUTO_PEDAL,
184
+ "soft_pedal": SOFT_PEDAL,
185
+ "legato_pedal": LEGATO_PEDAL,
186
+ "hold_2": HOLD_2,
187
+
188
+ # Sound controllers
189
+ "sound_variation": SOUND_VARIATION,
190
+ "filter_resonance": FILTER_RESONANCE,
191
+ "release_time": RELEASE_TIME,
192
+ "attack_time": ATTACK_TIME,
193
+ "filter_cutoff": FILTER_CUTOFF,
194
+ "sound_control_6": SOUND_CONTROL_6,
195
+ "sound_control_7": SOUND_CONTROL_7,
196
+ "sound_control_8": SOUND_CONTROL_8,
197
+ "sound_control_9": SOUND_CONTROL_9,
198
+ "sound_control_10": SOUND_CONTROL_10,
199
+
200
+ # General purpose controllers
201
+ "general_purpose_5": GENERAL_PURPOSE_5,
202
+ "general_purpose_6": GENERAL_PURPOSE_6,
203
+ "general_purpose_7": GENERAL_PURPOSE_7,
204
+ "general_purpose_8": GENERAL_PURPOSE_8,
205
+
206
+ # Effects send levels
207
+ "portamento_control": PORTAMENTO_CONTROL,
208
+ "reverb_depth": REVERB_DEPTH,
209
+ "tremolo_depth": TREMOLO_DEPTH,
210
+ "chorus_depth": CHORUS_DEPTH,
211
+ "celeste_depth": CELESTE_DEPTH,
212
+ "phaser_depth": PHASER_DEPTH,
213
+
214
+ # Parameter control
215
+ "data_increment": DATA_INCREMENT,
216
+ "data_decrement": DATA_DECREMENT,
217
+ "nrpn_lsb": NRPN_LSB,
218
+ "nrpn_msb": NRPN_MSB,
219
+ "rpn_lsb": RPN_LSB,
220
+ "rpn_msb": RPN_MSB,
221
+
222
+ # Channel mode messages
223
+ "all_sound_off": ALL_SOUND_OFF,
224
+ "reset_all_controllers": RESET_ALL_CONTROLLERS,
225
+ "local_control_on_off": LOCAL_CONTROL_ON_OFF,
226
+ "all_notes_off": ALL_NOTES_OFF,
227
+ "omni_mode_off": OMNI_MODE_OFF,
228
+ "omni_mode_on": OMNI_MODE_ON,
229
+ "mono_mode_on": MONO_MODE_ON,
230
+ "poly_mode_on": POLY_MODE_ON,
231
+ }
232
+
233
+
234
+ # ── 14-bit value pack/unpack ─────────────────────────────────────────────────
235
+ # Many MIDI 1.0 message types — Bank Select, Data Entry, RPN/NRPN parameter
236
+ # numbers, pitch bend, song position pointer — split a 14-bit value across
237
+ # two 7-bit MSB/LSB bytes. These helpers convert between the integer form
238
+ # and the wire-format byte pair.
239
+
240
+ def pack_14bit (value: int) -> tuple[int, int]:
241
+
242
+ """Split a 14-bit integer (0–16383) into ``(MSB, LSB)`` bytes.
243
+
244
+ Each returned byte is in the 7-bit range 0–127. The tuple ordering
245
+ ``(MSB, LSB)`` is the logical pairing — wire-transmission order is
246
+ message-dependent (Bank Select and RPN/NRPN parameter selection send MSB
247
+ first, but pitch bend sends LSB first).
248
+
249
+ >>> pack_14bit(0)
250
+ (0, 0)
251
+ >>> pack_14bit(16383)
252
+ (127, 127)
253
+ >>> pack_14bit(8192)
254
+ (64, 0)
255
+ """
256
+
257
+ if not 0 <= value <= 16383:
258
+ raise ValueError(
259
+ f"14-bit value must be 0–16383, got {value}"
260
+ )
261
+
262
+ return (value >> 7) & 0x7F, value & 0x7F
263
+
264
+
265
+ def unpack_14bit (msb: int, lsb: int) -> int:
266
+
267
+ """Combine an ``(MSB, LSB)`` byte pair into a 14-bit integer (0–16383).
268
+
269
+ Both inputs must be in the 7-bit range 0–127.
270
+
271
+ >>> unpack_14bit(0, 0)
272
+ 0
273
+ >>> unpack_14bit(127, 127)
274
+ 16383
275
+ >>> unpack_14bit(64, 0)
276
+ 8192
277
+ """
278
+
279
+ if not 0 <= msb <= 127:
280
+ raise ValueError(f"MSB must be 0–127, got {msb}")
281
+ if not 0 <= lsb <= 127:
282
+ raise ValueError(f"LSB must be 0–127, got {lsb}")
283
+
284
+ return (msb << 7) | lsb
pymididefs/ci.py ADDED
@@ -0,0 +1,79 @@
1
+ """MIDI 2.0 Capability Inquiry (MIDI-CI) constants.
2
+
3
+ MIDI-CI enables bidirectional communication between MIDI devices for
4
+ capability discovery, profile configuration, and property exchange.
5
+ MIDI-CI messages are transported as Universal System Exclusive (Non-Real-Time).
6
+
7
+ ::
8
+
9
+ import pymididefs.ci
10
+ pymididefs.ci.DISCOVERY # 0x70
11
+ pymididefs.ci.CI_SUB_ID # 0x0D
12
+
13
+ Source: M2-101-UM v1.2 — MIDI-CI Specification.
14
+ """
15
+
16
+
17
+ # ── Universal SysEx Framing ──────────────────────────────────────────────────
18
+ # MIDI-CI messages are wrapped in Universal Non-Real-Time SysEx:
19
+ # F0 7E <device_id> 0D <sub_id_2> <data...> F7
20
+
21
+ UNIVERSAL_NON_REALTIME = 0x7E # Universal Non-Real-Time SysEx ID byte
22
+ UNIVERSAL_REALTIME = 0x7F # Universal Real-Time SysEx ID byte
23
+ CI_SUB_ID = 0x0D # MIDI-CI Sub-ID #1 (identifies CI messages)
24
+ BROADCAST_ADDRESS = 0x7F # Device ID for broadcast (all devices on port)
25
+
26
+
27
+ # ── MIDI-CI Message Types (Sub-ID #2) ────────────────────────────────────────
28
+ # These identify the specific MIDI-CI message within the SysEx payload.
29
+
30
+ # Management messages
31
+ DISCOVERY = 0x70 # Discovery Inquiry
32
+ DISCOVERY_REPLY = 0x71 # Reply to Discovery
33
+ INVALIDATE_MUID = 0x7E # Invalidate MUID (device going offline)
34
+ NAK = 0x7F # NAK (negative acknowledgement)
35
+
36
+ # Profile Configuration messages
37
+ PROFILE_INQUIRY = 0x20 # Profile Inquiry
38
+ PROFILE_INQUIRY_REPLY = 0x21 # Reply to Profile Inquiry
39
+ SET_PROFILE_ON = 0x22 # Set Profile On
40
+ SET_PROFILE_OFF = 0x23 # Set Profile Off
41
+ PROFILE_ENABLED = 0x24 # Profile Enabled Report
42
+ PROFILE_DISABLED = 0x25 # Profile Disabled Report
43
+ PROFILE_ADDED = 0x26 # Profile Added Report
44
+ PROFILE_REMOVED = 0x27 # Profile Removed Report
45
+ PROFILE_DETAILS_INQUIRY = 0x28 # Profile Details Inquiry
46
+ PROFILE_DETAILS_REPLY = 0x29 # Reply to Profile Details Inquiry
47
+ PROFILE_SPECIFIC_DATA = 0x2F # Profile Specific Data
48
+
49
+ # Property Exchange messages
50
+ PROPERTY_CAPABILITIES = 0x30 # Inquiry: Property Exchange Capabilities
51
+ PROPERTY_CAPABILITIES_REPLY = 0x31 # Reply to Property Exchange Capabilities
52
+ PROPERTY_GET = 0x34 # Get Property Data Inquiry
53
+ PROPERTY_GET_REPLY = 0x35 # Reply to Get Property Data
54
+ PROPERTY_SET = 0x36 # Set Property Data Inquiry
55
+ PROPERTY_SET_REPLY = 0x37 # Reply to Set Property Data
56
+ PROPERTY_SUBSCRIBE = 0x38 # Subscription Inquiry
57
+ PROPERTY_SUBSCRIBE_REPLY = 0x39 # Reply to Subscription
58
+ PROPERTY_NOTIFY = 0x3F # Notify (subscription update)
59
+
60
+ # Process Inquiry messages (MIDI-CI 1.2)
61
+ PROCESS_INQUIRY = 0x40 # Process Inquiry
62
+ PROCESS_INQUIRY_REPLY = 0x41 # Reply to Process Inquiry
63
+ PROCESS_MIDI_REPORT = 0x42 # MIDI Message Report
64
+ PROCESS_MIDI_REPORT_REPLY = 0x43 # Reply to MIDI Message Report
65
+ PROCESS_MIDI_REPORT_END = 0x44 # End of MIDI Message Report
66
+
67
+
68
+ # ── Broadcast MUID ───────────────────────────────────────────────────────────
69
+ # 28-bit MUID (Message Unique Identifier) value used for broadcast discovery.
70
+ # Sent as 4 bytes in the SysEx payload (7 bits each, totalling 28 bits).
71
+
72
+ BROADCAST_MUID = 0x0FFFFFFF # All-ones 28-bit MUID (broadcast)
73
+
74
+
75
+ # ── MIDI-CI Version ──────────────────────────────────────────────────────────
76
+ # Version byte included in Discovery and Reply to Discovery messages.
77
+
78
+ CI_VERSION_1_1 = 0x01 # MIDI-CI version 1.1
79
+ CI_VERSION_1_2 = 0x02 # MIDI-CI version 1.2
pymididefs/drums.py ADDED
@@ -0,0 +1,221 @@
1
+ """General MIDI Level 1 percussion key map.
2
+
3
+ Standard MIDI percussion assignments for channel 10 (0-indexed channel 9).
4
+ These note numbers are defined by the General MIDI Level 1 specification and
5
+ are supported by virtually all GM-compatible instruments, drum machines, and
6
+ DAWs.
7
+
8
+ Two ways to use this module::
9
+
10
+ import pymididefs.drums
11
+
12
+ # As named constants
13
+ pymididefs.drums.KICK_1 # 36
14
+ pymididefs.drums.HI_HAT_CLOSED # 42
15
+
16
+ # As a lookup dictionary
17
+ pymididefs.drums.GM_DRUM_MAP["kick_1"] # 36
18
+
19
+ The four instruments GM defines in numbered pairs — kick, snare, crash, ride —
20
+ also have unnumbered *primary aliases* pointing to the "1" variant: the
21
+ ``KICK`` / ``SNARE`` / ``CRASH`` / ``RIDE`` constants and the
22
+ ``GM_DRUM_PRIMARY_ALIASES`` lookup, kept separate from ``GM_DRUM_MAP`` so the
23
+ canonical key map stays one name per note.
24
+
25
+ Source: General MIDI Level 1 Specification — Percussion Key Map.
26
+ Note range: 27 (High Q) through 87 (Open Surdo).
27
+ Channel: 10 (1-indexed) / 9 (0-indexed).
28
+ """
29
+
30
+ import typing
31
+
32
+
33
+ # ── GM Level 1 Percussion Key Map (notes 27–87) ─────────────────────────────
34
+ # Organised by instrument family for readability.
35
+
36
+ # Electronic percussion / effects (27–34)
37
+ HIGH_Q = 27 # High Q
38
+ SLAP = 28 # Slap
39
+ SCRATCH_PUSH = 29 # Scratch Push
40
+ SCRATCH_PULL = 30 # Scratch Pull
41
+ STICKS = 31 # Sticks
42
+ SQUARE_CLICK = 32 # Square Click
43
+ METRONOME_CLICK = 33 # Metronome Click
44
+ METRONOME_BELL = 34 # Metronome Bell
45
+
46
+ # Kick drums (35–36)
47
+ KICK_2 = 35 # Acoustic Bass Drum
48
+ KICK_1 = 36 # Bass Drum 1
49
+
50
+ # Snare and side stick (37–40)
51
+ SIDE_STICK = 37 # Side Stick
52
+ SNARE_1 = 38 # Acoustic Snare
53
+ HAND_CLAP = 39 # Hand Clap
54
+ SNARE_2 = 40 # Electric Snare
55
+
56
+ # Toms (41, 43, 45, 47, 48, 50)
57
+ LOW_FLOOR_TOM = 41 # Low Floor Tom
58
+ HI_HAT_CLOSED = 42 # Closed Hi-Hat
59
+ HIGH_FLOOR_TOM = 43 # High Floor Tom
60
+ HI_HAT_PEDAL = 44 # Pedal Hi-Hat
61
+ LOW_TOM = 45 # Low Tom
62
+ HI_HAT_OPEN = 46 # Open Hi-Hat
63
+ LOW_MID_TOM = 47 # Low-Mid Tom
64
+ HIGH_MID_TOM = 48 # Hi-Mid Tom
65
+ CRASH_1 = 49 # Crash Cymbal 1
66
+ HIGH_TOM = 50 # High Tom
67
+
68
+ # Cymbals (51–53, 55, 57, 59)
69
+ RIDE_1 = 51 # Ride Cymbal 1
70
+ CHINESE_CYMBAL = 52 # Chinese Cymbal
71
+ RIDE_BELL = 53 # Ride Bell
72
+ TAMBOURINE = 54 # Tambourine
73
+ SPLASH_CYMBAL = 55 # Splash Cymbal
74
+ COWBELL = 56 # Cowbell
75
+ CRASH_2 = 57 # Crash Cymbal 2
76
+ VIBRASLAP = 58 # Vibraslap
77
+ RIDE_2 = 59 # Ride Cymbal 2
78
+
79
+ # Latin percussion (60–69)
80
+ HIGH_BONGO = 60 # Hi Bongo
81
+ LOW_BONGO = 61 # Low Bongo
82
+ MUTE_HIGH_CONGA = 62 # Mute Hi Conga
83
+ OPEN_HIGH_CONGA = 63 # Open Hi Conga
84
+ LOW_CONGA = 64 # Low Conga
85
+ HIGH_TIMBALE = 65 # High Timbale
86
+ LOW_TIMBALE = 66 # Low Timbale
87
+ HIGH_AGOGO = 67 # High Agogo
88
+ LOW_AGOGO = 68 # Low Agogo
89
+ CABASA = 69 # Cabasa
90
+
91
+ # Shakers and small percussion (70–79)
92
+ MARACAS = 70 # Maracas
93
+ SHORT_WHISTLE = 71 # Short Whistle
94
+ LONG_WHISTLE = 72 # Long Whistle
95
+ SHORT_GUIRO = 73 # Short Guiro
96
+ LONG_GUIRO = 74 # Long Guiro
97
+ CLAVES = 75 # Claves
98
+ HIGH_WOODBLOCK = 76 # Hi Wood Block
99
+ LOW_WOODBLOCK = 77 # Low Wood Block
100
+ MUTE_CUICA = 78 # Mute Cuica
101
+ OPEN_CUICA = 79 # Open Cuica
102
+
103
+ # Triangle and bells (80–87)
104
+ MUTE_TRIANGLE = 80 # Mute Triangle
105
+ OPEN_TRIANGLE = 81 # Open Triangle
106
+ SHAKER = 82 # Shaker
107
+ JINGLE_BELL = 83 # Jingle Bell
108
+ BELL_TREE = 84 # Bell Tree
109
+ CASTANETS = 85 # Castanets
110
+ MUTE_SURDO = 86 # Mute Surdo
111
+ OPEN_SURDO = 87 # Open Surdo
112
+
113
+
114
+ # ── Lookup dictionary ────────────────────────────────────────────────────────
115
+ # Maps snake_case names to MIDI note numbers for string-based access.
116
+
117
+ GM_DRUM_MAP: typing.Final[dict[str, int]] = {
118
+ # Electronic percussion / effects
119
+ "high_q": HIGH_Q,
120
+ "slap": SLAP,
121
+ "scratch_push": SCRATCH_PUSH,
122
+ "scratch_pull": SCRATCH_PULL,
123
+ "sticks": STICKS,
124
+ "square_click": SQUARE_CLICK,
125
+ "metronome_click": METRONOME_CLICK,
126
+ "metronome_bell": METRONOME_BELL,
127
+
128
+ # Kick drums
129
+ "kick_2": KICK_2,
130
+ "kick_1": KICK_1,
131
+
132
+ # Snare and side stick
133
+ "side_stick": SIDE_STICK,
134
+ "snare_1": SNARE_1,
135
+ "hand_clap": HAND_CLAP,
136
+ "snare_2": SNARE_2,
137
+
138
+ # Toms and hi-hats
139
+ "low_floor_tom": LOW_FLOOR_TOM,
140
+ "hi_hat_closed": HI_HAT_CLOSED,
141
+ "high_floor_tom": HIGH_FLOOR_TOM,
142
+ "hi_hat_pedal": HI_HAT_PEDAL,
143
+ "low_tom": LOW_TOM,
144
+ "hi_hat_open": HI_HAT_OPEN,
145
+ "low_mid_tom": LOW_MID_TOM,
146
+ "high_mid_tom": HIGH_MID_TOM,
147
+ "high_tom": HIGH_TOM,
148
+
149
+ # Cymbals
150
+ "crash_1": CRASH_1,
151
+ "ride_1": RIDE_1,
152
+ "chinese_cymbal": CHINESE_CYMBAL,
153
+ "ride_bell": RIDE_BELL,
154
+ "tambourine": TAMBOURINE,
155
+ "splash_cymbal": SPLASH_CYMBAL,
156
+ "cowbell": COWBELL,
157
+ "crash_2": CRASH_2,
158
+ "vibraslap": VIBRASLAP,
159
+ "ride_2": RIDE_2,
160
+
161
+ # Latin percussion
162
+ "high_bongo": HIGH_BONGO,
163
+ "low_bongo": LOW_BONGO,
164
+ "mute_high_conga": MUTE_HIGH_CONGA,
165
+ "open_high_conga": OPEN_HIGH_CONGA,
166
+ "low_conga": LOW_CONGA,
167
+ "high_timbale": HIGH_TIMBALE,
168
+ "low_timbale": LOW_TIMBALE,
169
+ "high_agogo": HIGH_AGOGO,
170
+ "low_agogo": LOW_AGOGO,
171
+ "cabasa": CABASA,
172
+
173
+ # Shakers and small percussion
174
+ "maracas": MARACAS,
175
+ "short_whistle": SHORT_WHISTLE,
176
+ "long_whistle": LONG_WHISTLE,
177
+ "short_guiro": SHORT_GUIRO,
178
+ "long_guiro": LONG_GUIRO,
179
+ "claves": CLAVES,
180
+ "high_woodblock": HIGH_WOODBLOCK,
181
+ "low_woodblock": LOW_WOODBLOCK,
182
+ "mute_cuica": MUTE_CUICA,
183
+ "open_cuica": OPEN_CUICA,
184
+
185
+ # Triangle and bells
186
+ "mute_triangle": MUTE_TRIANGLE,
187
+ "open_triangle": OPEN_TRIANGLE,
188
+ "shaker": SHAKER,
189
+ "jingle_bell": JINGLE_BELL,
190
+ "bell_tree": BELL_TREE,
191
+ "castanets": CASTANETS,
192
+ "mute_surdo": MUTE_SURDO,
193
+ "open_surdo": OPEN_SURDO,
194
+ }
195
+
196
+
197
+ # ── Primary aliases ──────────────────────────────────────────────────────────
198
+ # General MIDI defines four instruments in numbered pairs — two kicks, two
199
+ # snares, two crashes, two rides. These unnumbered aliases point to the
200
+ # GM-designated *primary* (the "1" variant): Bass Drum 1, Acoustic Snare,
201
+ # Crash Cymbal 1, Ride Cymbal 1 — the notes every GM kit treats as the main
202
+ # kick / snare / crash / ride. Use ``KICK`` when you just want "the kick"
203
+ # rather than choosing between the two.
204
+ #
205
+ # Kept separate from GM_DRUM_MAP, which stays one name per note (the canonical
206
+ # percussion key map). Only these four instruments come in numbered pairs; the
207
+ # single-instance voices (closed/open/pedal hi-hat, side stick, cowbell, …) are
208
+ # already unnumbered. Ambiguous cases with no clear primary (e.g. the six toms)
209
+ # are deliberately not aliased.
210
+
211
+ KICK = KICK_1 # Bass Drum 1 (36)
212
+ SNARE = SNARE_1 # Acoustic Snare (38)
213
+ CRASH = CRASH_1 # Crash Cymbal 1 (49)
214
+ RIDE = RIDE_1 # Ride Cymbal 1 (51)
215
+
216
+ GM_DRUM_PRIMARY_ALIASES: typing.Final[dict[str, int]] = {
217
+ "kick": KICK_1,
218
+ "snare": SNARE_1,
219
+ "crash": CRASH_1,
220
+ "ride": RIDE_1,
221
+ }