@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.
- package/AGENTS.md +72 -0
- package/CONTRIBUTING.md +8 -0
- package/README.md +261 -45
- package/dist/recording/recording.wasm +0 -0
- package/dist/recording/recording.wasm.json +5 -0
- package/dist/typescript/index.d.ts +2 -1
- package/dist/typescript/index.js +1 -0
- package/dist/typescript/motion-packages.d.ts +125 -0
- package/dist/typescript/motion-packages.js +625 -0
- package/docs/blender-motion-import.md +228 -0
- package/docs/mesh-demos.md +163 -0
- package/docs/midi-harness.md +180 -0
- package/docs/motion-packages.md +324 -0
- package/docs/release-demo.md +197 -0
- package/docs/rig-motion.md +285 -0
- package/docs/rigify-catalogue.md +246 -0
- package/examples/motion-packages/inspect_package.py +21 -0
- package/examples/motion-packages/page-motion.mjs +168 -0
- package/package.json +25 -4
|
@@ -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
|