JSON schema (v1)¶
The alignment save file is a single UTF-8 JSON document with indent=2, strict finite-number policy (allow_nan=False), and schema_version == 1. This page documents every field with types and validation rules.
Example¶
{
"schema_version": 1,
"participant_id": "participant_042",
"participant_folder": "C:/data/study/participant_042",
"global_shift_seconds": 412.3481,
"alignment_notes": "",
"midi_files": [
{
"filename": "trial_001.mid",
"file_path": "disklavier/trial_001.mid",
"unix_start": 1707345000.0,
"unix_end": 1707345187.4,
"duration": 187.4,
"sample_rate": 1920.0,
"ticks_per_beat": 480,
"tempo": 500000.0
}
],
"camera_files": [
{
"filename": "C0001.MP4",
"xml_filename": "C0001M01.XML",
"mp4_path": "overhead camera/C0001.MP4",
"xml_path": "overhead camera/C0001M01.XML",
"raw_unix_start": 1707344587.0,
"raw_unix_end": 1707345037.1,
"duration": 450.1,
"capture_fps": 239.76,
"total_frames": 107928,
"alignment_anchors": [
{
"midi_filename": "trial_001.mid",
"midi_timestamp_seconds": 12.456,
"camera_frame": 2987,
"label": "phrase 1 opening"
}
]
}
]
}
Top level¶
| Field | Type | Notes |
|---|---|---|
schema_version |
int | Must equal 1. Mismatch raises UnsupportedSchemaVersionError. |
participant_id |
string | Typically the last component of participant_folder. |
participant_folder |
string | Absolute path at save time; used as the base for relative file_path, mp4_path, xml_path values. |
global_shift_seconds |
float | Must be finite. Zero is legal. |
alignment_notes |
string | Optional (defaults to "" on load if missing). Reserved for future use. |
midi_files |
array of MidiFile | May be empty. |
camera_files |
array of CameraFile | May be empty. |
MidiFile¶
| Field | Type | Notes |
|---|---|---|
filename |
string | Basename, e.g. "trial_001.mid". |
file_path |
string | Relative to participant_folder when possible; absolute or empty strings pass through unchanged. Resolved at load via _resolve_path. |
unix_start |
float | Must be finite. |
unix_end |
float | Must be finite. |
duration |
float | Must be finite and > 0. |
sample_rate |
float | Must be finite. Typically 1 / time_resolution (~1920). |
ticks_per_beat |
int | No validation applied. |
tempo |
float | Microseconds per beat. No validation applied. 500000.0 is the 120-BPM default. |
CameraFile¶
| Field | Type | Notes |
|---|---|---|
filename |
string | MP4 basename, e.g. "C0001.MP4". |
xml_filename |
string | XML basename, e.g. "C0001M01.XML". |
mp4_path |
string | Same resolution rule as file_path. |
xml_path |
string | Same resolution rule. |
raw_unix_start |
float | Must be finite. Unmodified mtime-derived start. |
raw_unix_end |
float | Must be finite. |
duration |
float | Not additionally validated at load. |
capture_fps |
float | Must be finite and > 0. Typically ~239.76. |
total_frames |
int | Must be > 0. |
alignment_anchors |
array of Anchor | May be empty. |
active_anchor_index is not persisted
The runtime CameraFileInfo dataclass has an active_anchor_index field, but it is deliberately not part of the JSON schema. Every camera clip starts with active_anchor_index = None after a fresh load, by design.
Anchor¶
| Field | Type | Notes |
|---|---|---|
midi_filename |
string | Must match the filename of some MIDI entry in the same file. |
midi_timestamp_seconds |
float | Must be finite. Seconds from the start of the referenced MIDI file. |
camera_frame |
int | Must be >= 0. 0-indexed cv2 frame. |
label |
string | Optional (defaults to "" on load if missing). Free text. |
Validation rules¶
After the JSON is parsed and rehydrated, _validate_state runs these checks. Any failure raises CorruptAlignmentFileError with a specific reason:
global_shift_secondsis finite.- For every MIDI file:
unix_start,unix_end,duration,sample_rateare finite.duration > 0.
- For every camera file:
raw_unix_start,raw_unix_end,capture_fpsare finite.capture_fps > 0.total_frames > 0.
- For every anchor:
midi_filenameexists in the MIDI file set.midi_timestamp_secondsis finite.camera_frame >= 0.
The validation is strict and non-recoverable — if any check fails, the load aborts and the previous session is preserved.
Path resolution¶
stored value → runtime value (after load)
------------------------------- -------------------------------
"" → "" (left as-is)
"disklavier/trial_001.mid" → {participant_folder}/disklavier/trial_001.mid
"C:/absolute/path/file.mid" → C:/absolute/path/file.mid (left as-is)
Saving does the inverse: absolute paths under participant_folder become relative, and paths outside that folder or empty strings are stored as-is.
This means:
- Moving a participant folder to a new location and running Load Alignment… will prompt to rebase paths once; afterward the session works unchanged (see Moving participant folders).
- Absolute paths that were never under
participant_foldersurvive saves and loads unchanged but won't be rebased.
Editing the JSON by hand¶
Hand-editing is possible and supported — the schema is intentionally flat, with no hashes or signatures. The only rules are:
- Keep
schema_version == 1. - Keep every numeric field finite (no
NaN,Infinity). - Keep
duration > 0,capture_fps > 0,total_frames > 0. - Keep every
anchor.midi_filenamematching an existing MIDI entry. - Keep
anchor.camera_frame >= 0.
If you break any of these, loading the file raises CorruptAlignmentFileError with a descriptive message.