@kieransimkin/dance-rudiments 0.2.4 → 0.4.0

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.
@@ -0,0 +1,228 @@
1
+ # Import source skeletons and map explicit FK controls
2
+
3
+ The optional Blender authoring module can extract individual recorded Actions
4
+ from local **BVH, FBX, glTF/GLB and BLEND** files. It preserves the source skeleton
5
+ under a distinct `syn://type/danceflow/motion/skeletal/source/...` identity.
6
+ Generating a source profile does not turn that skeleton into a Rigify rig.
7
+
8
+ The implementation is `dancerudiments_authoring.blender_import`; the checkout
9
+ wrapper is `tools/blender_motion_import.py`. Importing, extracting and retargeting
10
+ are offline operations. Playback continues to use native `SampledRecording`
11
+ through `RigMotion.player()`, with 64 integer pips per beat. No downloaded motions
12
+ are added to the default catalogue or automatically loaded into a website.
13
+
14
+ ## Inspect and extract a local file
15
+
16
+ Run these commands in a dedicated Blender process, or with a Python environment
17
+ containing the pinned official `bpy` runtime:
18
+
19
+ ```sh
20
+ blender --background --factory-startup --python tools/blender_motion_import.py -- inspect character.glb --output inspected.json
21
+ blender --background --factory-startup --python tools/blender_motion_import.py -- extract character.glb --source-id my_character --output-dir extracted --sample-fps 30
22
+ ```
23
+
24
+ The inspector lists armatures, Action names, slots, source frame ranges, animated
25
+ bone/property paths and rejected candidates. An existing Action/slot or NLA
26
+ association establishes ownership. Otherwise an unbound Action must contain
27
+ bone paths that resolve on the selected armature. Object-only animation needs
28
+ an existing association. This avoids applying an unrelated camera Action to a
29
+ character simply because it contains location curves.
30
+
31
+ Select specific `--armature NAME`, `--action NAME` or `--binding-id ID` values
32
+ from the report; each option may be repeated. BLEND files with multiple scenes
33
+ require an explicit `--scene NAME`. Embedded scripts are not executed. Original
34
+ files are preserved, and output paths must be new.
35
+
36
+ Each successful extraction writes a `.capture.json` and `.profile.json` pair.
37
+ `index.json` records source file SHA-256, importer options, exact Action/slot,
38
+ frame count, channel count, duration, relative output names and unsupported
39
+ animation. `--continue-on-error` retains successful captures and records failures;
40
+ the process returns status 2 if any selected Action failed. A failure without
41
+ that option stops the batch and writes the partial index. The importer does not
42
+ download assets or infer permission from file format.
43
+
44
+ ### Source time and coordinate conventions
45
+
46
+ The scene's effective `fps / fps_base` converts frames to seconds. BVH import
47
+ preserves the source FPS. glTF timestamps may produce fractional Blender frame
48
+ positions; those endpoints are retained. The shared Action range includes all
49
+ its slots, including an animated parent object's lead-in or final key. NLA
50
+ strip scaling, repeats and blending are not applied when extracting an
51
+ individual Action.
52
+
53
+ `--sample-fps` selects an explicit sampling rate. Omitting it uses the imported
54
+ scene's rate. `--frame-start` and `--frame-end` select a finite excerpt inside the
55
+ Action; they do not extrapolate choreography. The final endpoint is retained
56
+ even when it falls between regular samples. The first captured frame becomes
57
+ time zero, with original frame bounds and rate retained in provenance.
58
+
59
+ There is **no assumed musical BPM or phase**. Compile the resulting capture with
60
+ the existing rig-motion compiler and explicit source beat annotations. A pack
61
+ may use a declared time-only 60-BPM coordinate so one beat represents one second;
62
+ that is a duration convention, not evidence that the dance was performed at
63
+ 60 BPM. See [rig-motion compilation](rig-motion.md#beat-matching-and-compilation).
64
+
65
+ BVH accepts explicit `--axis-forward`, `--axis-up` and `--global-scale` options.
66
+ The defaults match Blender's BVH importer: `-Z`, `Y` and `1`. BVH files do not
67
+ establish a physical unit. FBX uses its importer and embedded coordinate metadata,
68
+ with an optional `--global-scale`. glTF and BLEND keep their declared transforms;
69
+ source-axis overrides and a source import scale are rejected for those formats.
70
+ glTF external buffers/textures are not included in the main file's SHA-256; use
71
+ the original dependency set or a self-contained GLB when distributing an asset.
72
+
73
+ ## Portable source profile and native playback
74
+
75
+ The source carrier has the original named hierarchy and bone rest matrices,
76
+ plus an explicit synthetic root containing the armature object's evaluated
77
+ world transform. Every source bone is considered, including fingers and bones
78
+ with Rigify internal prefixes. Object parenting, constraints and bone inheritance
79
+ are evaluated by Blender and baked into local translation, unit quaternion
80
+ `w,x,y,z`, and scale values on a constraint-free carrier.
81
+
82
+ The profile stores the portable skeleton as a list of names, parents, row-major
83
+ rest matrices, lengths and deform flags. Carrier inheritance is explicitly full
84
+ scale, inherited rotation and local location, and its object transform is
85
+ identity. A pure BVH decoder may use the actual root joint instead of a synthetic
86
+ object root. It can choose identity joint rest rotations while preserving exact
87
+ joint offsets; displayed bone-tail direction is then a visualization convention.
88
+
89
+ Only transform groups that remain neutral within the declared tolerance are
90
+ omitted. The default tolerance is `1e-6`; set `--neutral-tolerance 0` to retain
91
+ all numerical differences. Every omitted group and its neutral values are stored
92
+ explicitly. Constant non-neutral groups remain recorded, and quaternion groups
93
+ are always complete. The adapter expands omitted groups during pose application,
94
+ so switching from a previously posed rig cannot leave stale values behind.
95
+ Each compact profile has its own exact fingerprint and numbered channel list;
96
+ do not reuse a channel number from a different profile merely because the source
97
+ skeleton type is the same.
98
+
99
+ For a package containing several Actions on that same source skeleton, use
100
+ `union_source_profile(capture.profile for capture in captures)` and then
101
+ `rebind_source_capture(capture, union_profile)` for each capture. The union
102
+ preserves the first profile's channel IDs and appends missing bindings in a
103
+ deterministic order. Rebinding expands only the explicitly declared neutral
104
+ groups; it cannot discard movement or change a channel's units or bounds.
105
+ Different skeletons, rest bases, import options or physical bounds need separate
106
+ packages. This explicit conversion gives every clip the exact profile expected
107
+ by the optional package loader without padding every source bone to ten channels.
108
+
109
+ Physical bounds are fixed options, not fitted independently to each Action:
110
+ locations default to ±10,000 Blender units, scales to ±100, and quaternion
111
+ components to ±1. Negative scale can represent an actual source reflection.
112
+ Out-of-range values fail. Matrices containing shear that local TRS cannot
113
+ represent also fail instead of being silently approximated. The existing scalar
114
+ budget is checked before allocating dense sampled rows; choose an excerpt or
115
+ lower explicit sample rate when necessary.
116
+
117
+ Create a compatible armature from a profile without requiring the original rig:
118
+
119
+ ```sh
120
+ blender --background --factory-startup --python tools/blender_motion_import.py -- create-carrier extracted/CLIP.profile.json --name source_carrier --save source-carrier.blend
121
+ blender --background source-carrier.blend --python tools/blender_rigify_bridge.py -- apply --armature source_carrier --motion compiled.motion.json --pip 64 --save sampled-source.blend
122
+ ```
123
+
124
+ The carrier supplies the skeleton, not mesh geometry. Keep a licensed source mesh
125
+ and its rest-space skinning data with an optional asset package if a renderer
126
+ needs them. The existing native `apply` and `bake` bridge commands accept source
127
+ profiles, validate the carrier's actual rest basis and hierarchy, expand declared
128
+ neutral groups and save a new Action. They preserve prior actions and prevent
129
+ old NLA blending from contaminating the saved result.
130
+
131
+ B-Bone segment curvature and mesh shape keys are reported as unsupported
132
+ deformations. Their skeletal bone transforms can still be captured, but the
133
+ carrier does not reproduce those additional deformations. Animated dependencies
134
+ outside the selected Action are rejected by default. Use
135
+ `--allow-scene-dependencies` only after choosing the intended scene context; the
136
+ dependency names are then retained in provenance.
137
+
138
+ ## Rebuild the optional Quaternius Robot package
139
+
140
+ `tools/import_quaternius_robot.py` is a complete source-to-package example using
141
+ the general APIs above. Supply the original public ZIP from the creator's
142
+ [Animated Lowpoly Robot page](https://opengameart.org/content/animated-lowpoly-robot).
143
+ The recipe verifies its exact SHA-256 and the original BLEND, FBX and licence
144
+ members before opening Blender data. It has no downloader. The package contains
145
+ 14 original Actions, including the dance, under the CC0 licence preserved in the
146
+ archive; the equivalent FBX motions are not counted a second time.
147
+
148
+ With the pinned `bpy==5.2.2` environment and matching native extension installed:
149
+
150
+ ```sh
151
+ python tools/import_quaternius_robot.py --archive Animated_Robot_Oct_2018.zip --work-dir robot-import --output-dir optional-packs/quaternius_robot/1.0.0 --package-archive quaternius_robot-1.0.0.zip
152
+ ```
153
+
154
+ All output paths must be new. The work directory retains the exact source
155
+ archive, licensed source mesh files, captures and verification report. The
156
+ optional motion package contains the union profile, 14 compressed recordings,
157
+ source hashes and notices, and stays separate from the default catalogue. Its
158
+ 178 numbered channels retain the source skeleton and fingers. Every compiled
159
+ pip is checked through the native sampler before a package ZIP is produced.
160
+
161
+ The explicit 60-BPM coordinate preserves elapsed time; musical tempo and phase
162
+ remain unverified. To reproduce an earlier package's exact ingestion provenance,
163
+ pass `--source-record PATH/TO/SOURCES.json` from that package. The recipe verifies
164
+ all source identity fields and retains the original date record. A later run
165
+ without that option records the recipe's source verification date, without
166
+ claiming that it downloaded a file.
167
+
168
+ ## Explicit Rigify FK mapping
169
+
170
+ The retargeter provides an explicit FK adapter, verified with a generated Basic
171
+ Human Rigify rig and a skinned mesh. It does not infer a bone map from names or
172
+ claim to solve IK poles, foot contacts, collision repair, missing fingers or
173
+ facial morphs.
174
+
175
+ First export the exact target rig's normal Rigify profile. Then supply a
176
+ `dancerudiments.fk-map` schema 1 JSON document with these fields:
177
+
178
+ | Field | Required meaning |
179
+ | --- | --- |
180
+ | `source_type_uri` | The exact portable source skeleton type |
181
+ | `target_profile_sha256` | The exact exported target Rigify profile fingerprint |
182
+ | `source_reference_object_matrix` | Explicit rigid 4×4 reference object transform, row-major; it establishes the source's rest coordinate basis |
183
+ | `source_to_target_rotation` | Explicit unit quaternion in `w,x,y,z` order aligning source motion directions with the target |
184
+ | `root_translation_scale` | Positive, explicit root distance scale; never inferred from an arbitrary bone length |
185
+ | `bones` | Entries with exact `source`, `target`, `rotation` and `translation` fields; flags are Boolean and targets are unique |
186
+ | `setup` | Explicit `{ "channel_number": ..., "value": ... }` entries, including required IK/FK switches |
187
+
188
+ The source reference matrix and alignment quaternion are separate choices. The
189
+ first removes the source object's declared reference orientation/placement when
190
+ forming rest-relative deltas. The second aligns those deltas with the target.
191
+ An identity reference is appropriate only when that is the intended source
192
+ reference. Scale belongs in `root_translation_scale`, not in the reference matrix.
193
+
194
+ For each mapped joint, the adapter computes the source's evaluated orientation
195
+ relative to its rest basis, aligns that rotation, and applies it to the target
196
+ control's rest orientation. Controls are solved in parent order using Blender's
197
+ [pose/local matrix conversion](https://docs.blender.org/api/5.2/bpy.types.Bone.html#bpy.types.Bone.convert_local_to_pose).
198
+ One mapping may transfer root translation. Source scale animation remains in the
199
+ source capture; target FK scales retain the explicit target baseline. Existing
200
+ target rotation modes and numbered property bindings are preserved. A constraint
201
+ that prevents reaching the requested control orientation causes an error.
202
+
203
+ ```sh
204
+ blender --background --factory-startup --python tools/blender_motion_import.py -- retarget source.capture.json --target-blend dancer.blend --scene Scene --target-armature rig --target-profile dancer.profile.json --mapping source-to-rigify.json --output rigify.capture.json
205
+ ```
206
+
207
+ The output capture uses the exact target Rigify profile. Its provenance includes
208
+ the complete mapping, source capture/profile hashes, fixed setup channels,
209
+ unmapped source bones, unmapped target channels and measured control orientation
210
+ error. Unmapped target controls retain their explicitly documented neutral/setup
211
+ baseline; they are not presented as captured source movement. The operation
212
+ restores the original target pose and animation assignment after authoring.
213
+
214
+ ## Reproducible verification
215
+
216
+ ```sh
217
+ python tools/test_blender_motion_batch.py --output-dir .cache/blender-motion-batch
218
+ python tools/test_blender_motion_imports.py --output-dir .cache/blender-imports
219
+ python tools/test_blender_rigify_bridge.py --output-dir .cache/rigify-bridge-smoke
220
+ ```
221
+
222
+ The new regression creates its own two-action BLEND fixture, non-aligned bone
223
+ rest axes, finger motion and object root translation. It compares evaluated
224
+ source/carrier matrices, verifies fractional multi-slot parent animation, checks
225
+ unresolved dependency rejection, and performs explicit FK retargeting to a real
226
+ generated Basic Human. Native pose application is saved and reopened in a fresh
227
+ Blender process, where actual skinned vertex positions are checked. No external
228
+ assets are downloaded by these tests.
@@ -0,0 +1,163 @@
1
+ # Low-poly mesh dance demos
2
+
3
+ The `meshes/` gallery presents two original skinned characters driven by real
4
+ captured dance recordings. It uses DanceRudiments' existing native recording
5
+ sampler, numbered skeletal channels and optional package loader. The gallery
6
+ is built separately from the default pattern catalogue and base npm/Python
7
+ packages.
8
+
9
+ | Character | Captured motion | Full source duration | Mesh |
10
+ | --- | --- | --- | --- |
11
+ | **Boxbot** | CMU `60_01`, salsa | About 37.37 seconds, 2,243 source frames at 60 fps | Angular robot, 2,160 weighted vertices. |
12
+ | **Prism** | CMU `05_18`, dance including arabesques, turning jumps and back bends | About 15.24 seconds, 1,830 source frames at approximately 120 fps | Faceted dancer, 2,850 weighted vertices. |
13
+
14
+ Both meshes contain 31 source joints and vertices blended between neighboring
15
+ bones. Their geometry is generated by the project's own Three.js character
16
+ code. The source poses, movement, timing and root translations come from the
17
+ pinned motion captures. The full source actions, including their calibration
18
+ first frame, are retained.
19
+
20
+ ## Use the gallery
21
+
22
+ Choose a character, then press **Load & play**. Initial navigation loads the
23
+ interface and renderer; the selected rig, package, profile, recording and WASM
24
+ are requested only after that explicit load. Choosing the other character
25
+ unloads the previous skin, recording cache and native handle. **Unload** releases
26
+ them without loading another character.
27
+
28
+ The transport supports play/pause, restart, integer-pip seeking and preview
29
+ speed. Playback stops at the final recorded pip. Camera orbit/zoom and reset
30
+ change the view, while the camera follows the captured root translation. A
31
+ reduced-motion preference leaves a newly loaded character paused. Desktop and
32
+ mobile layouts share the same native playback.
33
+
34
+ The clips have **time-only timing**: one second maps to one nominal beat and
35
+ 64 native pips. Their musical BPM and downbeat phase have not been annotated or
36
+ verified. Preview speed changes elapsed playback speed; it does not establish
37
+ musical beat matching. No music is bundled. To align a recording to a song,
38
+ provide source beat anchors and a target clock through the offline
39
+ [rig-motion compiler](rig-motion.md).
40
+
41
+ ## Skeleton and playback contract
42
+
43
+ Each dance uses its exact source skeleton profile: 31 joints, 127 numbered
44
+ channels, saved rest transforms, a stable type URI and a profile fingerprint.
45
+ These are source-skeleton recordings; Rigify retargeting is a separate explicit
46
+ operation. The browser applies complete WXYZ quaternion groups and root
47
+ translation in source space, then transforms the presentation group for the
48
+ Three.js view. Styling, floor placement and camera framing do not alter the
49
+ recording or its source profile.
50
+
51
+ The production TypeScript motion-package loader checks the manifest, expected
52
+ profile, compressed byte count and hashes before constructing the existing
53
+ native WASM recording handle. A scene holds only its current motion chunk; the
54
+ transport requests absolute integer pips. JavaScript handles loading, clock
55
+ position, decoded channel application and rendering. Native recording sampling
56
+ remains shared with the Python/C++ implementation.
57
+
58
+ ## Rebuild just the gallery
59
+
60
+ The asset builder needs the native Python extension and the optional
61
+ `motion-import` NumPy extra. The frontend builder needs the lockfile's exact
62
+ Three.js and esbuild versions. Blender and the bulk optional motion collections
63
+ are not required.
64
+
65
+ Run the temporary-directory preflight required by
66
+ [AGENTS.md](../AGENTS.md), then:
67
+
68
+ ```sh
69
+ python -m pip install '.[motion-import]'
70
+ npm ci
71
+ python tools/build_mesh_demo_assets.py --output-dir generated/mesh-demo-assets --cache-root .cache/motion-sources/cmu-dance --download
72
+ node tools/build_mesh_demo.mjs --assets-dir generated/mesh-demo-assets --output-dir dist/pages/meshes
73
+ python -m http.server 8000 --directory dist/pages
74
+ ```
75
+
76
+ Open `http://localhost:8000/meshes/`. The builder defaults to the checkout's
77
+ package version and complete Git commit; `--version` and `--commit` may supply
78
+ the validated release identity explicitly.
79
+
80
+ `--download` authorizes only the two URLs pinned in
81
+ [`tools/mesh_demo_sources.json`](../tools/mesh_demo_sources.json). The source
82
+ files total about 3.1 MB and are checked for exact size and SHA-256 before
83
+ conversion. Without `--download`, the asset builder requires those verified
84
+ files already in its local cache. Use a fresh asset output directory; source
85
+ archives and generated motions stay out of the base packages and default
86
+ catalogue. The frontend builder can replace its own previously generated
87
+ output tree and removes stale files as part of that bounded rebuild.
88
+
89
+ The source asset directory contains `catalogue.json`, two scoped motion
90
+ packages, source rigs, notices and independent browser references. The final
91
+ HTTP route adds locally bundled Three.js, the production package loader,
92
+ `recording.wasm`, `build.json` and the UI. It has no runtime CDN dependencies.
93
+
94
+ ## Reproduce the browser checks
95
+
96
+ ```sh
97
+ python -m pip install playwright==1.64.0
98
+ python -m playwright install chromium
99
+ npm test
100
+ python -m unittest discover -s tests/python -p test_mesh_demo_assets.py -v
101
+ python -m unittest discover -s tests/python -p test_release_pages.py -v
102
+ python tools/test_mesh_demo_browser.py --site-dir dist/pages --output-dir dist/mesh-qa --gallery-only
103
+ ```
104
+
105
+ The browser tool starts its own loopback HTTP server. `--chromium /path/to/chromium`
106
+ selects an existing matching browser; otherwise it uses Playwright's installed
107
+ Chromium. Software WebGL through ANGLE/SwiftShader is supported for headless CI.
108
+ It saves a JSON receipt and one desktop/mobile PNG per character.
109
+
110
+ For each dance, four reference pips cover the first frame, an early moving
111
+ frame, the middle and the last sample. The tool compares every native normalized
112
+ channel with separately decoded package bytes, every physical channel with the
113
+ Python-native player, and all 31 joint world matrices with independently
114
+ composed NumPy transforms. It also checks actual skinned vertex movement,
115
+ playback controls, lazy fetches, disposal, layout and unhandled browser errors.
116
+ The browser never fetches the reference files for playback.
117
+
118
+ A `--gallery-only` run reports exactly that scope. It is useful when developing
119
+ the meshes without rebuilding the large catalogue snapshot, but cannot seal a
120
+ release site. The complete release path first renders and tests the existing
121
+ native visualizer, prepares its navigation link, then runs this tool without
122
+ `--gallery-only` against the combined site. See the
123
+ [release guide](release-demo.md) for exact assembly, attachment and deployment
124
+ commands.
125
+
126
+ ## Sources and rights
127
+
128
+ The captures originate in the [CMU Graphics Lab Motion Capture Database](http://mocap.cs.cmu.edu/),
129
+ with [subject 60](http://mocap.cs.cmu.edu/search.php?subjectnumber=60) and
130
+ [subject 5](http://mocap.cs.cmu.edu/search.php?subjectnumber=5) identifying the
131
+ performances. The pinned BVH conversion is Bruce Hahne's cgspeed distribution,
132
+ retrieved through the exact mirror commit recorded in the source plan. The
133
+ converter's 60 fps correction for the salsa take is explicit in its provenance.
134
+ The approximately 120 fps value for the second take follows its saved BVH frame
135
+ time. Both retain documented units, axes and source hashes.
136
+
137
+ The motion data carries CMU's custom terms: it may be copied, modified and used
138
+ in commercial projects, with direct resale of the motion data prohibited.
139
+ It is not relabeled as CC0 or CC BY. Each generated package and downloadable
140
+ mesh bundle includes the source terms, CMU/NSF attribution, converter credit,
141
+ source URLs, hashes and conversion choices. Original character geometry and
142
+ runtime dependencies retain their own project/dependency notices. Consult the
143
+ bundled `LICENSE.txt`, `THIRD_PARTY_NOTICES.md` and `SOURCES.json` for the full
144
+ source-specific record.
145
+
146
+ ## Release and deployment
147
+
148
+ The [Release demo workflow](../.github/workflows/demo-release.yml) builds this
149
+ gallery from the same exact tag as the root pattern viewer. The
150
+ `DanceRudiments-pages-vX.Y.Z.zip` download contains the complete site; the
151
+ separate `DanceRudiments-mesh-demos-vX.Y.Z.zip` contains the `meshes/` route.
152
+
153
+ Pages deployment uses the repository's existing enablement variable and
154
+ environment approvals. Only the latest stable release can publish the site;
155
+ older-tag rebuilds keep their release downloads without replacing the latest
156
+ Pages content. The post-deployment verifier checks both routes and every public
157
+ asset against the tested version/commit manifest.
158
+
159
+ Earlier API/package changes did not add a visible mesh renderer. A green
160
+ main-branch check or a successful deployment of the older pattern visualizer
161
+ therefore does not establish that this gallery was deployed. The mesh page
162
+ requires a fresh release containing these builders; pre-gallery tags such as
163
+ `v0.3.0` cannot acquire the new code through a manual rerun.
@@ -0,0 +1,180 @@
1
+ # Full-catalogue visualizer and MIDI beat player
2
+
3
+ The main `harness/index.html` is now a **single self-contained HTML file** with
4
+ all default movements, the C++/WebAssembly snapshot sampler, the complete beat
5
+ library, a small procedural percussion synthesiser, and all interface code.
6
+ `harness/index.json` is an audit sidecar, not a browser dependency. The top of the
7
+ README links directly to the downloadable HTML, rather than an unbuilt harness,
8
+ a GitHub source-code view, or an external JavaScript/WASM dependency.
9
+
10
+ ## Listen and compare
11
+
12
+ Open the HTML locally and press **Play**. The initial beat is the complete
13
+ four-bar Amen study at the harness's current BPM. **MIDI sound** mutes/unmutes the
14
+ synthesiser; **Volume** controls its level. Nothing auto-plays. Tempo is shared
15
+ with animation, including pause, restart, seeking, single-pip stepping and live
16
+ BPM changes. Changing the beat preserves phase and BPM. **Use suggested BPM** is
17
+ the explicit action that changes tempo to the score's suggestion.
18
+
19
+ The Library menu separates dance music, recorded Groove MIDI excerpts, abstract
20
+ event studies, and the original C++ sticking rudiments. Search also matches IDs
21
+ and families. A selected beat remains available while filtering.
22
+
23
+ **Only matching movements** restricts the catalogue to movements derived from
24
+ that score. **Compare this beat's movements** selects its first four mappings;
25
+ other cards can be chosen normally. **Follow compared movement** automatically
26
+ selects the first newly compared movement's source beat. Individual cards also
27
+ have **Use this movement's beat**. A waveform or geometric path without scored
28
+ onsets can still be auditioned against any beat, but is not assigned a fictitious
29
+ source drum pattern.
30
+
31
+ Expand beat details for the complete onset grid, per-lane mutes, provenance and
32
+ MIDI import/export. The grid uses exact fractional source times and represents
33
+ velocity with dot size/opacity; its numbered axis counts quarter notes over the
34
+ whole loop. It is not an automatically quantised sixteenth-note grid.
35
+
36
+ The local-audio option is retained. Loading a track pauses MIDI and uses the
37
+ track's audio-element clock, BPM and manually entered beat-zero offset instead.
38
+ Remove the track to return to MIDI. The two audio sources are not accidentally
39
+ played together. Hiding the tab pauses playback to avoid background timer drift.
40
+
41
+ ## Coverage
42
+
43
+ The current registry includes **305 scores**:
44
+
45
+ | Source | Scores |
46
+ | --- | ---: |
47
+ | Club 05 + Dancefloor 06, deduplicated by exact shared rhythm identity | 68 |
48
+ | Earlier authored event scores (Euclidean, additive, polyrhythm, swing, sticking, etc.) | 226 |
49
+ | Groove MIDI excerpt A/B, played and grid variants | 4 |
50
+ | Original C++ drum-sticking definitions | 7 |
51
+
52
+ They are linked to **1,325 movements**. All **1,731** movements are available in the
53
+ same visualizer; continuous shapes without note events can use any accompaniment.
54
+ This is not a claim that 305 unrelated dance genres or recordings were imported.
55
+ Some entries intentionally share timing while differing in instrument/gesture
56
+ emphasis. Abstract event lanes receive explicitly assigned audition timbres.
57
+
58
+ `tools/harness_beats.py` reads the committed `*/rhythms.json`, `*/*.score.json`,
59
+ pack provenance, and default-selection lock. It also extracts the seven legacy
60
+ stroke lists from their C++ function bodies rather than duplicating those lists.
61
+ A changed/unsupported C++ representation fails loudly. This preserves the saved
62
+ rhythms, not freshly recalculated platform-dependent motion definitions.
63
+
64
+ The original stroke timestamps represent **gesture starts**; those movement
65
+ peaks occur half a stroke-width later. Generated scores retain their declared
66
+ onset/peak/end anchor semantics. Neither the source timings nor movement tables
67
+ are silently shifted to conceal that distinction.
68
+
69
+ The Amen study retains all 16 quarter-note beats (four bars), 81 programmed note
70
+ onsets, ghost-note velocities, displaced later backbeats and the cymbal change.
71
+ **Amen / ghost notes A–B** switches to the existing no-ghost comparison, without
72
+ changing the animation tempo. It remains a quantised structural interpretation,
73
+ not the original recording or measured drummer microtiming.
74
+
75
+ ## MIDI support
76
+
77
+ **Download beat as MIDI** writes a real type-1 Standard MIDI File, with a tempo
78
+ meta-event at the current animation BPM, meter when known, note-on/off events,
79
+ credits and an end-of-track marker at the exact loop endpoint where representable.
80
+ Percussion uses zero-based channel 9 (human channel 10); bass uses channel 1.
81
+ Built-in listening preserves rational source onsets; SMF export chooses a PPQ
82
+ that represents those fractions exactly where possible (maximum 32767). When the
83
+ combined denominators do not fit, it uses 30720 PPQ and reports the maximum timing
84
+ quantisation. MIDI velocities necessarily use 7-bit precision. Notes that would
85
+ extend past the loop end are gated at the boundary in exported files.
86
+
87
+ **Import MIDI** supports PPQ type-0/type-1 `.mid`/`.midi` files, up to 2 MiB, 65,536
88
+ notes, 128 tracks and 4,096 quarter notes. It handles running status, note-offs,
89
+ velocity-zero note-ons, track-end silence, tempo/meter metadata and ignorable
90
+ SysEx. Invalid/truncated input fails without replacing the current beat. SMPTE
91
+ and independent-sequence type-2 files are deliberately rejected.
92
+
93
+ Imported tempo maps do **not** change the animation tempo; tick positions are
94
+ normalised to quarter notes and played at the harness BPM. The first embedded
95
+ tempo is offered as a suggestion. Channel-10 notes use the synthesised kit;
96
+ other channels use simple pitched cues. This is a rhythm audition player, **not**
97
+ a full General MIDI sound module: patch changes, controllers, pitch bends,
98
+ sustain pedals and expressive articulation are not emulated. Long imported scores
99
+ have a horizontally scrollable grid; at most 32 distinct lanes are drawn, while
100
+ all valid notes remain scheduled. There is a defensive 256-voice playback cap.
101
+ No MIDI hardware access or permissions are requested.
102
+
103
+ The Groove MIDI attribution/licence and source/transformation notes are retained
104
+ in the HTML and exported MIDI copyright/text metadata. Original recordings and
105
+ commercial samples are not included. Existing source terms still apply.
106
+
107
+ ## Clock design
108
+
109
+ Web Audio's `currentTime` is the scheduling clock once audio is enabled. A 25 ms
110
+ control timer schedules a short 100 ms look-ahead window; `requestAnimationFrame`
111
+ never triggers drum hits. Visual phase uses `getOutputTimestamp()` where available
112
+ and a reported-latency fallback, so it follows the output audio clock rather than
113
+ an unrelated elapsed-time counter. Device/display latency can still affect
114
+ perceived synchronisation; this is not a hardware calibration tool.
115
+
116
+ Each musical window is half-open to avoid duplicate hits at loop boundaries.
117
+ Beat times remain unwrapped; every movement applies its own C++ loop modulus.
118
+ Pause, seek, source changes, mutes and tempo edits cancel queued voices and rebase
119
+ the same clock. Pending audio unlocks are invalidated by pause. A short scheduling
120
+ lead is used when rebasing; the beat position is preserved, but sample-seamless
121
+ DJ-style tempo warping is not promised. Web Audio must first be enabled through
122
+ a user gesture. The maximum accepted tempo is 300 BPM and the minimum is 20 BPM.
123
+
124
+ ## Build and test
125
+
126
+ ```sh
127
+ python -m pip install .
128
+ python tools/build_harness.py
129
+ python tools/harness_beats.py --output harness/beats.json
130
+ node tools/export_harness_midi.mjs dist/midi
131
+ node --test tests/typescript/beat-player.test.js
132
+ python tools/test_midi_harness.py harness/index.html
133
+ ```
134
+
135
+ Generation requires clang++ and wasm-ld, as the previous native release demo did.
136
+ The browser test requires Playwright/Chromium. `--in-memory` is available only for
137
+ restricted test environments and is recorded in the report; normal CI tests local
138
+ file loading. The checked-in HTML is an immediate download; regenerate and commit
139
+ it when changing the catalogue or player. The per-push build produces a fresh
140
+ artifact even when a checked-in preview has not yet been refreshed.
141
+
142
+ Release generation uses the same template/player and reads that tag's native
143
+ catalogue. The release workflow validates MIDI playback before packaging its
144
+ HTML, screenshots and checksums. Stable `DanceRudiments-visualizer.html` and
145
+ `DanceRudiments-visualizer.json` aliases are attached as well, enabling a stable
146
+ `releases/latest/download/DanceRudiments-visualizer.html` URL after the next release
147
+ containing these changes. Older releases are not retroactively altered.
148
+
149
+ ## Optional live GitHub Pages site
150
+
151
+ The README's **downloadable HTML works without Pages**. For a hosted click-to-play
152
+ copy at `https://kieransimkin.github.io/DanceRudiments/`:
153
+
154
+ 1. Set Repository **Settings → Pages → Source → GitHub Actions**.
155
+ 2. Add the repository Actions variable `DANCERUDIMENTS_PAGES_ENABLED` with value
156
+ `true`.
157
+ 3. Publish a fresh release tag containing the demo builders, or manually run
158
+ **Release demo** for an existing compatible release tag. The environment's
159
+ normal deployment approvals still apply.
160
+
161
+ **Pattern visualizer** builds and tests artifacts on pushes and pull requests.
162
+ It no longer deploys Pages: a visualizer-only deployment would erase the optional
163
+ mesh gallery. **Release demo** is the sole Pages publisher and uses the exact
164
+ validated release tag. It publishes **only `dist/pages`**, containing this
165
+ visualizer at the root and the [3D dance examples](mesh-demos.md) at `meshes/`.
166
+ Source files, caches and the bulk optional motion collections are outside that
167
+ site. A gallery recording loads only after **Load & play** is clicked for that
168
+ character.
169
+ Applying or pushing the code alone does not publish a new release or website.
170
+
171
+ See [the release demo guide](release-demo.md) for build, attachment verification,
172
+ Pages deployment and post-deployment checks. The existing root URL and offline
173
+ visualizer download remain available through the same interfaces.
174
+
175
+ ## Protocol and hosting references
176
+
177
+ - W3C Web Audio API: https://www.w3.org/TR/webaudio/
178
+ - MIDI Association, Standard MIDI Files: https://midi.org/standard-midi-files
179
+ - GitHub Pages custom workflows: https://docs.github.com/en/pages/getting-started-with-github-pages/using-custom-workflows-with-github-pages
180
+ - GitHub Pages publishing source: https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site