Static hosting for the Motion Bank online scores — interactive,
horizontally scrolling web scores made up of cells (text, images, videos,
interactive annotation/media modules) that communicate with each other via
postMessage-based messaging (e.g. clicking a timed scene annotation moves a
synchronized video cell to that moment).
Originally served from http://scores.motionbank.org (ca. 2013–2014, Brunch /
Chaplin / Backbone build). This repository is a fully static export: the
scores, their JSON data, and their media are all served as files.
| Path | Score | Content focus |
|---|---|---|
/dh |
Using the Sky — Deborah Hay's No Time To Fly | text cells, video recording cells with scene overlays, data visualizations |
/jbmf |
Seven Duets — Jonathan Burrows & Matteo Fargion | Vimeo video cells, interactive generator/performer modules |
/two |
TWO — "A Choreographic Object" companion score | image/text cells, Vimeo, self-hosted three.js mocap viewers, S3 text modules |
A landing page (index.html) links to all three. Shared assets (fonts,
styles) live in /assets.
index.html landing page linking the three scores
dh/ jbmf/ two/ score front-ends (HTML shell + compiled JS/CSS)
api/<score>/ score data as static JSON
(sets.json, sets/<id>.json, cells/<id>.json)
api/jbmf/pm2/ PieceMaker 2 event data, one JSON per video event
(extracted from the restored heroku pg dump)
system/uploads/ poster/upload images referenced by the JSON
assets/ shared landing-page assets, Open Sans fonts
media/ self-hosted media — NOT in git; ships as a separate
archive (see "Media self-hosting"):
/media/dh/piecemaker/*.mp4, /media/jbmf/piecemaker/*.mp4
(mirrored from CloudFront), /media/two/data/…
(mirrored from S3)
modules/ score iframe modules, all served same-origin
(generators, performer modules, mocap viewer, …)
Dockerfile nginx-based container image for the whole site
docker/ nginx site configuration used by the image
Each score folder contains a MODERNIZATION.md chronicle documenting the
2026 modernization (analysis, findings, changes, verification, and known
limitations):
Each score is a set: a large canvas-like grid of cells, laid out by
x, y, width, height grid coordinates (set JSON) and navigated by
horizontal paging. Sets and cells were authored in the MoSys CMS
(score-system-backend,
the original Rails publishing system) and are rendered by the
score-system-frontend
Chaplin.js/Backbone single-page app. The compiled score front-end
(app-base.js + app-extend.js, a Brunch/Chaplin/Backbone bundle) fetches
api/<score>/sets.json, renders the cell grid, and lazily activates cells
as they scroll into view.
A cell (api/<score>/cells/<id>.json) is the atomic content unit. Its
type decides the view class: image, text, html, vimeo,
set_link, and — the most important one — iframe. An iframe cell's
iframe-src (a Handlebars template with config interpolation) points at a
self-contained module page under /modules/, sized and positioned by the
grid, communicating with the score and other cells over postMessage.
Cells additionally carry additional_fields that surface as DOM
attributes (autoload, sticky, solo, css-*, attr-*) controlling
load behaviour and styling.
All cross-cell interaction runs through the PostMessenger wrapper around
postMessage (postmessenger-js).
The score shell acts as a hub:
- every iframe cell gets a
connect?→connect!handshake on load; thedomain=URL parameter (added by the shell at render time) pins the accepted origin — now derived fromwindow.location.origin, which is what makes the scores domain-independent; - messages starting with
>>(e.g.>>set-scene,>>ping) received from one cell are re-broadcast to all other open cells, and published internally (dispatcher:); that is how a click in the text score seeks a video in another cell; get-config→set-confighands the shell's runtime config (media hosts, paths) to modules that need it;set>store/set>fetchlet modules keep shared state in the shell (e.g. the "current scene" a viewer should restore when reopened).
The players themselves speak a small protocol:
>>ping {title} (annotation cell → player: seek to the event titled
title), >>set-scene <title> (score text link → player: seek to scene),
get-scene / set>fetch (player → shell: restore scene state),
flowplayer:finish (player → score: recording ended).
The 2013–2014 scores were driven by PieceMaker 2 (PM2), a Ruby/
Postgres research tool (The Forsythe Company) for annotating rehearsal
— software now open-source as
piecemaker2-api +
piecemaker2-frontend.
documentation. Recordings of rehearsals/performances were ingested as
video events, and researchers pressed keys while watching to create
annotation/marker/scene events, timestamped in absolute Unix epoch
seconds (utc_timestamp). Events belong to groups (one group per
score's film material, e.g. group 40 jbmf_all) and carry a fields
hash (title, file names, performers, …).
The scores never shipped a database: the modules called the PM2 HTTP API
(getEvent, findEvents(group, {from, to}), listEventsOfType) on
now-deleted Heroku apps. During the 2026 restoration the original Heroku
pg_dumps were located and restored (jbmf-pm2, deborah-hay-pm2,
notimetofly), and the events were exported to static JSON — one file
per referenced video:
api/jbmf/pm2/<vid>.json—{ video, events }: the video event (utc_timestamp,duration,fields.file,vid_service,vid_service_id,alt-angle-*, …) plus every event inside its time window.vid=in a cell'siframe-srcis simply the PM2 event id; annotation offsets areevent.utc_timestamp − video.utc_timestampin seconds — exactly what the originalfindEventscall returned.modules/jbmf/generators/yt_annot_01/pm2-data/yt-lookup.json— YouTube id → vid mapping for the annotation cells linked byytid=.dh/dhay/player/scenes/<file>.json—{ scene title → seconds }maps generated from group 24/29sceneevents (same offset formula), which the/dhplayer uses for>>set-sceneseeks.modules/dhay/data_inspect_tracks_01/videos.json— the NTTF video list with theirdataevents (motion-capture track files) for the track-data inspection cell.
- /jbmf pm_player_01 (138 cells) — rehearsal filmings of the Burrows/
Fargion duets. PM2 video events carry
fields.file(e.g.D01_T03_C_Single_MF_Come_T02) with alternate camera angles infields['alt-angle-lh|ls|rh|rs'](selected via&angle=in the cell URL). The MP4s are self-hosted under/media/jbmf/piecemaker/and played by a native HTML5<video>element; annotations of the configured types (&type=, performer-filtered) drive>>pingseeks in partner cells. - /jbmf yt_annot_01 (7 cells) — YouTube videos (interview excerpts, e.g. "Heads In Sets", "Marble machine") with time-stamped annotations that steer other cells; plays via the YouTube iframe API.
- /jbfm vimeo cells (294) — the interview score proper. These play
directly from Vimeo's player (verified 2026-11-06: player + HLS segments
load and stream from any domain; the earlier "domain-locked" reading was
an artifact of Cloudflare bot-blocking curl, not referer enforcement).
See
jbmf/MODERNIZATION.md§6. - /dh recording cells (49) — No Time To Fly rehearsal takes
(camera-angle pairs: wide
*_sync_AJA_1+ center*_sync_Center_Small). Served from/media/dh/piecemaker/through the local Video.js player; scene titles fromscenes/<file>.jsonsync the text score and overlay visualizations. - /two — Vimeo, S3 text/infographic modules (now self-hosted under
/media/two/data/), and the self-hosted three.js mocap viewers (seemodules/two/mocap/README.md).
All media and data now resolve against the serving origin. The original hosts were mirrored once and are no longer contacted at runtime:
| Original | Mirrored to | State |
|---|---|---|
…/jbmf/piecemaker/*.mp4 (CloudFront, RTMP-era) |
media/jbmf/piecemaker/ |
complete (419 files) |
…/dh/piecemaker/*.mp4 (CloudFront) |
media/dh/piecemaker/ |
complete (123 files) |
s3.amazonaws.com/motionbank-media/two/… |
media/two/data/ |
complete |
lab.motionbank.org/dhay/data/*_com.txt |
modules/dhay/data_inspect_tracks_01/data/ |
20/22 (2 files already gone upstream) |
| Open Sans (Google Fonts) | assets/fonts/ |
complete |
| jQuery / froogaloop2 CDNs | vendored in each module | complete |
The media/ directory (~7 GB, 542 MP4s + text cells) is not stored in
git — 25 of the dh full-take recordings exceed GitHub's 100 MB file
limit, and it would dominate the repository history. It ships as a
separate archive, published at a fixed URL:
https://assets.motionbank.org/archive/motionbank-scores-media.tar.gz (gzip-compressed tar, ~7.8 GiB)
A SHA-256 checksum is published next to the archive
(…/archive/motionbank-scores-media.tar.gz.sha256) — verify before
extracting:
# fetch and extract into the repository root (creates ./media/):
curl -O https://assets.motionbank.org/archive/motionbank-scores-media.tar.gz
curl -O https://assets.motionbank.org/archive/motionbank-scores-media.tar.gz.sha256
# verify the download (macOS: replace sha256sum with shasum -a 256):
sha256sum -c motionbank-scores-media.tar.gz.sha256
# → motionbank-scores-media.tar.gz: OK
tar -xzf motionbank-scores-media.tar.gz -C /path/to/motionbank-scores-staticGitHub CI (.github/workflows/docker-image.yml) pulls the archive from
that URL at build time and verifies it against the published checksum
(the build fails on mismatch), so the Docker image is built without any
manual media setup.
After extraction the directory contains:
media/dh/piecemaker/*.mp4 No Time To Fly recording takes + camera angles
media/jbmf/piecemaker/*.mp4 Seven Duets filming takes + alternate angles
media/two/data/ TWO text cells + infographics (from S3)
The Docker image copies media/ in at build time, so extract the archive
before docker build. Without it, everything except the recording/
filming video cells works. The only external runtime dependencies are the
Vimeo/YouTube embeds themselves (and the optional Google Analytics);
occasional individual videos may still be private/deleted on Vimeo's side.
Everything under api/<score>/ is a static snapshot of the 2013 CMS
(MongoDB → JSON export, repaired in 2026 from a Feb-2020 dump). Three
levels, plus the timing side-data:
| File | Role |
|---|---|
api/<score>/sets.json |
Set index — one entry per set (id, title, path slug, published). The score menu shows published sets; the shell fetches one of sets/<id>.json on navigation. |
api/<score>/sets/<id>.json |
A set — the cell grid. Holds grid_cols/grid_rows (canvas size in cells), cell_width/cell_height (cell aspect), css_class_name, and a cells[] array of embedded cell copies with grid placement (x, y, width, height). |
api/<score>/cells.json |
Flat cell index (id, title, type, description…) without placement — used by search/overview helpers. |
api/<score>/cells/<id>.json |
A cell — the content record (no placement). The set file's embedded copy is authoritative for position. |
api/jbmf/pm2/<vid>.json |
PM2 timing data per video event — see "PieceMaker 2" above. |
dh/dhay/player/scenes/<file>.json |
{ scene title → offset seconds } for one NTTF recording. |
modules/dhay/data_inspect_tracks_01/videos.json |
NTTF video list + motion-capture data events for the track inspector. |
A cell record:
{
"title": "…", // shown in the cell's info overlay
"description": "…", // longer text, info overlay
"type": "iframe", // picks the Backbone view class (see below)
"id": "06605f716d6d6d8d052c0000", // Mongo ObjectId
"css_class_name": "cell-156", // legacy per-cell styling hook
"poster_image": { "url": "/system/uploads/…", "small": {…}, "medium": {…} },
"additional_fields": { … } // the type-specific payload (below)
}additional_fields is a grab-bag with three conventions the shell applies
literally:
attr-*→ DOM attributes on the cell element (attr-allowfullscreen,attr-mozallowfullscreen, …);css-*→ inline styles (css-background-color,css-font-size,css-border-color, …);- interaction flags:
autoload(open the cell when scrolled into view),sticky(keep playing when scrolled out),solo(close other cells of the same group when opened); class→ extra DOM classes (e.g.add-fullscreen,no-ui);- keys with an
xxx-prefix are archival CMS notes (original video id, file names) — not used at runtime.
Cell types and who handles them:
| Type | n | Handled by | Key fields / behaviour |
|---|---|---|---|
vimeo |
381 | cell-vimeo-view |
vimeo-id → builds the player.vimeo.com iframe; listens for Vimeo finish events to trigger on-finish links. |
context |
86 | cell-image view | poster-image-only cells (background image, no interaction). |
iframe |
81 | cell-iframe-view + cell-media-view |
iframe-src (Handlebars template, config-interpolated, gets domain=<origin> appended); opened lazily into an iframe; PostMessenger handshake. |
visualization |
53 | dh-specific view | base-path/fixed-image/per-recording/per-scene → builds the overlay PNG URL under /media/dh/app/cells/visualization/… (see dh/MODERNIZATION.md §7); switches with >>set-scene. |
recording |
48 | dh-specific iframe view | iframe-src at /dh/dhay/player/ with file=/vid=/gid=/perf=; reacts to >>set-scene by overlay-switching. |
html |
45 | cell-html-view |
html-content injected inline (credits, notes). |
image |
41 | cell-image-view |
image-src/img-src + optional link. |
text |
13 | cell-text-view |
text-content inline text. |
set_link |
11 | cell-set_link-view |
set-id/cell-id → navigates to another set (with optional deep-link cell). |
title |
2 | (template only) | big display text (e.g. "end" slate). |
The iframe modules under modules/ are ordinary static pages; their
iframe-src is the contract. Two templates recur: the PieceMaker-driven
players (pm_player_01, yt_annot_01, /dh/dhay/player/, the mocap
viewer) which consume a vid=/file= and speak >>ping/>>set-scene,
and self-contained generator/performer toys (gen_mash_01, 05_clap,
audio, the Edge-Animate performer/* set) which only listen for
activate!/deactivate!.
Placement coordinates: x/width are columns, y/height are rows in
the set's grid_cols×grid_rows canvas; the shell computes pixel sizes
from cell_width/cell_height and the viewport, then pages horizontally
through grid_cols. The pager, keyboard arrows and scrollToCell
deep-links all work off those numbers.
Everything in this repository is a static snapshot of a live system whose source code is open on the motionbank GitHub organisation. The parts this site depends on:
| Site component | Original repository |
|---|---|
Score shells (app-base.js / app-extend.js brunch bundles, Chaplin.js + Backbone SPA, cell/set views, pager, PostMessenger wiring) |
score-system-frontend — its app-base/ directory compiles to the assets/javascripts/app.js bundle vendored here |
Cell & set creation (the Rails CMS that produced every JSON under api/ — cells, sets, grid placement, additional_fields) |
score-system-backend — "MoSys", a »choreographic« publishing system |
Cell ↔ iframe messaging (>>ping, >>set-scene, get-config, …) |
postmessenger-js — middleware over window.postMessage; the protocol each vendored module speaks |
PieceMaker 2 (annotation of the video material; source of all vid=, scene and marker timing data, and of the restored dumps in § "PieceMaker 2: the timing database") |
piecemaker2-api (Grape/Rack + Sequel) and piecemaker2-frontend (Backbone UI), wrapped by piecemaker2-app |
The PieceMakerApi client used by the original pm_player_01/yt_annot_01 modules |
piecemaker-api-client (Java + JavaScript) |
| Original per-score repositories (2013, pre-MoSys consolidation) | deborah-hay-online-score, burrows-fargion-online-score, two-osu-online-score (built at OSU ACCAD) |
| Related tools around the data | piecemaker1-tfc (PieceMaker 1, source of the PM1 notimetofly data), piecemaker2-api-migration (PM1→PM2), piecemaker2-timeline and piecemaker2-live-annotator (annotation tools), bvh (JS BVH mocap parser — same motion-capture material the track-data inspector shows), score-system-frontend-sample (example score front-end extension) |
The scores were built for http://scores.motionbank.org and no longer
worked in modern browsers. This restoration stands on previous work by
Florian Jenett (Motion Bank), who pulled the initial dump of the
scores site — including the database export used to repair the data —
from its previous hosting environment at MESO Frankfurt. All three
modernization passes were then performed within roughly 24 hours:
First pass (Nov 5, 2026) — browser compatibility. All three scores
modernized to run from any https:// domain with no hard-coded hosts:
- removed mixed content (http font/CDN links, http iframe embeds → https)
- the cross-frame messaging handshake (
domain=URL parameter andpostMessagetarget origins) now derives fromwindow.location.originat runtime instead of a hard-codedhttp://origin - dead hosts and links (
motionbank.meso.net,scores.motionbank.org) replaced with same-origin/https equivalents - removed IE conditional comments, Chrome-Frame prompts, and the retired Google Analytics snippet
- repaired a corrupted
two/index.htmltail
Second pass (Nov 5–6, 2026) — data and playback layer:
- restored the 192 corrupted cell JSON files (nginx "502 Bad Gateway" bodies from the original scrape) from a February 2020 database export, with the conversion verified by round-trip-diffing all intact files
- replaced the Flash-era
/dhflowplayer iframe module with a local Video.js 10.3.0 player module (/dh/dhay/player/) that speaks the score's PostMessenger protocol and plays the still-hosted CloudFront MP4s on the first click — no dead Heroku API calls - vendored the
/dhtext/notes iframe modules locally and upgraded every remaininghttp://iframe embed across all three scores tohttps
See the per-score chronicles for details, verification results, and the known remaining limitations (occasionally private/deleted Vimeo videos and Unity InterAct scenes playing as viewers only).
Third pass (Nov 6, 2026) — timing data and full self-hosting:
- located and restored the original Heroku
pg_dumps of the PM2 databases (jbmf-pm2,deborah-hay-pm2,notimetofly) and exported the event data to static JSON (see "PieceMaker 2" above) /jbmfpm_player_01(138 cells) andyt_annot_01(7 cells) now load their video metadata and annotations from that JSON instead of the dead API;pm_player_01's Flash/RTMP flowplayer stack was replaced with a native HTML5<video>player class with the same interface/dhscene timing maps generated from the dump — text-score scene links and overlay switching seek the recordings again- all remaining external hosts mirrored and repointed: piecemaker MP4s
(CloudFront) →
/media/, two's text/infographic cells (S3) →/media/two/data/, Open Sans →assets/fonts/, jQuery/froogaloop CDNs → vendored copies,lab.motionbank.orgfullscreen helper + track data → vendored. Only Vimeo/YouTube embeds (and the optional Google Analytics snippet) remain external.
What is still broken or unfinished, and what each item needs to move forward.
-
PM2 database dumps — done: found in the local
Motion Bank Heroku/folder, restored into throwaway Postgres containers, exported to static JSON (api/jbmf/pm2/,dh/dhay/player/scenes/,modules/dhay/data_inspect_tracks_01/), and wired into all players. The restoration process is documented injbmf/MODERNIZATION.md§7 anddh/MODERNIZATION.md§7. -
Vimeo videos play from any domain — resolved (2026-11-06): the earlier
curl403s onplayer.vimeo.com/…/configthat looked like a domain allowlist were Cloudflare bot-blocking curl itself. Verified in a real browser againsthttps://localhost:8443(headless Chromium): the vimeo cell iframe loads (HTTP 200), the player modules load, and HLS segments stream fromskyfire.vimeocdn.com— and playback was confirmed manually across several/jbmfcells. Individual videos may still be private/deleted on Vimeo's side. -
Two lost NTTF track-data files — the data-inspect cell ("Inspecting track data") loads center-of-mass track files from the lab mirror; 20 of 22 were recovered, but these two already returned 404 on
lab.motionbank.orgat restoration time (used by the videos2011-10/D01_T02_JeanineandJanine_D06T03_BackgroundSubstracted_Corrected): -2011-10/D01_T02_Jeanine/Tracked3DPosition_com.txt-Janine_D06T03_BackgroundSubstracted_Corrected/Tracked3DPosition_com.txtSearch old lab backups / deployment archives / Forsythe Company archives; drop-ins for `modules/dhay/data_inspect_tracks_01/data/<path>`. -
Unity-Web-Player mocap cells in
/two— done: all seven mocap viewers were rebuilt as a self-hosted three.js player (/modules/two/mocap/); skeleton, skinning and animation were extracted from the original Unity 4.2.unity3dbundles with UnityPy, and everyiframe-srcnow points at the local viewer. Seemodules/two/mocap/README.mdfor the pipeline and Unity-specific findings. The three additional Unity InterAct scenes (24 Recall, 25 Redux, 31 Phrasing Landscape) are ported through the same viewer — as playback (the games' compiled logic is not reproduced); InterAct 27–30 are web-native d3.js/paper.js pages, vendored under/modules/two/interact/.
- Junk/test cells in the data — done (2026-11-06): removed 30
unpublished
TEST:/DELETE --scaffolding sets (jbmf 15, dh 12, two 3) and the two junk-content cells ("IFrame messaging tester", "recording-test"); 363 dangling cell ids that existed only inside the removed sets' JSON went with them. The published scores are untouched (jbmf 51→36, dh 39→27, two 10→7 sets; all remaining set files verified present, scores re-tested in headless Chromium).two's "General information - do not delete" set was explicitly kept. - Missing
/dhoverlay PNGs — search material — done: the complete filename space the score can request is reconstructed indh/tools/list-overlay-pngs.py(from the cell data + the restored PM2 scene maps; 7,661 candidates indh/tools/overlay-png-candidates.txt). Search old S3/CloudFront bucket listings or deployment archives for these names, then drop recovered files into a localmedia/dh/app/cells/…mirror and repointdh/javascripts/app-extend.js. - CloudFront overlay images recovered and self-hosted — done:
HEAD-probing the reconstructed filename space against the original
CloudFront distribution found 5,151 of the 7,115 candidates live
(HTTP 200). All were downloaded into
media/dh/app/cells/ {recording,visualization}/full/…(372 MB, zero failures); a first candidate-name generator bug (double underscore in visualization names) was found by probe miss analysis and fixed before the final pass.dh/javascripts/app-extend.jsalready resolves overlays viafileHost + baseUrl=/media/dh/app/…, so no code change was needed — both overlay types verified loading in the live score page (recording overlay 640×360, visualization path overlay 700×700) with zero CloudFront requests. - Remaining remote embeds on
lab.motionbank.org(moba/logo,data_inspect_tracks_01, jbmf performer/generator modules) — done: all mirrored under/modules/and everyiframe-srcrewritten same-origin. The score sites no longer request anything fromlab.motionbank.org(20 of 22 track-data files recovered; the two lost ones are listed under "Data" above).
- Media self-hosting (~7 GB) — done: mirrored from CloudFront/S3,
all references repointed same-origin. The media set is not stored in
git (25 dh takes exceed GitHub's 100 MB limit); it ships as a separate
archive with download + checksum documented in the root README
("Media self-hosting"). Extract before
docker build. - Commit
docker/ssl/(self-signed-TLS image for local testing) and the matching.dockerignoreexception — done, including a fix for the healthcheck (dedicated plain-HTTP/healthendpoint; the server-level 301 used to hijack it). - Framework modernization (jQuery 2.0.3 / Backbone / Chaplin in the bundles) — deliberately out of scope so far; works in current browsers for what the scores use.
- Deploy-time verification on a real https host — local testing ran on self-signed TLS; a check against the production domain (and its certificate chain / CDN headers) is still recommended.
python3 -m http.server 8765
# then open:
# http://localhost:8765/ (landing page)
# http://localhost:8765/dh/
# http://localhost:8765/jbmf/
# http://localhost:8765/two/docker build -t motionbank-scores .
docker run --rm -p 8080:80 motionbank-scores
# → http://localhost:8080/ (landing page; scores under /dh, /jbmf, /two)The image is based on nginx:1.27-alpine and serves the whole site (score
front-ends, api/ JSON data, system/ images) with gzip compression,
sane cache headers, and a health check on /. Deploy it to any host or
container platform and put your TLS termination in front (or use the TLS
support of your ingress/reverse proxy) — the scores work from any
https:// origin.
No build step is required — the javascripts/ bundles are pre-built Brunch
output (vendor.js, app-base.js, app-extend.js) and the data lives in
api/. Each score is self-contained on its host (data is fetched
same-origin from api/<score>/).
© The Forsythe Company / Motion Bank, 2013–2014. All rights reserved. Modernization changes: 2026.
The entire modernization — analysis, data restoration from the archived Heroku database dumps, media scraping and self-hosting, player rewrites, Docker packaging and this documentation — was performed by Pi Coding Agent using the GLM 5.3 Flash LLM hosted at JGU University Mainz by the KITeGG project at Mainz University of Applied Sciences.