forked from simonholliday/subsequence
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathUpdated_Readme
More file actions
446 lines (306 loc) · 15.7 KB
/
Updated_Readme
File metadata and controls
446 lines (306 loc) · 15.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# POMSKI
Write music as Python code. Change it while it's playing.
**POMSKI** — *Python Only MIDI Sequencer Keyboard Interface* — named after Qina, a very good Pomsky dog.
POMSKI is a MIDI sequencer you control from a Python script — or from a live coding environment where you can rewrite patterns, shift harmonies, and retune the tempo mid-performance without ever stopping playback.
```python
import subsequence
import subsequence.constants.instruments.gm_drums as gm_drums
composition = subsequence.Composition(key="C", bpm=120)
@composition.pattern(channel=0, length=4)
def bass(p):
p.note(36, beat=0, velocity=100, duration=0.5)
p.note(43, beat=2, velocity=80, duration=0.5)
composition.web_ui() # open the browser dashboard
composition.live() # start the live coding server
composition.play()
```
---
## Origin
POMSKI is a fork of [subsequence](https://github.com/simonholliday/subsequence) by Simon Holliday, extended with a browser-based Web UI, Ableton Link sync, Windows compatibility fixes, and a Max for Live device. The original AGPLv3 license and copyright are preserved.
---
## Installation
You'll need Python 3.10+ and a DAW or hardware synth receiving MIDI.
```bash
git clone https://github.com/ThinkInSound/POMSKI.git
cd POMSKI
pip install -e .
```
> **Important:** use `git clone`, not the ZIP download. The ZIP is missing files that the installer needs.
**Windows users** — POMSKI works on Windows. See the [Windows section](#windows) below.
### Optional extras
```bash
pip install aalink # Ableton Link tempo sync
pip install mido python-rtmidi # MIDI device selection in the Web UI
pip install music21 # Exotic scale support (p.quantize_m21)
```
---
## The basics
### Patterns
A pattern is a function that gets called every time its loop comes around. Place notes by calling methods on `p`, the pattern builder:
```python
@composition.pattern(channel=0, length=2)
def kick(p):
p.note(36, beat=0, velocity=110, duration=0.1)
```
`length` sets the loop length in beats. Inside the pattern, `p.bar` tells you the current global bar and `p.cycle` tells you how many times this pattern has looped — use them to vary things over time.
### MIDI channels
POMSKI channels are **0-indexed**: `channel=0` = MIDI channel 1, `channel=9` = MIDI channel 10 (drums). The template assigns slots `ch1`–`ch16` to channels `0`–`15` respectively.
### Drums
Use `drum_note_map=gm_drums.GM_DRUM_MAP` on the decorator to address drums by name. Channel 9 is the standard GM drums channel.
```python
import subsequence.constants.instruments.gm_drums as gm_drums
@composition.pattern(channel=9, length=4, drum_note_map=gm_drums.GM_DRUM_MAP)
def drums(p):
p.hit_steps("kick_1", [0, 3, 8, 12], velocity=110)
p.hit_steps("snare_1", [4, 12], velocity=100)
p.hit_steps("hi_hat_closed", range(16), velocity=70)
```
### Harmony
Tell POMSKI what harmonic style to use and it will manage chord progressions automatically:
```python
composition.harmony(style="functional_major", cycle_beats=4, gravity=0.8)
```
Available styles include `"functional_major"`, `"aeolian_minor"`, `"dorian"`, `"lydian"`, and others. `cycle_beats` sets how often the chord changes. `gravity` (0–1) controls how strongly chords pull toward the tonic.
### Sections
Organise your piece into named sections with automatic transitions:
```python
composition.form([
("intro", 4), # 4 bars
("verse", 8),
("chorus", 8),
])
```
---
## Live coding
The real power of POMSKI is changing things while they play. Start the live coding server before `play()`:
```python
composition.live() # starts a server on port 5555
composition.play()
```
Then from the **Web UI** (or any text editor that can open a socket) you can type Python and hit send — your changes take effect on the next bar:
```python
# change the tempo
composition.set_bpm(140)
# mute a pattern while you work on it
composition.mute("bass")
# redefine a pattern entirely
@composition.pattern(channel=0, length=4)
def bass(p):
p.note(33, beat=0, velocity=90, duration=2)
```
---
## Web UI
The browser dashboard gives you a visual overview of everything happening in your composition and a built-in code editor for sending live changes.
```python
composition.web_ui()
composition.live()
composition.play()
```
Then open **http://localhost:8080** in any browser.
### What's on screen
**Topbar**
- BPM display — drag up/down to change tempo, or tap the TAP button to set it by feel
- Bar and beat counters
- Current chord and key
- Link pill — shows Ableton Link status; click to toggle sync on/off
- Section progress bar
**Log tab** — everything you send to the REPL and every response comes back here in colour. The quick command input at the bottom accepts Python one-liners, or prefix with `cx:` to send a ClyphX Pro action instead (e.g. `cx: 1/MUTE ON`).
**Signals tab** — live scrolling waveforms for any LFOs or values you've written to `composition.data`. Each signal has a ✕ button to remove it. Useful for checking that modulations are doing what you expect.
**Patterns tab** — every running pattern listed with a mute button, a clear (×) button, and a small 16-step grid showing which steps have notes and how loud they are.
**Refs tab** — copy-ready code examples for every algorithmic method, plus a button to open the full tutorial in a new window.
**Prefs tab** — turn Ableton Link on or off, select your MIDI input/output device, record a MIDI session to file, and monitor AbletonOSC connection status.
### Keyboard shortcuts
| Keys | What it does |
|---|---|
| `Shift+Enter` | Send the current code block to the live coding server |
| `Ctrl+Shift+Enter` | Send the entire editor contents |
| `Ctrl+↑` / `Ctrl+↓` | Step through previous commands (last 200) |
| `Tab` | Indent (4 spaces) |
---
## Ableton Link
Link keeps POMSKI's tempo locked to Ableton Live — and anything else on your network that supports Link (Ableton Live, Reason, Traktor, various iOS apps).
```bash
pip install aalink
```
That's it. If aalink is installed POMSKI will connect to Link automatically when you call `play()`. You'll see the peer count in the Link pill in the Web UI.
- Change tempo in Ableton → POMSKI follows
- Call `composition.set_bpm(140)` → Ableton follows
- Toggle sync on/off any time from the Prefs tab
---
## AbletonOSC integration
POMSKI can communicate directly with Ableton Live via [AbletonOSC](https://github.com/ideoforms/AbletonOSC), giving you programmatic control over Live's session from Python code or the REPL.
### Setup
1. Install AbletonOSC as a remote script in Ableton (see its README)
2. Add `LiveBridge` to your script:
```python
from live_bridge import LiveBridge
composition = subsequence.Composition(key="C", bpm=120)
live = LiveBridge(composition)
composition._live_bridge = live # exposes Live state to the web UI
# ... your patterns ...
composition.web_ui()
composition.live()
composition.play()
```
The bridge connects automatically once `play()` starts. Connection status and track count are shown in the Prefs tab.
### Controlling Live from Python
```python
# Transport
live.scene_play(0)
live.track_stop(0)
# Mixer
live.track_volume(0, 0.85) # track index, value 0.0–1.0
live.track_mute(0, True)
# Clips
live.clip_play(0, 0) # track, clip slot
live.clip_stop(0, 0)
# Devices
live.device_param(0, 0, 3, 0.5) # track, device, param, value 0–1
# Tempo
live.set_tempo(128.0)
# Subscribe to a Live value — pushes to composition.data automatically
live.watch("track/0/volume") # → composition.data["live_track_0_volume"]
# Inspect the session
live.tracks # list of track names
live.scenes # list of scene names
live.connected # True if bridge is active
```
### Reading Live state inside patterns
```python
# After calling live.watch("track/0/volume"):
@composition.pattern(channel=0, length=4)
def melody(p):
vol = p.data.get("live_track_0_volume", 0.8)
p.seq("60 _ 63 65", velocity=int(vol * 127))
```
---
## ClyphX Pro integration
If you have [ClyphX Pro](https://isotonikstudios.com/product/clyphx-pro/) installed, POMSKI can trigger its action strings directly from Python.
```python
# Arbitrary action strings — uses a hidden X-Clip trigger track
live.clyphx("BPM 128")
live.clyphx("1/MUTE ON")
live.clyphx("1/DEV(1) ON ; 2/ARM ON ; BPM 140")
live.clyphx("(PSEQ) 1/MUTE ; 2/MUTE ; 3/MUTE")
# Pre-defined X-OSC addresses — faster, bypasses AbletonOSC entirely
# Requires entries in ClyphX Pro's X-OSC.txt file
live.clyphx_osc("/MY_ACTION")
```
From the Web UI quick command box, prefix with `cx:`:
```
cx: 1/MUTE ON
cx: BPM 128 ; METRO
```
### How it works
`live.clyphx()` creates a single hidden MIDI track (`_POMSKI_CLYPHX`) on first call, drops a 1-bar clip in slot 0, renames it to the action string wrapped in ClyphX bracket syntax, and fires it. ClyphX Pro intercepts the launch and executes the action list. The track is muted so it makes no sound.
`live.clyphx_osc()` sends a raw OSC message directly to ClyphX Pro's built-in OSC receiver on port 7005, bypassing AbletonOSC entirely. Use this for performance-critical or frequently-triggered actions that you've pre-mapped in `X-OSC.txt`.
---
## External API feeds
POMSKI includes a `feeds` object pre-loaded into the REPL namespace. Call it from the browser command box or any REPL client to pull live data from any HTTP JSON API into your patterns — no template editing required.
```python
# Start a feed — polls every 5 seconds
feeds.add("iss", "http://api.open-notify.org/iss-now.json",
interval=5, extract=lambda r: float(r["iss_position"]["latitude"]))
# See active feeds and their current values
feeds
# Stop a feed
feeds.stop("iss")
```
Values land in `composition.data["feed_<key>"]` and are readable inside any pattern:
```python
@composition.pattern(channel=0, length=4)
def ch1(p):
lat = composition.data.get("feed_iss", 0) # default before first fetch
pitch = int(48 + (float(lat) + 90) / 180 * 36) # map −90..+90 → 48..84
p.note(pitch, beat=0)
```
Calling `feeds.add()` with the same key replaces the existing feed — so you can swap data sources mid-performance without changing any pattern code.
### feeds API
| Call | What it does |
|---|---|
| `feeds.add(key, url, interval=30, extract=None, headers=None, method="GET", body=None)` | Start or replace a named feed |
| `feeds.stop("key")` | Cancel a feed by name |
| `feeds.stop_all()` | Cancel every running feed |
| `feeds` | Show active feeds and current values |
`extract` is a callable that receives the parsed JSON and returns the value to store. Omit it to store the full response dict. No extra dependencies — uses Python's built-in `urllib`.
---
## Troubleshooting MIDI output
**LoopBe Internal MIDI — silent muting**
LoopBe has a feedback protection feature that silently mutes the port if it detects a MIDI loop. The port indicator in the system tray turns red. This can happen when Ableton Live and AbletonOSC are running alongside POMSKI.
Fix: right-click the LoopBe icon in the taskbar and reset/unmute the port.
**MIDI activity light blinking but no sound**
Check that your DAW instrument tracks are set to receive from the correct MIDI port — the one POMSKI is sending to. Run `print(composition._sequencer.output_device_name)` in the REPL to confirm which port is in use.
---
## Windows
POMSKI runs on Windows with two things to be aware of:
**1. Use git clone**
Download via `git clone` rather than the ZIP button on GitHub. The ZIP is missing the `.git` folder and the installer won't work without it.
**2. No extra steps needed for asyncio**
Older versions of POMSKI crashed on Windows due to a signal handler that Windows doesn't support. This is fixed — it just works.
---
## Max for Live device
If you use Ableton Live you can add a small MIDI device to any track that shows whether POMSKI is connected and lets you open the Web UI with one click.
The device files are in the project root:
- `subsequence_webui.amxd` — the Max for Live device
- `subsequence_webui.maxpat` — the Max patch source
- `subsequence.js` — the node.script WebSocket connector
**Setup (one time):**
1. In Ableton, drag `subsequence_webui.amxd` onto any MIDI track
2. First time only: click the `node.script` object inside the device and send it the message `script npm install ws`
The device connects automatically when POMSKI is running and reconnects if it drops. You'll see a green LED when it's live and the current BPM ticking alongside it.
---
## API quick reference
### Composition
| Call | What it does |
|---|---|
| `composition.play()` | Start everything — call this last |
| `composition.set_bpm(120)` | Change tempo; syncs to Link if connected |
| `composition.mute("name")` | Silence a pattern by name |
| `composition.unmute("name")` | Bring a pattern back |
| `composition.web_ui()` | Start the browser dashboard (port 8080) |
| `composition.live()` | Start the live coding server (port 5555) |
| `composition.harmony(style, cycle_beats, gravity)` | Set harmonic style and progression speed |
| `composition.form_next()` | Jump to the next section now |
| `composition.form_jump("chorus")` | Jump to a named section |
| `composition.data` | Shared dict for cross-pattern state and Live values |
### Pattern builder (p.)
| Call | What it does |
|---|---|
| `p.note(pitch, beat, velocity, duration)` | Place a single note |
| `p.hit_steps(pitch, steps, velocity)` | Place hits at 16th-note grid positions (0–15) |
| `p.seq("60 _ 62 64", velocity)` | Sonic Pi-style string notation; `_` = rest |
| `p.sequence(steps, pitches, velocity)` | Pair grid positions with a pitch list |
| `p.euclidean(pitch, pulses, steps)` | Euclidean rhythm distribution |
| `p.brownian(start, steps, step_size, pitch_range)` | Random-walk melody |
| `p.lorenz(steps, pitch_range)` | Lorenz attractor → pitch |
| `p.game_of_life(pitch, cols, rows, generations)` | Conway CA → rhythm |
| `p.logistic(steps, r, pitch_range)` | Logistic map chaos |
| `p.gray_scott(pitch, n, f, k)` | Reaction-diffusion → velocity |
| `p.golden_ratio(pitch, count)` | φ-spaced note distribution |
| `p.spectral(harmonics, steps, pitch_range)` | Sum-of-harmonics contour |
| `p.markov(graph, steps, start)` | Markov chain melody |
| `p.from_midi(filepath, track)` | Load notes from a .mid file |
| `p.quantize(key, mode)` | Snap notes to a named scale |
| `p.quantize_m21(key, scale_name)` | Snap notes to any Music21 scale |
| `p.randomize(timing, velocity)` | Add human-feel micro-variation |
| `p.dropout(probability)` | Randomly remove notes each cycle |
| `p.transpose(semitones)` | Shift all notes up/down |
| `p.shift(steps)` | Shift timing by 16th-note steps |
| `p.reverse()` | Flip the pattern backwards |
| `p.velocity_shape(low, high)` | Rescale velocities to a range |
| `p.thin(density, strategy)` | Remove notes by position bias |
### Pattern attributes
| Attribute | Description |
|---|---|
| `p.cycle` | Integer — how many times this pattern has looped |
| `p.bar` | Global bar number at the time this pattern fired |
| `p.rng` | Seeded `random.Random` instance for reproducible randomness |
| `p.data` | Direct reference to `composition.data` |
| `p.section` | Current `SectionInfo` (name, bar, bars) or `None` |
---
## Tutorial
A full interactive tutorial covering philosophy, API reference, step-by-step composition walkthrough, Python primer, and performance tips is included.
Open **http://localhost:8080/tutorial.html** while POMSKI is running, or open `docs/tutorial.html` directly in any browser.
---
## License
AGPL-3.0 — inherited from [subsequence](https://github.com/simonholliday/subsequence). If you run a modified version of POMSKI as a network service, you must make the source available to its users. See the [LICENSE](LICENSE) file for details.