subsequence 0.6.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.
- subsequence/__init__.py +231 -0
- subsequence/__main__.py +24 -0
- subsequence/assets/web/index.html +345 -0
- subsequence/cadences.py +113 -0
- subsequence/chord_graphs/__init__.py +100 -0
- subsequence/chord_graphs/aeolian_minor.py +158 -0
- subsequence/chord_graphs/chromatic_mediant.py +113 -0
- subsequence/chord_graphs/diminished.py +97 -0
- subsequence/chord_graphs/dorian_minor.py +127 -0
- subsequence/chord_graphs/functional_major.py +102 -0
- subsequence/chord_graphs/hooktheory_major.py +88 -0
- subsequence/chord_graphs/lydian_major.py +130 -0
- subsequence/chord_graphs/mixolydian.py +98 -0
- subsequence/chord_graphs/phrygian_minor.py +76 -0
- subsequence/chord_graphs/suspended.py +109 -0
- subsequence/chord_graphs/turnaround_global.py +157 -0
- subsequence/chord_graphs/whole_tone.py +77 -0
- subsequence/chords.py +419 -0
- subsequence/composition.py +6099 -0
- subsequence/conductor.py +238 -0
- subsequence/constants/__init__.py +24 -0
- subsequence/constants/durations.py +37 -0
- subsequence/constants/instruments/__init__.py +13 -0
- subsequence/constants/instruments/gm_cc.py +46 -0
- subsequence/constants/instruments/gm_drums.py +53 -0
- subsequence/constants/instruments/gm_instruments.py +32 -0
- subsequence/constants/instruments/roland_tr8s.py +320 -0
- subsequence/constants/instruments/vermona_drm1_drums.py +87 -0
- subsequence/constants/midi_notes.py +29 -0
- subsequence/constants/pulses.py +17 -0
- subsequence/constants/velocity.py +22 -0
- subsequence/definitions.py +232 -0
- subsequence/display.py +617 -0
- subsequence/easing.py +347 -0
- subsequence/event_emitter.py +109 -0
- subsequence/form_state.py +665 -0
- subsequence/forms.py +257 -0
- subsequence/groove.py +323 -0
- subsequence/harmonic_rhythm.py +83 -0
- subsequence/harmonic_state.py +352 -0
- subsequence/harmony.py +197 -0
- subsequence/held_notes.py +91 -0
- subsequence/helpers/__init__.py +0 -0
- subsequence/helpers/network.py +58 -0
- subsequence/helpers/wing.py +430 -0
- subsequence/intervals.py +436 -0
- subsequence/keystroke.py +249 -0
- subsequence/link_clock.py +128 -0
- subsequence/live_client.py +187 -0
- subsequence/live_reloader.py +298 -0
- subsequence/live_server.py +161 -0
- subsequence/melodic_state.py +483 -0
- subsequence/midi.py +97 -0
- subsequence/midi_utils.py +329 -0
- subsequence/mini_notation.py +164 -0
- subsequence/motifs.py +2356 -0
- subsequence/osc.py +194 -0
- subsequence/pattern.py +363 -0
- subsequence/pattern_algorithmic.py +2010 -0
- subsequence/pattern_builder.py +2589 -0
- subsequence/pattern_midi.py +1208 -0
- subsequence/progressions.py +1913 -0
- subsequence/py.typed +0 -0
- subsequence/roles.py +63 -0
- subsequence/sequence_utils.py +3123 -0
- subsequence/sequencer.py +2086 -0
- subsequence/tuning.py +453 -0
- subsequence/voicings.py +151 -0
- subsequence/web_ui.py +337 -0
- subsequence/weighted_graph.py +156 -0
- subsequence-0.6.4.dist-info/METADATA +208 -0
- subsequence-0.6.4.dist-info/RECORD +78 -0
- subsequence-0.6.4.dist-info/WHEEL +5 -0
- subsequence-0.6.4.dist-info/entry_points.txt +2 -0
- subsequence-0.6.4.dist-info/licenses/LICENSE +661 -0
- subsequence-0.6.4.dist-info/scm_file_list.json +193 -0
- subsequence-0.6.4.dist-info/scm_version.json +8 -0
- subsequence-0.6.4.dist-info/top_level.txt +1 -0
subsequence/conductor.py
ADDED
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Global automation signals — LFOs and ramps that modulate a composition over time.
|
|
3
|
+
|
|
4
|
+
The Conductor holds named, time-varying signals (swells, fades, filter sweeps)
|
|
5
|
+
that any pattern can read via ``p.signal(name)`` to shape velocity, CCs, or
|
|
6
|
+
note choices across long stretches of music.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import math
|
|
10
|
+
import typing
|
|
11
|
+
import warnings
|
|
12
|
+
|
|
13
|
+
import subsequence.easing
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_VALID_LFO_SHAPES = frozenset({"sine", "triangle", "saw", "square"})
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Signal:
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
Abstract base class for a time-varying signal.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def value_at (self, beat: float) -> float:
|
|
26
|
+
|
|
27
|
+
"""
|
|
28
|
+
Return the signal value at the given beat time. Subclasses must override.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
raise NotImplementedError
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LFO(Signal):
|
|
35
|
+
|
|
36
|
+
"""
|
|
37
|
+
A Low-Frequency Oscillator for generating periodic modulation signals.
|
|
38
|
+
|
|
39
|
+
LFOs are used to create cyclical changes (like a 'swell' or 'vibrato')
|
|
40
|
+
over many bars.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
def __init__ (self, shape: str = "sine", cycle_beats: float = 16.0, min_val: float = 0.0, max_val: float = 1.0, phase: float = 0.0) -> None:
|
|
44
|
+
|
|
45
|
+
"""
|
|
46
|
+
Initialize an LFO.
|
|
47
|
+
|
|
48
|
+
Parameters:
|
|
49
|
+
shape: The waveform shape ("sine", "triangle", "saw", "square").
|
|
50
|
+
cycle_beats: How many beats for one full cycle (e.g., 16.0 = 4 bars).
|
|
51
|
+
min_val: The bottom of the LFO range (default 0.0).
|
|
52
|
+
max_val: The top of the LFO range (default 1.0).
|
|
53
|
+
phase: Phase offset (0.0 to 1.0).
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
if cycle_beats <= 0:
|
|
57
|
+
raise ValueError("cycle_beats must be positive")
|
|
58
|
+
|
|
59
|
+
if shape not in _VALID_LFO_SHAPES:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
f"Unknown LFO shape {shape!r}. "
|
|
62
|
+
f"Valid shapes: {sorted(_VALID_LFO_SHAPES)}"
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
self.shape = shape
|
|
66
|
+
self.cycle_beats = cycle_beats
|
|
67
|
+
self.min_val = min_val
|
|
68
|
+
self.max_val = max_val
|
|
69
|
+
self.phase = phase
|
|
70
|
+
|
|
71
|
+
def value_at (self, beat: float) -> float:
|
|
72
|
+
|
|
73
|
+
"""
|
|
74
|
+
Compute the signal value at a given beat time.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
progress = (beat / self.cycle_beats + self.phase) % 1.0
|
|
78
|
+
val = 0.0
|
|
79
|
+
|
|
80
|
+
if self.shape == "sine":
|
|
81
|
+
# Map -1..1 to 0..1
|
|
82
|
+
raw = math.sin(progress * 2 * math.pi)
|
|
83
|
+
val = (raw + 1) / 2
|
|
84
|
+
|
|
85
|
+
elif self.shape == "triangle":
|
|
86
|
+
# 0 -> 0.5 -> 1 -> 0.5 -> 0
|
|
87
|
+
if progress < 0.5:
|
|
88
|
+
val = progress * 2
|
|
89
|
+
else:
|
|
90
|
+
val = 2 - (progress * 2)
|
|
91
|
+
|
|
92
|
+
elif self.shape == "saw":
|
|
93
|
+
val = progress
|
|
94
|
+
|
|
95
|
+
else: # "square"
|
|
96
|
+
val = 1.0 if progress < 0.5 else 0.0
|
|
97
|
+
|
|
98
|
+
return self.min_val + (val * (self.max_val - self.min_val))
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Line(Signal):
|
|
102
|
+
|
|
103
|
+
"""
|
|
104
|
+
A ramp signal that moves from one value to another over time.
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
def __init__ (self, start_val: float, end_val: float, duration_beats: float, start_beat: float = 0.0, loop: bool = False, shape: typing.Union[str, subsequence.easing.EasingFn] = "linear") -> None:
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
Initialize a ramp signal.
|
|
111
|
+
|
|
112
|
+
Parameters:
|
|
113
|
+
start_val: Initial value.
|
|
114
|
+
end_val: Target value.
|
|
115
|
+
duration_beats: How long it takes to reach the target (in beats).
|
|
116
|
+
start_beat: Global beat time when the ramp should start (default 0).
|
|
117
|
+
loop: Whether to jump back to start_val and repeat (default False).
|
|
118
|
+
shape: Easing curve — a name string (e.g. ``"ease_in_out"``) or any
|
|
119
|
+
callable that maps [0, 1] → [0, 1]. Defaults to ``"linear"``.
|
|
120
|
+
See :mod:`subsequence.easing` for available shapes.
|
|
121
|
+
"""
|
|
122
|
+
|
|
123
|
+
if duration_beats <= 0:
|
|
124
|
+
raise ValueError("duration_beats must be positive")
|
|
125
|
+
|
|
126
|
+
self.start_val = start_val
|
|
127
|
+
self.end_val = end_val
|
|
128
|
+
self.duration_beats = duration_beats
|
|
129
|
+
self.start_beat = start_beat
|
|
130
|
+
self.loop = loop
|
|
131
|
+
self._easing_fn = subsequence.easing.get_easing(shape)
|
|
132
|
+
|
|
133
|
+
def value_at (self, beat: float) -> float:
|
|
134
|
+
|
|
135
|
+
"""
|
|
136
|
+
Compute the ramp value at a given beat time.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
elapsed = beat - self.start_beat
|
|
140
|
+
|
|
141
|
+
if elapsed < 0:
|
|
142
|
+
return self.start_val
|
|
143
|
+
|
|
144
|
+
if self.loop:
|
|
145
|
+
elapsed %= self.duration_beats
|
|
146
|
+
elif elapsed >= self.duration_beats:
|
|
147
|
+
return self.end_val
|
|
148
|
+
|
|
149
|
+
progress = elapsed / self.duration_beats
|
|
150
|
+
eased = self._easing_fn(progress)
|
|
151
|
+
return self.start_val + (eased * (self.end_val - self.start_val))
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Conductor:
|
|
155
|
+
|
|
156
|
+
"""
|
|
157
|
+
A registry for global automation signals.
|
|
158
|
+
|
|
159
|
+
The ``Conductor`` allows you to define time-varying signals (like LFOs or
|
|
160
|
+
ramps) that are available to all pattern builders. This is ideal for
|
|
161
|
+
modulating parameters (like velocity or filter cutoff) over long
|
|
162
|
+
compositional timeframes.
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__ (self) -> None:
|
|
166
|
+
|
|
167
|
+
"""
|
|
168
|
+
Create an empty conductor; register signals with lfo() or line().
|
|
169
|
+
"""
|
|
170
|
+
|
|
171
|
+
self._signals: typing.Dict[str, Signal] = {}
|
|
172
|
+
self._warned_missing: typing.Set[str] = set()
|
|
173
|
+
|
|
174
|
+
@property
|
|
175
|
+
def signal_names (self) -> typing.Tuple[str, ...]:
|
|
176
|
+
"""Names of all registered signals, in sorted order."""
|
|
177
|
+
return tuple(sorted(self._signals))
|
|
178
|
+
|
|
179
|
+
def lfo (self, name: str, shape: str = "sine", cycle_beats: float = 16.0, min_val: float = 0.0, max_val: float = 1.0, phase: float = 0.0) -> None:
|
|
180
|
+
|
|
181
|
+
"""
|
|
182
|
+
Register a named LFO signal.
|
|
183
|
+
|
|
184
|
+
Example:
|
|
185
|
+
```python
|
|
186
|
+
comp.conductor.lfo("swell", shape="sine", cycle_beats=32)
|
|
187
|
+
```
|
|
188
|
+
"""
|
|
189
|
+
|
|
190
|
+
self._signals[name] = LFO(shape, cycle_beats, min_val, max_val, phase)
|
|
191
|
+
|
|
192
|
+
def line (self, name: str, start_val: float, end_val: float, duration_beats: float, start_beat: float = 0.0, loop: bool = False, shape: typing.Union[str, subsequence.easing.EasingFn] = "linear") -> None:
|
|
193
|
+
|
|
194
|
+
"""
|
|
195
|
+
Register a named ramp signal.
|
|
196
|
+
|
|
197
|
+
Parameters:
|
|
198
|
+
name: Signal name, used to retrieve the value via ``p.signal(name)``.
|
|
199
|
+
start_val: Value at the start of the ramp.
|
|
200
|
+
end_val: Value at the end of the ramp.
|
|
201
|
+
duration_beats: Duration of the ramp in beats.
|
|
202
|
+
start_beat: Beat time at which the ramp begins (default 0).
|
|
203
|
+
loop: If True, the ramp restarts from start_val after each cycle.
|
|
204
|
+
shape: Easing curve name or callable. Defaults to ``"linear"``.
|
|
205
|
+
Use ``"ease_in_out"`` for smooth crossfades, ``"exponential"``
|
|
206
|
+
for filter sweeps. See :mod:`subsequence.easing`.
|
|
207
|
+
|
|
208
|
+
Example:
|
|
209
|
+
```python
|
|
210
|
+
comp.conductor.line("fadein", start_val=0, end_val=1, duration_beats=64)
|
|
211
|
+
comp.conductor.line("filter", start_val=0, end_val=1, duration_beats=32, shape="ease_in_out")
|
|
212
|
+
```
|
|
213
|
+
"""
|
|
214
|
+
|
|
215
|
+
self._signals[name] = Line(start_val, end_val, duration_beats, start_beat, loop, shape)
|
|
216
|
+
|
|
217
|
+
def get (self, name: str, beat: float) -> float:
|
|
218
|
+
|
|
219
|
+
"""
|
|
220
|
+
Retrieve the value of a signal at a specific beat time.
|
|
221
|
+
|
|
222
|
+
Most patterns should use ``p.signal("name")`` instead, which
|
|
223
|
+
calls this method with the current bar time automatically.
|
|
224
|
+
Use ``get()`` directly when you need a beat time other than
|
|
225
|
+
the current bar.
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
if name not in self._signals:
|
|
229
|
+
if name not in self._warned_missing:
|
|
230
|
+
warnings.warn(
|
|
231
|
+
f"Conductor signal {name!r} not found; returning 0.0. "
|
|
232
|
+
"Register it with conductor.lfo() or conductor.line() first.",
|
|
233
|
+
stacklevel=2,
|
|
234
|
+
)
|
|
235
|
+
self._warned_missing.add(name)
|
|
236
|
+
return 0.0
|
|
237
|
+
|
|
238
|
+
return self._signals[name].value_at(beat)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""Constants for Subsequence.
|
|
2
|
+
|
|
3
|
+
This package contains the following sets of constants:
|
|
4
|
+
|
|
5
|
+
- ``subsequence.constants.pulses`` - Pulse-based MIDI timing (internal engine use)
|
|
6
|
+
- ``subsequence.constants.durations`` - Beat-based durations for pattern lengths and steps
|
|
7
|
+
- ``subsequence.constants.velocity`` - MIDI velocity constants
|
|
8
|
+
- ``subsequence.constants.instruments`` - Instrument-specific note maps (GM, Vermona, etc.)
|
|
9
|
+
- ``subsequence.constants.midi_notes`` - Named MIDI note constants C0–G9, C4 = 60 (Middle C)
|
|
10
|
+
- ``subsequence.constants.instruments.gm_cc`` - Named MIDI Continuous Controller (CC) numbers
|
|
11
|
+
|
|
12
|
+
Pulse constants are re-exported here for backwards compatibility, so
|
|
13
|
+
``subsequence.constants.MIDI_QUARTER_NOTE`` continues to work.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
# Re-export pulse constants for backwards compatibility.
|
|
17
|
+
# These match the values in subsequence.constants.pulses.
|
|
18
|
+
|
|
19
|
+
MIDI_THIRTYSECOND_NOTE = 3
|
|
20
|
+
MIDI_SIXTEENTH_NOTE = 6
|
|
21
|
+
MIDI_EIGHTH_NOTE = 12
|
|
22
|
+
MIDI_QUARTER_NOTE = 24
|
|
23
|
+
MIDI_HALF_NOTE = 48
|
|
24
|
+
MIDI_WHOLE_NOTE = 96
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Beat-based duration constants for pattern lengths and note timing.
|
|
2
|
+
|
|
3
|
+
All values are in **beats**, where 1.0 = one quarter note. Use these to express
|
|
4
|
+
pattern lengths and step sizes in musical terms instead of raw floats.
|
|
5
|
+
|
|
6
|
+
Multiply by a count for multi-note durations::
|
|
7
|
+
|
|
8
|
+
import subsequence.constants.durations as dur
|
|
9
|
+
|
|
10
|
+
# "9 sixteenth notes"
|
|
11
|
+
length = 9 * dur.SIXTEENTH # 2.25 beats
|
|
12
|
+
|
|
13
|
+
# "21 eighth notes"
|
|
14
|
+
length = 21 * dur.EIGHTH # 10.5 beats
|
|
15
|
+
|
|
16
|
+
# "4 bars of quarter notes"
|
|
17
|
+
length = 4 * dur.QUARTER # 4.0 beats
|
|
18
|
+
|
|
19
|
+
Use directly for step sizes and note durations::
|
|
20
|
+
|
|
21
|
+
p.arpeggio(tones, spacing=dur.SIXTEENTH, velocity=90)
|
|
22
|
+
p.arpeggio(tones, spacing=dur.DOTTED_SIXTEENTH, velocity=80)
|
|
23
|
+
p.repeat(60, spacing=dur.EIGHTH)
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
THIRTYSECOND = 0.125
|
|
27
|
+
SIXTEENTH = 0.25
|
|
28
|
+
DOTTED_SIXTEENTH = 0.375
|
|
29
|
+
TRIPLET_EIGHTH = 1 / 3
|
|
30
|
+
EIGHTH = 0.5
|
|
31
|
+
DOTTED_EIGHTH = 0.75
|
|
32
|
+
TRIPLET_QUARTER = 2 / 3
|
|
33
|
+
QUARTER = 1.0
|
|
34
|
+
DOTTED_QUARTER = 1.5
|
|
35
|
+
HALF = 2.0
|
|
36
|
+
DOTTED_HALF = 3.0
|
|
37
|
+
WHOLE = 4.0
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Instrument definitions — note maps, CC maps, and constants.
|
|
2
|
+
|
|
3
|
+
Each instrument module exports constants and lookup dictionaries that can be
|
|
4
|
+
passed to ``@composition.pattern()`` via ``drum_note_map`` and ``cc_name_map``.
|
|
5
|
+
|
|
6
|
+
Available instruments:
|
|
7
|
+
|
|
8
|
+
- ``subsequence.constants.instruments.gm_cc`` - General MIDI CC assignments
|
|
9
|
+
- ``subsequence.constants.instruments.gm_drums`` - General MIDI Level 1 drums
|
|
10
|
+
- ``subsequence.constants.instruments.gm_instruments`` - General MIDI Level 1 program numbers
|
|
11
|
+
- ``subsequence.constants.instruments.roland_tr8s`` - Roland TR-8S (drums + CCs)
|
|
12
|
+
- ``subsequence.constants.instruments.vermona_drm1_drums`` - Vermona DRM1 MKIV drums
|
|
13
|
+
"""
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""General MIDI Continuous Controller (CC) constants.
|
|
2
|
+
|
|
3
|
+
Standard MIDI CC assignments (0-127). These are supported by virtually all GM-compatible
|
|
4
|
+
instruments, synthesizers, and DAWs.
|
|
5
|
+
|
|
6
|
+
Two ways to use this module:
|
|
7
|
+
|
|
8
|
+
1. **As constants** - reference CC numbers directly when sending MIDI CC messages::
|
|
9
|
+
|
|
10
|
+
import subsequence.constants.instruments.gm_cc as gm_cc
|
|
11
|
+
|
|
12
|
+
@composition.pattern(...)
|
|
13
|
+
def sweep (p):
|
|
14
|
+
p.cc(gm_cc.FILTER_CUTOFF, 127)
|
|
15
|
+
|
|
16
|
+
2. **As a cc_name_map** - pass ``GM_CC_MAP`` to the ``cc_name_map`` parameter
|
|
17
|
+
of ``@composition.pattern()`` and use human-readable names like ``"filter_cutoff"``
|
|
18
|
+
or ``"sustain_pedal"`` in your CC calls::
|
|
19
|
+
|
|
20
|
+
import subsequence.constants.instruments.gm_cc as gm_cc
|
|
21
|
+
|
|
22
|
+
@composition.pattern(channel=1, beats=4, cc_name_map=gm_cc.GM_CC_MAP)
|
|
23
|
+
def synth (p):
|
|
24
|
+
p.cc("filter_cutoff", 100)
|
|
25
|
+
p.cc_ramp("expression", 0, 127, shape="ease_in")
|
|
26
|
+
|
|
27
|
+
Canonical source: `pymididefs <https://github.com/simonholliday/PyMidiDefs>`_.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
import typing
|
|
31
|
+
|
|
32
|
+
# Re-export everything from pymididefs.cc — all CC constants and the lookup dict.
|
|
33
|
+
# These constants modules are deliberate re-export shims, exempt from the
|
|
34
|
+
# project's import-x-only rule (which the rest of the codebase satisfies).
|
|
35
|
+
from pymididefs.cc import * # noqa: F401,F403
|
|
36
|
+
from pymididefs.cc import CC_MAP # noqa: F401 — explicit re-export for type checkers
|
|
37
|
+
|
|
38
|
+
# ─── Backward-compatibility aliases ─────────────────────────────────────────
|
|
39
|
+
# Subsequence used FOOT_PEDAL_LSB; pymididefs uses FOOT_CONTROLLER_LSB.
|
|
40
|
+
FOOT_PEDAL_LSB = FOOT_CONTROLLER_LSB # noqa: F405
|
|
41
|
+
|
|
42
|
+
# GM_CC_MAP extends the canonical CC_MAP with the backward-compat alias.
|
|
43
|
+
GM_CC_MAP: typing.Dict[str, int] = {
|
|
44
|
+
**CC_MAP,
|
|
45
|
+
"foot_pedal_lsb": FOOT_PEDAL_LSB,
|
|
46
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""General MIDI Level 1 drum note map.
|
|
2
|
+
|
|
3
|
+
Standard MIDI percussion assignments for channel 10 (0-indexed channel 9).
|
|
4
|
+
These note numbers are supported by virtually all GM-compatible instruments,
|
|
5
|
+
drum machines, and DAWs.
|
|
6
|
+
|
|
7
|
+
Two ways to use this module:
|
|
8
|
+
|
|
9
|
+
1. **As a drum_note_map** - pass ``GM_DRUM_MAP`` to the ``drum_note_map`` parameter
|
|
10
|
+
of ``@composition.pattern()`` and use human-readable names like ``"kick"``,
|
|
11
|
+
``"snare"``, or the numbered ``"kick_1"`` in your pattern builder calls::
|
|
12
|
+
|
|
13
|
+
import subsequence.constants.instruments.gm_drums
|
|
14
|
+
|
|
15
|
+
@composition.pattern(channel=10, beats=4, drum_note_map=subsequence.constants.instruments.gm_drums.GM_DRUM_MAP)
|
|
16
|
+
def drums (p):
|
|
17
|
+
p.hit_steps("kick", [0, 4, 8, 12], velocity=127)
|
|
18
|
+
|
|
19
|
+
2. **As constants** - reference note numbers directly::
|
|
20
|
+
|
|
21
|
+
import subsequence.constants.instruments.gm_drums
|
|
22
|
+
|
|
23
|
+
@composition.pattern(channel=10, beats=4)
|
|
24
|
+
def drums (p):
|
|
25
|
+
p.hit_steps(subsequence.constants.instruments.gm_drums.KICK, [0, 4, 8, 12], velocity=127)
|
|
26
|
+
|
|
27
|
+
This map is the canonical GM percussion key map **plus** the unnumbered
|
|
28
|
+
"primary" aliases (``"kick"`` / ``"snare"`` / ``"crash"`` / ``"ride"`` → the
|
|
29
|
+
``_1`` variant), so a pattern can use either ``"kick"`` or ``"kick_1"``. The
|
|
30
|
+
pure, one-name-per-note spec map is always available upstream as
|
|
31
|
+
``pymididefs.drums.GM_DRUM_MAP``.
|
|
32
|
+
|
|
33
|
+
Canonical source: `pymididefs <https://github.com/simonholliday/PyMidiDefs>`_.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
import typing
|
|
37
|
+
|
|
38
|
+
import pymididefs.drums
|
|
39
|
+
|
|
40
|
+
# Re-export every GM drum constant and the primary-alias names (KICK, SNARE,
|
|
41
|
+
# CRASH, RIDE, GM_DRUM_PRIMARY_ALIASES) so callers can write e.g.
|
|
42
|
+
# ``gm_drums.KICK`` or ``gm_drums.KICK_1``.
|
|
43
|
+
from pymididefs.drums import * # noqa: F401,F403
|
|
44
|
+
|
|
45
|
+
# The composition-facing map: the canonical GM Level 1 percussion key map plus
|
|
46
|
+
# the unnumbered primary aliases. Merged here (not upstream) so the spec map at
|
|
47
|
+
# ``pymididefs.drums.GM_DRUM_MAP`` stays one name per note. The annotated
|
|
48
|
+
# assignment deliberately specialises the spec map re-exported by ``import *``
|
|
49
|
+
# above (hence the no-redef suppression).
|
|
50
|
+
GM_DRUM_MAP: typing.Dict[str, int] = { # type: ignore[no-redef]
|
|
51
|
+
**pymididefs.drums.GM_DRUM_MAP,
|
|
52
|
+
**pymididefs.drums.GM_DRUM_PRIMARY_ALIASES,
|
|
53
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""General MIDI Level 1 instrument program numbers.
|
|
2
|
+
|
|
3
|
+
All 128 GM Level 1 instrument assignments, organised into 16 families of 8.
|
|
4
|
+
Use these constants with ``p.program_change()`` to select instruments on
|
|
5
|
+
GM-compatible synthesizers and sound modules.
|
|
6
|
+
|
|
7
|
+
Usage::
|
|
8
|
+
|
|
9
|
+
import subsequence.constants.instruments.gm_instruments as gm
|
|
10
|
+
|
|
11
|
+
@composition.pattern(channel=1, length=4)
|
|
12
|
+
def strings (p):
|
|
13
|
+
p.program_change(gm.VIOLIN)
|
|
14
|
+
p.note(60, beat=0)
|
|
15
|
+
|
|
16
|
+
# Lookup by name
|
|
17
|
+
gm.GM_INSTRUMENT_MAP["flute"] # 73
|
|
18
|
+
gm.GM_INSTRUMENT_NAMES[73] # "Flute"
|
|
19
|
+
|
|
20
|
+
# Family ranges
|
|
21
|
+
gm.GM_FAMILIES["piano"] # (0, 7)
|
|
22
|
+
|
|
23
|
+
Canonical source: `pymididefs <https://github.com/simonholliday/PyMidiDefs>`_.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# Re-export everything from pymididefs.gm - all instrument constants and lookup tables.
|
|
27
|
+
from pymididefs.gm import * # noqa: F401,F403
|
|
28
|
+
from pymididefs.gm import ( # noqa: F401 - explicit re-exports for type checkers
|
|
29
|
+
GM_FAMILIES,
|
|
30
|
+
GM_INSTRUMENT_MAP,
|
|
31
|
+
GM_INSTRUMENT_NAMES,
|
|
32
|
+
)
|