feat(sequences): multi-lane playback and per-lane manual beats

- Add sequence_playback with beat and time advance, zone targeting fixes
- Per-lane manual beat routing in beat_driver_route (parallel lanes)
- Sequence API, editor JS, fix sequence model filename, tests

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-13 00:44:08 +12:00
parent 0ae39ab94b
commit cad0aa7e59
9 changed files with 2750 additions and 169 deletions

View File

@@ -123,7 +123,7 @@ def server(monkeypatch, tmp_path_factory):
import models.pallet as models_pallet # noqa: E402
import models.scene as models_scene # noqa: E402
import models.pattern as models_pattern # noqa: E402
import models.squence as models_sequence # noqa: E402
import models.sequence as models_sequence # noqa: E402
import models.device as models_device # noqa: E402
for cls in (
@@ -527,21 +527,24 @@ def test_groups_sequences_scenes_palettes_patterns_endpoints(server):
assert resp.status_code == 200
# Sequences.
unique_seq_group_name = f"pytest-seq-group-{uuid.uuid4().hex[:8]}"
unique_seq_name = f"pytest-seq-{uuid.uuid4().hex[:8]}"
resp = c.post(
f"{base_url}/sequences",
json={"group_name": unique_seq_group_name, "presets": []},
json={
"name": unique_seq_name,
"steps": [{"preset_id": "1", "group_ids": []}],
},
)
assert resp.status_code == 201
sequences_list = c.get(f"{base_url}/sequences").json()
seq_id = _find_id_by_field(sequences_list, "group_name", unique_seq_group_name)
seq_id = _find_id_by_field(sequences_list, "name", unique_seq_name)
resp = c.get(f"{base_url}/sequences/{seq_id}")
assert resp.status_code == 200
resp = c.put(f"{base_url}/sequences/{seq_id}", json={"sequence_duration": 1234})
resp = c.put(f"{base_url}/sequences/{seq_id}", json={"step_duration_ms": 1234})
assert resp.status_code == 200
assert resp.json()["sequence_duration"] == 1234
assert resp.json()["step_duration_ms"] == 1234
resp = c.delete(f"{base_url}/sequences/{seq_id}")
assert resp.status_code == 200